Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] add OAuth key integration and additional URL-style params to --auth-key flags (ephemeral & preauthorized) #399

Merged
merged 19 commits into from
Dec 21, 2023
Merged
3 changes: 2 additions & 1 deletion .ansible-lint
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ exclude_paths:
- .vscode/
- molecule/default/headscale.config.yaml
- molecule/default/init_tailscale_vars.yml

- molecule/oauth/headscale.config.yaml
- molecule/oauth/init_tailscale_vars.yml

skip_list:
- yaml[line-length]
Expand Down
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
This role installs and configures [Tailscale][] on a Linux target.

Supported operating systems:

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fine without the extra empty line, right?

- Debian / Ubuntu
- CentOS / RedHat
- Rocky Linux / AlmaLinux
Expand Down Expand Up @@ -76,11 +77,35 @@ Is **not** required if `tailscale_up_skip` is set to `true`.

A Tailscale Node Authorization auth key.

A Node Authorization auth key can be generated under your Tailscale account at <https://login.tailscale.com/admin/authkeys>.
Note that reusable authorization keys now expire 90 days after they are generated.
A Node Authorization key can be generated under your Tailscale account. The role supports two type of keys:

- Auth key (`tskey-auth-XXX-YYYYY`) <https://login.tailscale.com/admin/authkeys>
- OAuth key (`tskey-client-XXX-YYYY`) <https://login.tailscale.com/admin/settings/oauth>

Note that auth keys expire up to a maximum of 90 days after they are generated. OAuth secrets do not expire unless revoked, and the generated OAuth access token expires after 1 hour.

For more information, see Tailscale's [OAuth clients](https://tailscale.com/kb/1215/oauth-clients) page, especially [Generating long-lived auth keys](https://tailscale.com/kb/1215/oauth-clients#generating-long-lived-auth-keys).

This value should be treated as a sensitive secret.

### tailscale_oauth_ephemeral

> [!NOTE]
> Used only when `tailscale_authkey` is an OAuth key.

**Default**: `true`

Register as an [ephemeral node](https://tailscale.com/kb/1111/ephemeral-nodes), if `true`.

### tailscale_oauth_preauthorized

> [!NOTE]
> Used only when `tailscale_authkey` is an OAuth key.

**Default**: `false`

Skip [manual device approval](https://tailscale.com/kb/1099/device-approval), if `true`.

### tailscale_up_skip

**If set to true, `tailscale_authkey` is not required.**
Expand Down Expand Up @@ -271,6 +296,5 @@ USE_HEADSCALE=true molecule test
[ephemeral auth keys]: https://tailscale.com/kb/1111/ephemeral-nodes/
[github action secret]: https://docs.github.com/en/actions/reference/encrypted-secrets
[tailscale]: https://tailscale.com/
[tailscale account]: https://login.tailscale.com/start
[tailscale up docs]: https://tailscale.com/kb/1080/cli/#up
[headscale]: https://github.com/juanfont/headscale/
6 changes: 6 additions & 0 deletions defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ state: latest
tailscale_authkey: ""
# Optional command-line arguments for 'tailscale up'
tailscale_args: ""
# Used for OAuth authentication
# Register as an ephemeral node (recommended)
tailscale_oauth_ephemeral: true
# Used for OAuth authentication
# Skip manual device approval
tailscale_oauth_preauthorized: false
# Whether to output debug information during role execution
verbose: false
# Whether to skip 'tailscale up'
Expand Down
8 changes: 8 additions & 0 deletions molecule/oauth/cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
- name: Cleanup
hosts: instance
tasks:
- name: De-register Tailscale node
become: true
ansible.builtin.command: tailscale logout
changed_when: false
12 changes: 12 additions & 0 deletions molecule/oauth/converge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
- name: Converge
hosts: instance
tasks:
- name: Init tailscale credentials variables
ansible.builtin.include_tasks: init_tailscale_vars.yml

- name: "Include artis3n.tailscale"
ansible.builtin.include_role:
name: artis3n.tailscale
vars:
verbose: true
34 changes: 34 additions & 0 deletions molecule/oauth/headscale.config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# minimal Headscale configuration for local testing
# See upstream example file for full description of all options:
# https://github.com/juanfont/headscale/blob/main/config-example.yaml
server_url: http://headscale:8080
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 0.0.0.0:9090
private_key_path: /etc/headscale/private.key
noise:
private_key_path: /etc/headscale/noise_private.key
db_type: sqlite3
db_path: /etc/headscale/db.sqlite

# Default Tailscale prefixes
ip_prefixes:
- fd7a:115c:a1e0::/48
- 100.64.0.0/10

# Disable TLS
tls_cert_path: ""
tls_key_path: ""

# Add DNS configuration so we can --accept-dns
dns_config:
override_local_dns: true
nameservers:
- 1.1.1.1

derp:
server:
enabled: true
region_id: 999
region_code: "headscale"
region_name: "Headscale Embedded DERP"
stun_listen_addr: "0.0.0.0:3478"
22 changes: 22 additions & 0 deletions molecule/oauth/init_tailscale_vars.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
- name: Use tailscale service
ansible.builtin.set_fact:
tailscale_authkey: "{{ lookup('ansible.builtin.env', 'TAILSCALE_OAUTH_CLIENT_SECRET') }}"
when: not lookup('ansible.builtin.env', 'USE_HEADSCALE', default=false)

- name: Fetch headscale preauth key
delegate_to: localhost
changed_when: false
community.docker.docker_container_exec:
container: headscale
command: headscale preauthkeys list -u test -o json
register: preauth_list
when: lookup('ansible.builtin.env', 'USE_HEADSCALE', default=false)

- name: Use headscale service
vars:
combined_args: "{{ tailscale_args|default('') }} --login-server=http://headscale:8080"
ansible.builtin.set_fact:
tailscale_authkey: "{{ (preauth_list.stdout|from_json)[0].key }}"
tailscale_args: "{{ combined_args }}"
when: lookup('ansible.builtin.env', 'USE_HEADSCALE', default=false)
46 changes: 46 additions & 0 deletions molecule/oauth/molecule.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
dependency:
name: galaxy
options:
requirements-file: requirements.yml
driver:
name: docker
platforms:
- name: instance
image: ${MOLECULE_DISTRO:-geerlingguy/docker-ubuntu2204-ansible:latest}
command: ${MOLECULE_COMMAND:-/usr/sbin/init}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
docker_networks:
- name: headscale
networks:
- name: bridge
- name: headscale
cgroupns_mode: host
privileged: true
pre_build_image: true
- name: headscale
image: ${HEADSCALE_IMAGE:-headscale/headscale:latest}
command: headscale serve
pre_build_image: true
networks:
- name: headscale
volumes:
- "${MOLECULE_PROJECT_DIRECTORY}/molecule/default/headscale.config.yaml:/etc/headscale/config.yaml"
provisioner:
name: ansible
verifier:
name: ansible
scenario:
name: oauth
test_sequence:
- dependency
- destroy
- syntax
- create
- prepare
- converge
- idempotence
- verify
- cleanup
- destroy
26 changes: 26 additions & 0 deletions molecule/oauth/prepare.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
- name: Prepare
hosts: localhost
gather_facts: false
tasks:
- name: Create Headscale user
community.docker.docker_container_exec:
container: headscale
command: headscale users create test

- name: Create preauth key
community.docker.docker_container_exec:
container: headscale
command: headscale preauthkeys create -u test --reusable

- name: Fetch Headscale container info
community.docker.docker_container_info:
name: headscale
register: headscale_info

- name: Set hosts override for Headscale
delegate_to: instance
ansible.builtin.lineinfile:
path: /etc/hosts
line: "{{ headscale_info.container.NetworkSettings.Networks.headscale.IPAddress }} headscale"
unsafe_writes: true # Hosts file in the docker container can't be written to atomically
15 changes: 15 additions & 0 deletions molecule/oauth/verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
- name: Verify
hosts: instance
tasks:
- name: Get Tailscale status
become: true
ansible.builtin.command: tailscale status
changed_when: false
register: tailscale_status

- name: Assertions
ansible.builtin.assert:
that:
- "'Logged out.' not in tailscale_status.stdout"
- "'not logged in' not in tailscale_status.stdout"
15 changes: 14 additions & 1 deletion tasks/install.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,22 @@
mode: '0644'
register: state_file

# OAuth key starts with 'tskey-client-', auth key starts with 'tskey-auth-', with headscale it can be 'unused'
- name: Install | Create authkey string
ansible.builtin.set_fact:
tailscale_authkey_sting: >-
{# Check if the key is an OAuth key #}
{% if tailscale_authkey.startswith('tskey-client-') %}
{{ tailscale_authkey }}?ephemeral={{ tailscale_oauth_ephemeral | bool }}&preauthorized={{ tailscale_oauth_preauthorized | bool }}
{# Check if the key is not OAuth (regular authkey or unused) #}
{% else %}
{{ tailscale_authkey }}
{% endif %}
no_log: "{{ not (insecurely_log_authkey | bool) }}"

- name: Install | Bring Tailscale Up
become: true
ansible.builtin.command: "tailscale up {{ tailscale_args | trim }} --authkey={{ tailscale_authkey }}"
ansible.builtin.command: "tailscale up {{ tailscale_args | trim }} --authkey='{{ tailscale_authkey_sting | trim }}'"
# Since the auth key is included in this task's output, we do not want to log output
no_log: "{{ not (insecurely_log_authkey | bool) }}"
changed_when: true
Expand Down