373 lines
13 KiB
YAML
373 lines
13 KiB
YAML
---
|
|
- name: Tailscale Network Management
|
|
hosts: all
|
|
gather_facts: yes
|
|
vars:
|
|
tailscale_timestamp: "{{ ansible_date_time.iso8601 }}"
|
|
tailscale_report_dir: "/tmp/tailscale_reports"
|
|
|
|
tasks:
|
|
- name: Create Tailscale reports directory
|
|
file:
|
|
path: "{{ tailscale_report_dir }}"
|
|
state: directory
|
|
mode: '0755'
|
|
delegate_to: localhost
|
|
run_once: true
|
|
|
|
- name: Check if Tailscale is installed
|
|
shell: command -v tailscale >/dev/null 2>&1
|
|
register: tailscale_available
|
|
changed_when: false
|
|
ignore_errors: yes
|
|
|
|
- name: Skip Tailscale tasks if not available
|
|
set_fact:
|
|
skip_tailscale: "{{ tailscale_available.rc != 0 }}"
|
|
|
|
- name: Get Tailscale status
|
|
shell: |
|
|
if ! command -v tailscale >/dev/null 2>&1; then
|
|
echo "Tailscale not installed"
|
|
exit 0
|
|
fi
|
|
|
|
echo "=== TAILSCALE STATUS ==="
|
|
tailscale status --json 2>/dev/null || tailscale status 2>/dev/null || echo "Tailscale not accessible"
|
|
register: tailscale_status
|
|
changed_when: false
|
|
when: not skip_tailscale
|
|
|
|
- name: Get Tailscale network information
|
|
shell: |
|
|
if ! command -v tailscale >/dev/null 2>&1; then
|
|
echo "Tailscale not installed"
|
|
exit 0
|
|
fi
|
|
|
|
echo "=== TAILSCALE NETWORK INFO ==="
|
|
|
|
# Get IP addresses
|
|
echo "Tailscale IPs:"
|
|
tailscale ip -4 2>/dev/null || echo "IPv4 not available"
|
|
tailscale ip -6 2>/dev/null || echo "IPv6 not available"
|
|
echo ""
|
|
|
|
# Get peer information
|
|
echo "Peer Status:"
|
|
tailscale status --peers 2>/dev/null || echo "Peer status not available"
|
|
echo ""
|
|
|
|
# Get routes
|
|
echo "Routes:"
|
|
tailscale status --self=false 2>/dev/null | grep -E "^[0-9]" | head -10 || echo "Route information not available"
|
|
echo ""
|
|
|
|
# Check connectivity to key peers
|
|
echo "Connectivity Tests:"
|
|
key_peers="100.83.230.112 100.103.48.78 100.125.0.20" # atlantis, calypso, setillo
|
|
for peer in $key_peers; do
|
|
if ping -c 1 -W 2 "$peer" >/dev/null 2>&1; then
|
|
echo "✅ $peer - reachable"
|
|
else
|
|
echo "❌ $peer - unreachable"
|
|
fi
|
|
done
|
|
register: tailscale_network
|
|
changed_when: false
|
|
when: not skip_tailscale
|
|
ignore_errors: yes
|
|
|
|
- name: Check Tailscale service health
|
|
shell: |
|
|
if ! command -v tailscale >/dev/null 2>&1; then
|
|
echo "Tailscale not installed"
|
|
exit 0
|
|
fi
|
|
|
|
echo "=== TAILSCALE SERVICE HEALTH ==="
|
|
|
|
# Check daemon status
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
echo "Service Status:"
|
|
systemctl is-active tailscaled 2>/dev/null || echo "tailscaled service status unknown"
|
|
systemctl is-enabled tailscaled 2>/dev/null || echo "tailscaled service enablement unknown"
|
|
echo ""
|
|
fi
|
|
|
|
# Check authentication status
|
|
echo "Authentication:"
|
|
if tailscale status --json 2>/dev/null | grep -q '"BackendState":"Running"'; then
|
|
echo "✅ Authenticated and running"
|
|
elif tailscale status 2>/dev/null | grep -q "Logged out"; then
|
|
echo "❌ Not logged in"
|
|
else
|
|
echo "⚠️ Status unclear"
|
|
fi
|
|
echo ""
|
|
|
|
# Check for exit node status
|
|
echo "Exit Node Status:"
|
|
if tailscale status --json 2>/dev/null | grep -q '"ExitNodeID"'; then
|
|
echo "Using exit node"
|
|
else
|
|
echo "Not using exit node"
|
|
fi
|
|
echo ""
|
|
|
|
# Check MagicDNS
|
|
echo "MagicDNS:"
|
|
if tailscale status --json 2>/dev/null | grep -q '"MagicDNSSuffix"'; then
|
|
suffix=$(tailscale status --json 2>/dev/null | grep -o '"MagicDNSSuffix":"[^"]*"' | cut -d'"' -f4)
|
|
echo "✅ Enabled (suffix: $suffix)"
|
|
else
|
|
echo "❌ Disabled or not available"
|
|
fi
|
|
register: tailscale_health
|
|
changed_when: false
|
|
when: not skip_tailscale
|
|
|
|
- name: Analyze Tailscale configuration
|
|
shell: |
|
|
if ! command -v tailscale >/dev/null 2>&1; then
|
|
echo "Tailscale not installed"
|
|
exit 0
|
|
fi
|
|
|
|
echo "=== TAILSCALE CONFIGURATION ==="
|
|
|
|
# Get preferences
|
|
echo "Preferences:"
|
|
tailscale debug prefs 2>/dev/null | head -20 || echo "Preferences not accessible"
|
|
echo ""
|
|
|
|
# Check for subnet routes
|
|
echo "Subnet Routes:"
|
|
tailscale status --json 2>/dev/null | grep -o '"AdvertiseRoutes":\[[^\]]*\]' || echo "No advertised routes"
|
|
echo ""
|
|
|
|
# Check ACL status (if accessible)
|
|
echo "ACL Information:"
|
|
tailscale debug netmap 2>/dev/null | grep -i acl | head -5 || echo "ACL information not accessible"
|
|
echo ""
|
|
|
|
# Check for Tailscale SSH
|
|
echo "Tailscale SSH:"
|
|
if tailscale status --json 2>/dev/null | grep -q '"SSH"'; then
|
|
echo "SSH feature available"
|
|
else
|
|
echo "SSH feature not detected"
|
|
fi
|
|
register: tailscale_config
|
|
changed_when: false
|
|
when: not skip_tailscale
|
|
ignore_errors: yes
|
|
|
|
- name: Tailscale network diagnostics
|
|
shell: |
|
|
if ! command -v tailscale >/dev/null 2>&1; then
|
|
echo "Tailscale not installed"
|
|
exit 0
|
|
fi
|
|
|
|
echo "=== NETWORK DIAGNOSTICS ==="
|
|
|
|
# Check DERP (relay) connectivity
|
|
echo "DERP Connectivity:"
|
|
tailscale netcheck 2>/dev/null | head -10 || echo "Network check not available"
|
|
echo ""
|
|
|
|
# Check for direct connections
|
|
echo "Direct Connections:"
|
|
tailscale status --json 2>/dev/null | grep -o '"CurAddr":"[^"]*"' | head -5 || echo "Connection info not available"
|
|
echo ""
|
|
|
|
# Interface information
|
|
echo "Network Interfaces:"
|
|
ip addr show tailscale0 2>/dev/null || echo "Tailscale interface not found"
|
|
echo ""
|
|
|
|
# Routing table
|
|
echo "Tailscale Routes:"
|
|
ip route show | grep tailscale0 2>/dev/null || echo "No Tailscale routes found"
|
|
register: tailscale_diagnostics
|
|
changed_when: false
|
|
when: not skip_tailscale
|
|
ignore_errors: yes
|
|
|
|
- name: Create Tailscale report
|
|
set_fact:
|
|
tailscale_report:
|
|
timestamp: "{{ tailscale_timestamp }}"
|
|
hostname: "{{ inventory_hostname }}"
|
|
tailscale_available: "{{ not skip_tailscale }}"
|
|
status: "{{ tailscale_status.stdout if not skip_tailscale else 'Not available' }}"
|
|
network: "{{ tailscale_network.stdout if not skip_tailscale else 'Not available' }}"
|
|
health: "{{ tailscale_health.stdout if not skip_tailscale else 'Not available' }}"
|
|
configuration: "{{ tailscale_config.stdout if not skip_tailscale else 'Not available' }}"
|
|
diagnostics: "{{ tailscale_diagnostics.stdout if not skip_tailscale else 'Not available' }}"
|
|
|
|
- name: Display Tailscale report
|
|
debug:
|
|
msg: |
|
|
|
|
==========================================
|
|
🌐 TAILSCALE REPORT - {{ inventory_hostname }}
|
|
==========================================
|
|
|
|
📊 AVAILABILITY: {{ 'Available' if tailscale_report.tailscale_available else 'Not Available' }}
|
|
|
|
📡 STATUS:
|
|
{{ tailscale_report.status }}
|
|
|
|
🔗 NETWORK INFO:
|
|
{{ tailscale_report.network }}
|
|
|
|
🏥 HEALTH CHECK:
|
|
{{ tailscale_report.health }}
|
|
|
|
⚙️ CONFIGURATION:
|
|
{{ tailscale_report.configuration }}
|
|
|
|
🔍 DIAGNOSTICS:
|
|
{{ tailscale_report.diagnostics }}
|
|
|
|
==========================================
|
|
|
|
- name: Generate JSON Tailscale report
|
|
copy:
|
|
content: |
|
|
{
|
|
"timestamp": "{{ tailscale_report.timestamp }}",
|
|
"hostname": "{{ tailscale_report.hostname }}",
|
|
"tailscale_available": {{ tailscale_report.tailscale_available | lower }},
|
|
"status": {{ tailscale_report.status | to_json }},
|
|
"network": {{ tailscale_report.network | to_json }},
|
|
"health": {{ tailscale_report.health | to_json }},
|
|
"configuration": {{ tailscale_report.configuration | to_json }},
|
|
"diagnostics": {{ tailscale_report.diagnostics | to_json }},
|
|
"recommendations": [
|
|
{% if not tailscale_report.tailscale_available %}
|
|
"Install Tailscale for network connectivity",
|
|
{% endif %}
|
|
{% if 'Not logged in' in tailscale_report.health %}
|
|
"Authenticate Tailscale client",
|
|
{% endif %}
|
|
{% if 'unreachable' in tailscale_report.network %}
|
|
"Investigate network connectivity issues",
|
|
{% endif %}
|
|
"Regular Tailscale health monitoring recommended"
|
|
]
|
|
}
|
|
dest: "{{ tailscale_report_dir }}/{{ inventory_hostname }}_tailscale_{{ ansible_date_time.epoch }}.json"
|
|
delegate_to: localhost
|
|
|
|
- name: Tailscale management operations (when action is specified)
|
|
block:
|
|
- name: Validate action parameter
|
|
fail:
|
|
msg: "Invalid action. Supported actions: status, login, logout, up, down, ping"
|
|
when: tailscale_action not in ['status', 'login', 'logout', 'up', 'down', 'ping']
|
|
|
|
- name: Execute Tailscale action
|
|
shell: |
|
|
case "{{ tailscale_action }}" in
|
|
"status")
|
|
tailscale status --peers
|
|
;;
|
|
"login")
|
|
echo "Login requires interactive authentication"
|
|
tailscale login --timeout=30s
|
|
;;
|
|
"logout")
|
|
tailscale logout
|
|
;;
|
|
"up")
|
|
tailscale up {{ tailscale_args | default('') }}
|
|
;;
|
|
"down")
|
|
tailscale down
|
|
;;
|
|
"ping")
|
|
if [ -n "{{ tailscale_target | default('') }}" ]; then
|
|
tailscale ping "{{ tailscale_target }}"
|
|
else
|
|
echo "Error: tailscale_target required for ping action"
|
|
exit 1
|
|
fi
|
|
;;
|
|
esac
|
|
register: tailscale_action_result
|
|
when: not skip_tailscale
|
|
|
|
- name: Display action result
|
|
debug:
|
|
msg: |
|
|
|
|
🔧 Tailscale action '{{ tailscale_action }}' completed on {{ inventory_hostname }}
|
|
|
|
Result:
|
|
{{ tailscale_action_result.stdout }}
|
|
|
|
{% if tailscale_action_result.stderr %}
|
|
Errors:
|
|
{{ tailscale_action_result.stderr }}
|
|
{% endif %}
|
|
|
|
when: tailscale_action is defined and not skip_tailscale
|
|
|
|
- name: Generate network topology map (run once)
|
|
shell: |
|
|
cd "{{ tailscale_report_dir }}"
|
|
|
|
echo "# Tailscale Network Topology" > network_topology.md
|
|
echo "" >> network_topology.md
|
|
echo "**Generated:** {{ tailscale_timestamp }}" >> network_topology.md
|
|
echo "" >> network_topology.md
|
|
|
|
# Process all Tailscale JSON reports
|
|
for json_file in *_tailscale_*.json; do
|
|
if [ -f "$json_file" ]; then
|
|
hostname=$(basename "$json_file" | cut -d'_' -f1)
|
|
echo "## 🖥️ $hostname" >> network_topology.md
|
|
echo "" >> network_topology.md
|
|
|
|
# Extract key information
|
|
if command -v jq >/dev/null 2>&1; then
|
|
available=$(jq -r '.tailscale_available' "$json_file" 2>/dev/null || echo "unknown")
|
|
echo "- **Tailscale:** $available" >> network_topology.md
|
|
|
|
# Try to extract IP if available
|
|
if [ "$available" = "true" ]; then
|
|
echo "- **Status:** Connected" >> network_topology.md
|
|
else
|
|
echo "- **Status:** Not available" >> network_topology.md
|
|
fi
|
|
fi
|
|
|
|
echo "- **Report:** [$json_file](./$json_file)" >> network_topology.md
|
|
echo "" >> network_topology.md
|
|
fi
|
|
done
|
|
|
|
echo "---" >> network_topology.md
|
|
echo "*Auto-generated by Ansible tailscale_management.yml playbook*" >> network_topology.md
|
|
delegate_to: localhost
|
|
run_once: true
|
|
|
|
- name: Summary message
|
|
debug:
|
|
msg: |
|
|
|
|
🌐 Tailscale management complete for {{ inventory_hostname }}
|
|
📄 Report saved to: {{ tailscale_report_dir }}/{{ inventory_hostname }}_tailscale_{{ ansible_date_time.epoch }}.json
|
|
🗺️ Network topology: {{ tailscale_report_dir }}/network_topology.md
|
|
|
|
{% if tailscale_action is defined %}
|
|
🔧 Action performed: {{ tailscale_action }}
|
|
{% endif %}
|
|
|
|
💡 Use -e tailscale_action=<action> for management operations
|
|
💡 Supported actions: status, login, logout, up, down, ping
|
|
💡 Use -e tailscale_target=<ip> with ping action
|