diff --git a/roles/headscale/README.md b/roles/headscale/README.md new file mode 100644 index 0000000..6c12ed0 --- /dev/null +++ b/roles/headscale/README.md @@ -0,0 +1 @@ +Sets up a headscale container diff --git a/roles/headscale/defaults/main.yaml b/roles/headscale/defaults/main.yaml new file mode 100644 index 0000000..588c3b1 --- /dev/null +++ b/roles/headscale/defaults/main.yaml @@ -0,0 +1,4 @@ +--- +headscale_oidc_issuer_url: "" +headscale_oidc_client_id: "" +headscale_oidc_client_secret: "" diff --git a/roles/headscale/meta/argument_specs.yaml b/roles/headscale/meta/argument_specs.yaml new file mode 100644 index 0000000..16ee7ea --- /dev/null +++ b/roles/headscale/meta/argument_specs.yaml @@ -0,0 +1,85 @@ +--- +argument_specs: + main: + description: "Sets up a headscale container" + options: + headscale_domain: + description: Domain headscale should be available at + type: str + required: true + headscale_magicdns_base_domain: + description: Base domain for magic dns hostnames + type: str + required: true + headscale_policy: + description: + - Headscale policy configuration. + - See https://headscale.net/stable/ref/acls/ + - This role's support for acls is currently quite limited. Waiting for headscale to release grants support. + type: dict + required: true + options: + groups: + description: >- + Groups of users. Formatted as dict where dict key is group name and + value is a list of users in the group. + type: dict + required: false + hosts: + description: Mapping of host names to ip addresses + type: dict + required: false + tagOwners: + description: Mapping of tags to lists of their owners + type: dict + required: false + acls: + description: List of acls + type: list + required: true + elements: dict + options: + action: + description: ACL action + type: str + required: true + choices: + - accept + - check + src: + description: >- + A list of sources, formatted as prefix:name:ports, + where prefix is the type of object, like tag: or group:, + name is the object name and ports is a comma separated list of ports or * + type: list + required: true + elements: str + dst: + description: A list of destinations. Same format as sources. + type: list + required: true + elements: str + proto: + description: Protocol + type: str + required: false + choices: + - tcp + - udp + - icmp + + headscale_oidc_issuer_url: + description: OIDC issuer url. Leave unset to not use OIDC. + type: str + required: false + default: "" + headscale_oidc_client_id: + description: OIDC client id. Required if OIDC issuer is set. + type: str + required: false + default: "" + headscale_oidc_client_secret: + description: OIDC client secret. Required if OIDC issuer is set. + type: str + required: false + default: "" diff --git a/roles/headscale/tasks/main.yaml b/roles/headscale/tasks/main.yaml new file mode 100644 index 0000000..b4a46b5 --- /dev/null +++ b/roles/headscale/tasks/main.yaml @@ -0,0 +1,36 @@ +--- +- name: Headscale service + ansible.builtin.import_role: + name: service + vars: + service_name: headscale + service_container_image: docker.io/headscale/headscale:stable + service_container_command: serve + service_container_mounts: + - type: volume + source: data + destination: /var/lib/headscale + - type: template + source: config.yaml.j2 + destination: /etc/headscale/config.yaml + - type: template + source: policy.hujson.j2 + destination: /etc/headscale/policy.hujson + template_validate_command: headscale policy check --file /etc/headscale/policy.hujson + service_container_http_port: 8080 + service_domains: + - "{{ headscale_domain }}" + service_container_publish_ports: + - name: stun + container_port: 3478 + protocol: udp + host_port: 3478 + service_container_secrets: "{{ _headscale_secrets }}" + service_container_reload_method: kill + +- name: Open port for stun + ansible.posix.firewalld: + service: stun + state: enabled + permanent: true + immediate: true diff --git a/roles/headscale/templates/config.yaml.j2 b/roles/headscale/templates/config.yaml.j2 new file mode 100644 index 0000000..1fc70de --- /dev/null +++ b/roles/headscale/templates/config.yaml.j2 @@ -0,0 +1,56 @@ +--- +# vim:ft=yaml +# {{ ansible_managed }} + +listen_addr: 0.0.0.0:8080 +server_url: https://{{ headscale_domain }} + +noise: + private_key_path: /var/lib/headscale/noise_private.key + +prefixes: + v4: 100.64.0.0/10 + v6: fd7a:115c:a1e0::/48 + + allocation: sequential + +derp: + server: + enabled: true + region_id: 999 + region_code: "headscale" + region_name: "Headscale embedded DERP" + verify_clients: true + stun_listen_addr: "0.0.0.0:3478" + private_key_path: /var/lib/headscale/derp_server_private.key + + urls: [] + +database: + type: sqlite + sqlite: + path: /var/lib/headscale/db.sqlite + +policy: + mode: file + path: /etc/headscale/policy.hujson + +dns: + magic_dns: true + base_domain: {{ headscale_magicdns_base_domain }} + nameservers: + global: + - https://dns.quad9.net/dns-query + +logtail: + enabled: false + +{% if headscale_oidc_issuer_url | length > 0 %} +oidc: + only_start_if_oidc_is_available: false + issuer: {{ headscale_oidc_issuer_url }} + client_id: "{{ headscale_oidc_client_id }}" + client_secret_path: /run/secrets/oidc-client-secret + pkce: + enabled: true +{% endif %} diff --git a/roles/headscale/templates/policy.hujson.j2 b/roles/headscale/templates/policy.hujson.j2 new file mode 100644 index 0000000..e82c5c4 --- /dev/null +++ b/roles/headscale/templates/policy.hujson.j2 @@ -0,0 +1,3 @@ +// {{ ansible_managed }} + +{{ headscale_policy | to_nice_json }} diff --git a/roles/headscale/vars/main.yaml b/roles/headscale/vars/main.yaml new file mode 100644 index 0000000..850992d --- /dev/null +++ b/roles/headscale/vars/main.yaml @@ -0,0 +1,6 @@ +--- +_headscale_oidc_secret: + name: oidc-client-secret + value: "{{ headscale_oidc_client_secret }}" + +_headscale_secrets: "{{ [_headscale_oidc_secret] if headscale_oidc_issuer_url | length > 0 else [] }}"