--- - 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= for management operations 💡 Supported actions: status, login, logout, up, down, ping 💡 Use -e tailscale_target= with ping action