From 1f7228eb535dae2b62b00c938fc673715961ce40 Mon Sep 17 00:00:00 2001 From: Ari Kalfus Date: Fri, 19 Apr 2024 14:08:21 -0400 Subject: [PATCH] feat: add node facts after role installation completes (#464) * feat: add node facts after role installation completes * fix: don't generate role facts if skip authentication is true * change task names --- README.md | 212 +++++++++++++++++++++++++++------------------- pyproject.toml | 2 +- tasks/facts.yml | 48 +++++++++++ tasks/install.yml | 5 ++ 4 files changed, 181 insertions(+), 86 deletions(-) create mode 100644 tasks/facts.yml diff --git a/README.md b/README.md index d3e86542..ddc28955 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# artis3n.tailscale +# artis3n.tailscale [![Ansible Role](https://img.shields.io/ansible/role/d/artis3n/tailscale)](https://galaxy.ansible.com/ui/standalone/roles/artis3n/tailscale/) [![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/artis3n/ansible-role-tailscale?include_prereleases)](https://github.com/artis3n/ansible-role-tailscale/releases) @@ -35,30 +35,29 @@ See the [CI worfklow](https://github.com/artis3n/ansible-role-tailscale/blob/mai > This role uses Ansible fully qualified collection names (FQCN) and therefore requires Ansible 2.11+. > Ansible 2.12 is set as the minimum required version as this was the version tested for compatibility during the FQCN refactor. -# State Tracking - -This role will create an `artis3n-tailscale` directory in the target's [`XDG_STATE_HOME`](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) directory, -or `$HOME/.local/state` if the variable is not present, -in order to maintain a concept of state from the configuration of the arguments passed to `tailscale up`. -This allows the role to idempotently update a Tailscale node's configuration when needed. -Deleting this directory will lead to this role re-configuring Tailscale when it is not needed, -but will not otherwise break anything. -However, it is recommended that you let this Ansible role manage this directory and its contents. - -Note that: - -> Flags are not persisted between runs; you must specify all flags each time. -> -> ... -> -> In Tailscale v1.8 or later, if you forget to specify a flag you added before, -> the CLI will warn you and provide a copyable command that includes all existing flags. - - +- [Role Outputs](#role-outputs) +- [Role Variables](#role-variables) + - [Required](#required) + - [tailscale\_authkey](#tailscale_authkey) + - [tailscale\_tags](#tailscale_tags) + - [tailscale\_up\_skip](#tailscale_up_skip) + - [Optional](#optional) + - [state](#state) + - [tailscale\_args](#tailscale_args) + - [tailscale\_oauth\_ephemeral](#tailscale_oauth_ephemeral) + - [tailscale\_oauth\_preauthorized](#tailscale_oauth_preauthorized) + - [insecurely\_log\_authkey](#insecurely_log_authkey) + - [release\_stability](#release_stability) + - [tailscale\_up\_timeout](#tailscale_up_timeout) + - [verbose](#verbose) +- [Dependencies](#dependencies) + - [Collections](#collections) +- [Example Playbook](#example-playbook) +- [State Tracking](#state-tracking) +- [License](#license) +- [Author Information](#author-information) +- [Development and Contributing](#development-and-contributing) -\- [docs: tailscale up][tailscale up docs] - - This role will bubble up any stderr messages from the Tailscale binary to resolve any end-user configuration errors with `tailscale up` arguments. @@ -66,6 +65,24 @@ The `--authkey=` value will be redacted unless [`insecurely_log_authkey`](#insec ![logged stderr](docs/images/printed_stderr.png) +# Role Outputs + +This role provides the IP v4 and v6 addresses of the Tailscale node as well as the output of `tailscale whois` against the node as facts. +Several key pieces of `whois` information are provided directly, with the rest of the whois output stored as a JSON fact for your convenience. + +Outputted facts: + +``` +tailscale_node_ipv4 (string): The IPv4 address of the Tailscale node. +tailscale_node_ipv6 (string): The IPv6 address of the Tailscale node. +tailscale_node_hostname_full (string): The full hostname (node.domain.ts.net) of the Tailscale node. +tailscale_node_hostname_short (string): The short hostname (node) of the Tailscale node. +tailscale_node_created_at (string): The ISO-8601 timestamp the Tailscale node was created. +tailscale_node_tags (list): The tags assigned to the Tailscale node. +tailscale_node_services (list): The discovered services running on the Tailscale node. +tailscale_node_whois (dict): The full output of `tailscale whois` against the Tailscale node. +``` + # Role Variables ## Required @@ -76,7 +93,7 @@ In most cases you will use `tailscale_authkey`. If you are uninstalling Tailscale (`state: absent`), neither `tailscale_authkey` nor `tailscale_up_skip` is required. -If you are authenticating with an OAuth key, you must also set `tailscale_tags` (see below). +If you are authenticating with an OAuth key, you must also set `tailscale_tags`. ### tailscale_authkey @@ -90,7 +107,7 @@ A Node Authorization key can be generated under your Tailscale account. The role - OAuth key (`tskey-client-XXX-YYYY`) > [!IMPORTANT] -> Using an OAuth key requires additionally setting the following variables: +> Using an OAuth key requires the following role variables: > `tailscale_tags` (must be provided), > `tailscale_oauth_ephemeral` (defaults to `true`), > and `tailscale_oauth_preauthorized` (defaults to `false`). @@ -107,24 +124,6 @@ If an OAuth key is used, be sure to grant the `write` Devices scope to the OAuth 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_tags **Default**: `[]` @@ -139,8 +138,6 @@ For more information, see [What are tags?](https://tailscale.com/kb/1068/acl-tag Entries should not include `tag:`. For example, `tailscale_tags: ['worker']` translates to `--advertise-tags=tag:worker`. -> Auth keys generated by this OAuth client must assign tags (or tags managed by these tags) to devices they authorize. - ### tailscale_up_skip **If set to true, `tailscale_authkey` is not required.** @@ -153,39 +150,6 @@ when the server should not yet authenticate to your Tailscale network. ## Optional -### insecurely_log_authkey - -**Default**: `false` - -If set to `true`, the "Bring Tailscale Up" command will include the raw value of the Tailscale authkey -when logging any errors encountered during `tailscale up`. -By default, the authkey is not logged in successful task completions -and is redacted in the `stderr` output by this role if an error occurs. - -![redacted authkey](docs/images/redacted_authkey.png) - -If you are encountering an error bringing Tailscale up -and want the "Bring Tailscale Up" task to _not_ redact the value of the authkey, -set this variable to `true`. - -Regardless, if the authkey is invalid, the role will relay Tailscale's error message on that fact: - -![invalid authkey](docs/images/invalid_authkey.png) - -### release_stability - -**Default**: `stable` - -Whether to use the Tailscale stable or unstable track. - -`stable`: - -> Stable releases. If you're not sure which track to use, pick this one. - -`unstable`: - -> The bleeding edge. Pushed early and often. Expect rough edges! - ### state **Default**: `latest` @@ -197,6 +161,7 @@ This role uses `latest` by default to help ensure your software remains up-to-da and incorporates the latest security and product features. For users who desire more control over configuration drift, `present` will not update Tailscale if it is already installed. + Changes to [`tailscale_args`](#tailscale_args) will be applied under both `latest` and `present`; this parameter only impacts the version of Tailscale installed to the target system. @@ -207,7 +172,7 @@ Note that neither `tailscale_authkey` nor `tailscale_up_skip` is required if `st ### tailscale_args -Pass any additional command-line arguments to `tailscale up`. +Pass command-line arguments to `tailscale up`. Note that the [command][ansible.builtin.command] module is used, which does not support subshell expressions (`$()`) or bash operations like `;` and `&`. @@ -233,6 +198,57 @@ Stderrs will continue to fail the role's execution. The sensitive `--authkey` value will be redacted by default. If you need to view the unredacted value, see [`insecurely_log_authkey`](#insecurely_log_authkey). +### 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`. + +### insecurely_log_authkey + +**Default**: `false` + +If set to `true`, the "Bring Tailscale Up" command will include the raw value of the Tailscale authkey +when logging any errors encountered during `tailscale up`. +By default, the authkey is not logged in successful task completions +and is redacted in the `stderr` output by this role if an error occurs. + +![redacted authkey](docs/images/redacted_authkey.png) + +If you are encountering an error bringing Tailscale up +and want the "Bring Tailscale Up" task to _not_ redact the value of the authkey, +set this variable to `true`. + +Regardless, if the authkey is invalid, the role will relay Tailscale's error message on that fact: + +![invalid authkey](docs/images/invalid_authkey.png) + +### release_stability + +**Default**: `stable` + +Whether to use the Tailscale stable or unstable track. + +`stable`: + +> Stable releases. If you're not sure which track to use, pick this one. + +`unstable`: + +> The bleeding edge. Pushed early and often. Expect rough edges! + ### tailscale_up_timeout **Default**: `120` @@ -346,6 +362,31 @@ De-register and uninstall a Tailscale node: state: absent ``` +# State Tracking + +This role will create an `artis3n-tailscale` directory in the target's [`XDG_STATE_HOME`](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) directory, +or `$HOME/.local/state` if the variable is not present, +in order to maintain a concept of state from the configuration of the arguments passed to `tailscale up`. +This allows the role to idempotently update a Tailscale node's configuration when needed. +Deleting this directory will lead to this role re-configuring Tailscale when it is not needed, +but will not otherwise break anything. +However, it is recommended that you let this Ansible role manage this directory and its contents. + +Note that: + +> Flags are not persisted between runs; you must specify all flags each time. +> +> ... +> +> In Tailscale v1.8 or later, if you forget to specify a flag you added before, +> the CLI will warn you and provide a copyable command that includes all existing flags. + + + +\- [docs: tailscale up][tailscale up docs] + + + # License MIT @@ -359,18 +400,18 @@ Ari Kalfus ([@artis3n](https://www.artis3nal.com/)) This GitHub repository uses a dedicated "test" Tailscale account to authenticate Tailscale during CI runs. Each Docker container creates a new authorized machine in that test account. The machines are authorized with [ephemeral auth keys][] -and are automatically cleaned up within 30 minutes-48 hours. +and are automatically cleaned up. -This value is stored in a [GitHub Action secret][] with the name `TAILSCALE_CI_KEY`. +This authkey is stored in a [GitHub Action secret][] with the name `TAILSCALE_CI_KEY`. To test OAuth authkey compatibility, a Tailscale OAuth client secret is stored as `TAILSCALE_OAUTH_CLIENT_SECRET`. If you are a Collaborator on this repository, -you can open a GitHub CodeSpace and these secrets will be pre-populated for you into the environment. +you can open a GitHub CodeSpace and these secrets will be pre-populated for you in the environment. To test this role locally, store the Tailscale ephemeral auth key in a `TAILSCALE_CI_KEY` env var and, if running the `oauth` Molecule scenario, -add an OAuth client secret in a `TAILSCALE_CI_KEY` env var. +add an OAuth client secret in a `TAILSCALE_OAUTH_CLIENT_SECRET` env var. -Alternatively for Molecule testing, +Alternatively for [Molecule][] testing, you can use a [Headscale][] container that is spun up as part of the create/prepare steps. To do this, set a `USE_HEADSCALE` env variable. For example: @@ -382,6 +423,7 @@ USE_HEADSCALE=true molecule test [ansible.builtin.command]: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/command_module.html [ephemeral auth keys]: https://tailscale.com/kb/1111/ephemeral-nodes/ [github action secret]: https://docs.github.com/en/actions/reference/encrypted-secrets +[molecule]: https://ansible.readthedocs.io/projects/molecule/ [tailscale]: https://tailscale.com/ [tailscale up docs]: https://tailscale.com/kb/1080/cli/#up [headscale]: https://github.com/juanfont/headscale/ diff --git a/pyproject.toml b/pyproject.toml index 2ac39a77..dd835fe6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "artis3n.tailscale" -version = "4.4.4" +version = "4.5.0" description = "Ansible role to install and enable a Tailscale node." authors = ["Artis3n "] license = "MIT" diff --git a/tasks/facts.yml b/tasks/facts.yml new file mode 100644 index 00000000..8df18bc4 --- /dev/null +++ b/tasks/facts.yml @@ -0,0 +1,48 @@ +--- +- name: Facts | Get IPv4 address + ansible.builtin.command: + cmd: tailscale ip --4 + register: tailscale_ipv4_cmd + changed_when: false + +- name: Facts | Get IPv6 address + ansible.builtin.command: + cmd: tailscale ip --6 + register: tailscale_ipv6_cmd + changed_when: false + +- name: Facts | Register IP facts + ansible.builtin.set_fact: + tailscale_node_ipv4: "{{ tailscale_ipv4_cmd.stdout }}" + tailscale_node_ipv6: "{{ tailscale_ipv6_cmd.stdout }}" + +- name: Facts | Get Tailscale host facts + ansible.builtin.command: + cmd: tailscale whois --json {{ tailscale_node_ipv4 }} + register: tailscale_whois_cmd + changed_when: false + +- name: Facts | Parse Tailscale host information + ansible.builtin.set_fact: + tailscale_whois: "{{ tailscale_whois_cmd.stdout | from_json }}" + +- name: Facts | Set Tailscale host facts + ansible.builtin.set_fact: + tailscale_node_hostname_full: "{{ tailscale_whois.Node.Name }}" + tailscale_node_hostname_short: "{{ tailscale_whois.Node.Hostinfo.Hostname }}" + tailscale_node_created_at: "{{ tailscale_whois.Node.Created }}" + tailscale_node_services: "{{ tailscale_whois.Node.Hostinfo.Services | default([]) }}" + tailscale_node_tags: "{{ tailscale_whois.Node.Tags | default([]) }}" + tailscale_node_whois: "{{ tailscale_whois.Node }}" + +- name: Facts | Display key facts + ansible.builtin.debug: + var: "{{ item }}" + when: verbose + loop: + - tailscale_node_hostname_full + - tailscale_node_hostname_short + - tailscale_node_created_at + - tailscale_node_ipv4 + - tailscale_node_ipv6 + - tailscale_node_tags diff --git a/tasks/install.yml b/tasks/install.yml index a9db9235..0804b1f4 100644 --- a/tasks/install.yml +++ b/tasks/install.yml @@ -170,3 +170,8 @@ msg: "{{ tailscale_start.stderr | default () | regex_replace(tailscale_authkey, 'REDACTED') | regex_replace('\\t', '') | split('\n') }}" when: - tailscale_start is failed + +- name: Install | Register role facts + ansible.builtin.include_tasks: facts.yml + when: + - not tailscale_up_skip