Skip to content

About

Ansible: Tailscale client role for the Fedora homelab (headscale-coordinated)

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

tailscale-ansible

License: MIT CI ansible-core

An Ansible role that installs and configures Tailscale on Fedora using restrictive, deny-by-default settings. It prevents automatic DNS overrides, avoids route hijacking, enables shields-up inbound blocking, and confines firewall changes to a single verified port. All routing and network adjustments require explicit per-host configuration.

This role works with Tailscale's hosted control plane or a self-hosted Headscale coordination server (such as headscale-ansible).

Features

  • Official packaging: Installs from Tailscale's signed Fedora repository, verifying both package and repository metadata GPG signatures.
  • DNS preservation: Sets --accept-dns=false. Verifies /etc/resolv.conf before and after execution to confirm DNS settings remain unchanged.
  • Routing isolation: Sets --accept-routes=false. Verifies the host default route before and after execution to prevent unexpected route changes.
  • Inbound connection protection: Enables --shields-up by default to block incoming connections from tailnet peers until explicitly permitted.
  • Controlled routing opt-ins: Exit node and subnet router advertising are disabled by default. Enabling either option configures required sysctl parameters, assigns tailscale0 to the firewalld trusted zone, enables masquerade, and applies a forwarding policy.
  • Headscale support: Set tailscale_login_server to authenticate against a self-hosted Headscale instance rather than login.tailscale.com.
  • Targeted firewall management: Opens a single UDP port in the firewalld zone assigned to the default-route interface, discovered at runtime. Never runs firewall-cmd --reload to avoid dropping runtime container network rules.
  • Encrypted credentials: Reads authentication keys from Ansible Vault and enforces no_log on all sensitive tasks.
  • Unprivileged status checks: Uses --operator=<user> to allow non-root users to run tailscale status and diagnostic commands.
  • Idempotent execution: Consecutive runs report zero changes when no configuration drifted.
  • Verification playbook: Includes a read-only verify.yml playbook to validate security settings at any time without changing system state.

Requirements

Control Node

  • ansible-core 2.15 or newer
  • Collections: ansible.posix, community.general (installed via requirements.yml)

Managed Node

  • Fedora (tested on Fedora 43)
  • systemd
  • firewalld as the active firewall manager
  • Python 3 available to Ansible
  • A user account with sudo permissions (the playbook escalates privilege per-task; no play-level become)

Quick Start

Run these steps on the control node:

1. Install dependencies

ansible-galaxy collection install -r requirements.yml

2. Create the vault password file

umask 077
printf '%s' 'your-strong-passphrase' > .vault_pass
chmod 600 .vault_pass

Store the passphrase in a password manager. If lost, encrypted vault contents cannot be recovered.

3. Create the inventory

cat > inventory.ini <<'EOF'
[tailscale_nodes]
node1 ansible_host=<ip-or-hostname> ansible_user=<user>
EOF

For local testing on the current machine, use ansible_connection=local.

inventory.ini, group_vars/, and host_vars/ are excluded from version control. Keep real hostnames, domains, and IP addresses in those files.

4. Override role defaults (optional)

Override defaults in group_vars/all/vars.yml if needed:

mkdir -p group_vars/all
cat > group_vars/all/vars.yml <<'EOF'
---
tailscale_listen_port: 41641
# tailscale_login_server: "https://headscale.example.com"
EOF

See Role variables for available settings.

5. Generate an auth key

  • Hosted Tailscale: Create a pre-authorized, single-use, non-ephemeral key in the admin console. Set a short expiration period.
  • Headscale: Generate a pre-auth key on the coordinator (headscale preauthkeys create --user <id> --expiration 24h).

6. Encrypt the auth key

ansible-vault create group_vars/all/vault.yml

Add your key to the file:

tailscale_authkey: tskey-auth-XXXXXXXXXXXXXXXXXXXXXXXXXX

7. Apply the playbook

ansible-playbook site.yml --check --diff    # preview changes
ansible-playbook site.yml                   # apply configuration
ansible-playbook verify.yml                 # verify settings
ansible-playbook site.yml                   # verify changed=0

Role Variables

Required Variables

Supply through the encrypted vault (group_vars/all/vault.yml):

Variable Description
tailscale_authkey Pre-authorized auth key from the Tailscale admin console or Headscale coordinator

Default Variables

Configured in roles/tailscale/defaults/main.yml. Override in group_vars/all/vars.yml or host_vars/<hostname>.yml:

Variable Default Description
tailscale_listen_port 41641 UDP listen port. Values below 1024 are rejected. Ports in 49152 to 65535 are recommended.
tailscale_login_server "" Coordination server URL. Empty uses hosted Tailscale; specify an https:// URL for Headscale.
tailscale_operator_user {{ ansible_user_id }} Local account permitted to run tailscale commands without sudo.
tailscale_hostname {{ inventory_hostname_short }} Machine name displayed in the tailnet admin console.
tailscale_firewalld_zone "" Firewalld zone. Empty string auto-discovers the zone assigned to the default-route interface.
tailscale_accept_routes false Accept subnet routes advertised by peer nodes.
tailscale_accept_dns false Allow Tailscale to modify host DNS settings.
tailscale_advertise_exit_node false Advertise this node as an exit node. Enables forwarding and required firewall rules.
tailscale_advertise_routes [] List of subnet CIDRs to advertise. Non-empty lists enable routing settings.
tailscale_ssh false Enable Tailscale SSH. Kept false so OpenSSH manages host access.
tailscale_shields_up true Block all inbound connections from tailnet peers until explicitly allowed.
tailscale_reset_on_first_run false Pass --reset to the initial tailscale up command. Clears previous daemon configuration.
tailscale_repo_name tailscale-stable DNF package repository name.
tailscale_repo_baseurl https://pkgs.tailscale.com/stable/fedora/$basearch Package repository URL.
tailscale_repo_gpgkey https://pkgs.tailscale.com/stable/fedora/repo.gpg GPG key URL for package and repository validation.

Usage

Basic playbook:

- hosts: tailscale_nodes
  gather_facts: true
  become: false
  roles:
    - role: tailscale

Playbook with custom options:

- hosts: tailscale_nodes
  roles:
    - role: tailscale
      vars:
        tailscale_listen_port: 52187
        tailscale_hostname: edge-1
        tailscale_advertise_routes:
          - 10.20.0.0/24

Verification

Run the read-only verification playbook at any time:

ansible-playbook verify.yml

The playbook asserts:

  1. tailscale status --json reports BackendState == Running and Self.Online.
  2. The default network route matches the preflight check.
  3. The checksum of /etc/resolv.conf matches the preflight check.
  4. The configured UDP port is open in the active firewalld zone.
  5. tailscaled is listening on the configured UDP port.

Rollback

To disconnect from the tailnet without removing software:

sudo tailscale down

Confirm that system network settings remain intact:

ip route show default
cat /etc/resolv.conf

To remove Tailscale completely:

IFACE="$(ip -json route show default | python3 -c 'import json,sys;print(json.load(sys.stdin)[0]["dev"])')"
ZONE="$(firewall-cmd --get-zone-of-interface="$IFACE")"
PORT=<configured tailscale_listen_port>

sudo systemctl disable --now tailscaled
sudo dnf remove -y tailscale
sudo firewall-cmd --zone="$ZONE" --remove-port="$PORT/udp" --permanent
sudo firewall-cmd --zone="$ZONE" --remove-port="$PORT/udp"
sudo rm -f /etc/yum.repos.d/tailscale-stable.repo
sudo rm -rf /etc/systemd/system/tailscaled.service.d

On nodes acting as exit nodes or subnet routers, remove routing configuration:

sudo firewall-cmd --zone=trusted --remove-interface=tailscale0 --permanent
sudo firewall-cmd --zone=trusted --remove-interface=tailscale0
sudo firewall-cmd --zone="$ZONE" --remove-masquerade --permanent
sudo firewall-cmd --zone="$ZONE" --remove-masquerade
sudo firewall-cmd --permanent --delete-policy=tailscale-egress
sudo rm -f /etc/sysctl.d/99-tailscale.conf

Auth Key Rotation

  1. Revoke the existing key in the admin console or with headscale preauthkeys expire.
  2. Generate a new pre-authorized key with matching tags.
  3. Run ansible-vault edit group_vars/all/vault.yml and update tailscale_authkey.
  4. Run sudo tailscale logout on the managed node to reset authentication.
  5. Run ansible-playbook site.yml. The role identifies the logged-out state and authenticates with the new key.

Troubleshooting

Issue Resolution
tailscale_authkey is empty during preflight Verify that .vault_pass exists and decrypts group_vars/all/vault.yml.
Resolved firewalld zone is empty Assign a firewalld zone to your default network interface, or set tailscale_firewalld_zone explicitly.
Default route changed after apply Run sudo tailscale down immediately and check routing tables with ip route show table all.
/etc/resolv.conf changed Check systemctl status systemd-resolved and inspect DNS managers for external modifications.
Re-running site.yml reports changed > 0 Inspect task output to determine which task triggered changes.
tailscaled is not listening on the configured port Run sudo systemctl daemon-reload && sudo systemctl restart tailscaled to reload systemd drop-ins.
Peers cannot establish new connections through exit node Verify that the tailscale-egress policy is present and active in firewalld.

Development

Run ansible-lint and playbook syntax checks:

ansible-lint
ansible-playbook site.yml --syntax-check

Contributing

Contributions and issue reports are welcome. See CONTRIBUTING.md and CODE_OF_CONDUCT.md for guidelines. Report security issues according to SECURITY.md.

License

This project is licensed under the MIT License.

About

Ansible: Tailscale client role for the Fedora homelab (headscale-coordinated)

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors