From 792dca976dc7a1347d4aa7649e3f43d51bbb26d7 Mon Sep 17 00:00:00 2001 From: michael Date: Mon, 27 Apr 2026 08:34:26 -0700 Subject: [PATCH 1/3] ansible: scaffold dangerousthings.authforge role skeleton Adds meta/main.yml (Galaxy metadata for Ubuntu 22.04/24.04 under the Apache-2.0 license), defaults/main.yml covering the policy/storage/ firstrun/pending knobs the tasks layer will consume, and a single restart-daemon handler. Tasks and template land in the next commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- ansible-role/defaults/main.yml | 31 +++++++++++++++++++++++++++++++ ansible-role/handlers/main.yml | 5 +++++ ansible-role/meta/main.yml | 30 ++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 ansible-role/defaults/main.yml create mode 100644 ansible-role/handlers/main.yml create mode 100644 ansible-role/meta/main.yml diff --git a/ansible-role/defaults/main.yml b/ansible-role/defaults/main.yml new file mode 100644 index 0000000..a2a7004 --- /dev/null +++ b/ansible-role/defaults/main.yml @@ -0,0 +1,31 @@ +--- +# Whether to additionally pull the GUI package. Headless fleets should leave +# this off; desktop fleets that want the AuthForge settings panel can flip it. +authforge_install_gui: false + +# PPA to install from. Override if you mirror packages internally. +authforge_ppa: "ppa:dangerousthings/authforge" + +# Map of stack name -> { mode, methods }. mode is one of +# "disabled" | "optional" | "required". methods is a list e.g. ["fido2", "totp"]. +# Default is empty so the role can be applied for "install only" runs. +authforge_stacks: {} + +# Credential storage backend. "per-user" stores credentials under each user's +# home directory; "central" stores them at authforge_storage_central_path so +# they survive home-directory wipes (e.g. AD-managed boxes). +authforge_storage_backend: "per-user" +authforge_storage_central_path: "" + +# First-login enforcement. Users who exist in the pending list will be required +# to enroll one of these methods on their next interactive login. +authforge_firstrun_methods: + - "fido2" + +# How long after the pending flag is set before the user is locked out if they +# have not enrolled. 0 means no deadline (warn forever). +authforge_firstrun_deadline_hours: 0 + +# Users to mark as pending-enrollment via `authforgectl pending set`. +# Each entry: { user: "alice", methods: ["fido2"] }. +authforge_pending_users: [] diff --git a/ansible-role/handlers/main.yml b/ansible-role/handlers/main.yml new file mode 100644 index 0000000..d121145 --- /dev/null +++ b/ansible-role/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart authforge-daemon + ansible.builtin.systemd: + name: authforge-daemon + state: restarted diff --git a/ansible-role/meta/main.yml b/ansible-role/meta/main.yml new file mode 100644 index 0000000..1cdbd6a --- /dev/null +++ b/ansible-role/meta/main.yml @@ -0,0 +1,30 @@ +--- +galaxy_info: + role_name: authforge + namespace: dangerousthings + author: Dangerous Things + description: > + Install and configure AuthForge (FIDO2 / U2F / TOTP MFA for Ubuntu) on + fleet hosts. Adds the project PPA, installs the headless component + packages, drops a fleet policy file, and seeds first-login pending + enrollment flags. + license: Apache-2.0 + min_ansible_version: "2.14" + + platforms: + - name: Ubuntu + versions: + - jammy + - noble + + galaxy_tags: + - authforge + - mfa + - fido2 + - u2f + - totp + - pam + - security + - ubuntu + +dependencies: [] From 457db3ced207652b60b900ba500d2077176fc3cc Mon Sep 17 00:00:00 2001 From: michael Date: Mon, 27 Apr 2026 08:35:28 -0700 Subject: [PATCH 2/3] ansible: implement install + policy + pending tasks Adds the apt_repository / apt steps that pull authforge-daemon, -pam, and -cli (with -gui gated behind authforge_install_gui), the template step that renders /etc/authforge/policy.d/90-fleet.conf and notifies the restart handler, and a loop that calls `authforgectl pending set` for each entry in authforge_pending_users. The template emits TOML that round-trips through tomllib for both the empty-stacks default and a populated multi-stack config. Co-Authored-By: Claude Opus 4.7 (1M context) --- ansible-role/tasks/main.yml | 52 +++++++++++++++++++++++++++ ansible-role/templates/policy.conf.j2 | 18 ++++++++++ 2 files changed, 70 insertions(+) create mode 100644 ansible-role/tasks/main.yml create mode 100644 ansible-role/templates/policy.conf.j2 diff --git a/ansible-role/tasks/main.yml b/ansible-role/tasks/main.yml new file mode 100644 index 0000000..55ed839 --- /dev/null +++ b/ansible-role/tasks/main.yml @@ -0,0 +1,52 @@ +--- +- name: Add the AuthForge PPA + ansible.builtin.apt_repository: + repo: "{{ authforge_ppa }}" + state: present + update_cache: true + +- name: Install AuthForge headless components + ansible.builtin.apt: + name: + - authforge-daemon + - authforge-pam + - authforge-cli + state: present + update_cache: true + +- name: Install AuthForge GUI (optional) + ansible.builtin.apt: + name: authforge-gui + state: present + when: authforge_install_gui | bool + +- name: Ensure /etc/authforge/policy.d exists + ansible.builtin.file: + path: /etc/authforge/policy.d + state: directory + owner: root + group: root + mode: "0755" + +- name: Render fleet policy file + ansible.builtin.template: + src: policy.conf.j2 + dest: /etc/authforge/policy.d/90-fleet.conf + owner: root + group: root + mode: "0644" + notify: restart authforge-daemon + +- name: Mark users as pending-enrollment + ansible.builtin.command: + argv: + - authforgectl + - pending + - set + - "{{ item.user }}" + - --methods + - "{{ item.methods | join(',') }}" + loop: "{{ authforge_pending_users }}" + loop_control: + label: "{{ item.user }}" + changed_when: true diff --git a/ansible-role/templates/policy.conf.j2 b/ansible-role/templates/policy.conf.j2 new file mode 100644 index 0000000..c9d56cf --- /dev/null +++ b/ansible-role/templates/policy.conf.j2 @@ -0,0 +1,18 @@ +# {{ ansible_managed }} +# Fleet-managed AuthForge policy. Hand-edits will be overwritten on the next +# Ansible run. To override locally, drop a higher-numbered file in +# /etc/authforge/policy.d/ (e.g. 99-local.conf). + +{% for stack, cfg in authforge_stacks.items() %} +[stacks.{{ stack }}] +mode = "{{ cfg.mode }}" +methods = {{ cfg.methods | to_json }} + +{% endfor %} +[storage] +backend = "{{ authforge_storage_backend }}" +central_path = "{{ authforge_storage_central_path }}" + +[firstrun] +default_required_methods = {{ authforge_firstrun_methods | to_json }} +deadline_hours = {{ authforge_firstrun_deadline_hours }} From 407e71072d041e08e3e86d130556827bd3b0fd76 Mon Sep 17 00:00:00 2001 From: michael Date: Mon, 27 Apr 2026 08:36:29 -0700 Subject: [PATCH 3/3] ansible: document role variables and ship example playbook README walks through what the role does, every default, and how to invoke it from a parent playbook. examples/playbook.yml is a runnable copy that targets a `workstations` group with a sudo=required+fido2 stack and one pending user. Co-Authored-By: Claude Opus 4.7 (1M context) --- ansible-role/README.md | 76 ++++++++++++++++++++++++++++++ ansible-role/examples/playbook.yml | 31 ++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 ansible-role/README.md create mode 100644 ansible-role/examples/playbook.yml diff --git a/ansible-role/README.md b/ansible-role/README.md new file mode 100644 index 0000000..cff0948 --- /dev/null +++ b/ansible-role/README.md @@ -0,0 +1,76 @@ +# dangerousthings.authforge + +Ansible role that installs and configures [AuthForge](https://github.com/dangerousthings/authforge), +the FIDO2 / U2F / TOTP MFA stack for Ubuntu, on fleet hosts. + +The role: + +1. Adds the `ppa:dangerousthings/authforge` PPA. +2. Installs `authforge-daemon`, `authforge-pam`, and `authforge-cli` (and + optionally `authforge-gui` for desktop fleets). +3. Renders a TOML fleet policy file at `/etc/authforge/policy.d/90-fleet.conf` + from your variables. +4. Restarts `authforge-daemon.service` whenever the policy file changes. +5. Seeds first-login pending-enrollment flags via `authforgectl pending set`. + +The role talks to AuthForge exclusively through `authforgectl` and the +documented config-file shape - it does not touch daemon state directly. + +## Requirements + +- Target host: Ubuntu 22.04 (jammy) or 24.04 (noble). +- Control host: Ansible 2.14 or newer. +- The host must be able to reach `ppa.launchpad.net` (or your internal + mirror - override `authforge_ppa` to point at it). + +## Role variables + +All variables and their defaults live in `defaults/main.yml`. + +| Variable | Default | Description | +| --- | --- | --- | +| `authforge_install_gui` | `false` | Also install the `authforge-gui` package (only useful on desktop fleets). | +| `authforge_ppa` | `ppa:dangerousthings/authforge` | Source PPA. Override to use an internal mirror. | +| `authforge_stacks` | `{}` | Map of PAM stack name to `{ mode, methods }`. `mode` is one of `disabled`, `optional`, `required`. `methods` is a list e.g. `["fido2", "totp"]`. | +| `authforge_storage_backend` | `per-user` | Credential storage backend. `per-user` writes under each user's home; `central` uses `authforge_storage_central_path`. | +| `authforge_storage_central_path` | `""` | Path for the central storage backend. Ignored when `authforge_storage_backend` is `per-user`. | +| `authforge_firstrun_methods` | `["fido2"]` | Methods accepted during first-login enrollment. | +| `authforge_firstrun_deadline_hours` | `0` | Hours before an unenrolled pending user is locked out. `0` disables the deadline. | +| `authforge_pending_users` | `[]` | List of `{ user, methods }` to mark as pending-enrollment. | + +## Example playbook + +```yaml +- hosts: workstations + become: true + roles: + - role: dangerousthings.authforge + vars: + authforge_stacks: + sudo: + mode: required + methods: [fido2] + sshd: + mode: optional + methods: [fido2, totp] + authforge_pending_users: + - user: alice + methods: [fido2] +``` + +A runnable copy lives at `examples/playbook.yml`. + +## Idempotence notes + +- The `apt_repository`, `apt`, `template`, and `file` tasks are all + idempotent; only the `authforgectl pending set` loop is forced to + `changed_when: true` because the CLI does not currently expose a clean + "is this user already pending?" check that's cheap to call from Ansible. + Running `pending set` against an already-pending user is a no-op on + the daemon side. +- The handler restarts `authforge-daemon` only when the policy file + contents actually change. + +## License + +Apache-2.0. Same as the rest of AuthForge. diff --git a/ansible-role/examples/playbook.yml b/ansible-role/examples/playbook.yml new file mode 100644 index 0000000..0cd8481 --- /dev/null +++ b/ansible-role/examples/playbook.yml @@ -0,0 +1,31 @@ +--- +# Example playbook that applies the dangerousthings.authforge role to a +# fleet of headless Ubuntu workstations. +# +# Run from the parent of this `ansible-role/` directory with: +# +# ansible-playbook -i inventory ansible-role/examples/playbook.yml +# +# (You'll need the role linked into your roles_path, or to install it from +# Galaxy as `dangerousthings.authforge`.) + +- name: Roll out AuthForge to engineering workstations + hosts: workstations + become: true + roles: + - role: dangerousthings.authforge + vars: + authforge_install_gui: false + authforge_stacks: + sudo: + mode: required + methods: + - fido2 + authforge_storage_backend: per-user + authforge_firstrun_methods: + - fido2 + authforge_firstrun_deadline_hours: 72 + authforge_pending_users: + - user: alice + methods: + - fido2