digitalboard.core/roles/garage
Simon Bärlocher 0733d5710f
feat(garage,authentik): console multi-domain + standalone proxy outpost
Two related additions for gating a cross-host service (garage WebUI on a
storage host) behind authentik without breaking on TLS or X-Forwarded-Host:

garage role:
- Add garage_webui_domains (list), mirroring garage_s3_domains. The
  console router now accepts every entry, so a DMZ reverseproxy can use a
  backend_host whose cert SAN matches an internal *.int.* name instead of
  connecting by IP (which fails acme cert verification). Defaults to the
  single garage_webui_domain so existing inventories are unaffected.

authentik_outpost_proxy role (new):
- Standalone proxy (ForwardAuth) outpost, modelled on authentik_outpost_ldap.
  Co-locate it with the protected service so the ForwardAuth subrequest
  reaches authentik over the local docker network. The embedded outpost
  only works for services on the authentik host: a cross-host subrequest
  routed through an extra reverse-proxy hop arrives with a polluted
  X-Forwarded-Host (port/comma-appended), which the outpost no longer
  matches against the provider's external_host (404).
2026-06-05 14:22:36 +02:00
..
defaults feat(garage,authentik): console multi-domain + standalone proxy outpost 2026-06-05 14:22:36 +02:00
handlers feat: add basic garage s3 storage role 2025-11-07 17:35:32 +01:00
meta feat(garage,authentik): console multi-domain + standalone proxy outpost 2026-06-05 14:22:36 +02:00
tasks fix(garage): make bootstrap & provision idempotent across reruns 2026-05-27 23:12:23 +02:00
templates feat(garage,authentik): console multi-domain + standalone proxy outpost 2026-06-05 14:22:36 +02:00
tests feat: add basic garage s3 storage role 2025-11-07 17:35:32 +01:00
vars feat: add basic garage s3 storage role 2025-11-07 17:35:32 +01:00
README.md docs(collection): document all roles and fix metadata drift 2026-05-27 23:12:24 +02:00

Garage

Ansible role to deploy Garage S3-compatible object storage via Docker Compose, with declarative key/bucket provisioning and an optional WebUI behind htpasswd or authentik ForwardAuth.

Requirements

  • Docker and Docker Compose installed on the target host
  • Ansible collection: community.docker
  • htpasswd (from apache2-utils / httpd-tools) when the WebUI is enabled and authentik ForwardAuth is not used
  • Traefik with a shared garage_traefik_network (default proxy)

Role variables

Full spec with types and defaults: meta/argument_specs.yml. The most common overrides:

Service

  • garage_s3_domains: FQDNs the S3 router accepts. The first entry is the canonical hostname; garage.toml derives the virtual-hosted-style S3 root_domain from it as .s3.<first-entry> (so buckets resolve under <bucket>.s3.<first-entry>).
  • garage_web_domain, garage_webui_domain: separate hostnames for the S3-website endpoint and the console.
  • garage_image, garage_replication_factor, garage_db_engine, garage_s3_region.

Required secrets

Generate with openssl rand -hex 32 (32 bytes / 64 hex chars):

  • garage_rpc_secret: node-to-node RPC secret
  • garage_admin_token: admin API token
  • garage_metrics_token: metrics endpoint token

WebUI authentication

Three modes:

  1. htpasswd (default): garage_webui_username / garage_webui_password in plaintext. The role hashes the password with htpasswd -nbBC 10, persists the hash on disk, and re-verifies with htpasswd -vbB so unchanged passwords don't churn the play.
  2. authentik ForwardAuth: set garage_webui_authentik_forward_auth: true and garage_webui_authentik_forward_auth_url: "https://auth.example.com/outpost.goauthentik.io/auth/traefik". AUTH_USER_PASS is dropped from the container env so authentik is the only gate.
  3. Disabled: garage_webui_enabled: false.

Layout bootstrap

Setting garage_bootstrap_enabled: true runs the bootstrap task, which joins the local node to the layout (zone: garage_bootstrap_zone, capacity: garage_bootstrap_capacity) on the first run. The check tolerates the 16-char truncation that garage layout show performs.

Declarative S3 keys and buckets

garage_s3_keys:
  - name: nextcloud
    buckets:
      - name: nextcloud-data
        permissions: [read, write]
  - name: backup
    buckets:
      - name: restic-prod
        permissions: [read, write, owner]

The role:

  • Lists existing keys (garage key list), creates missing ones
  • Lists existing buckets (garage bucket list), creates missing ones
  • Reads current permissions via garage bucket info and runs garage bucket allow only when the current RWO flags for the key don't already match the desired permissions

stdout parsing is hardened against ANSI escapes and interleaved INFO log lines, so probe noise no longer produces spurious changes.

Dependencies

  • Traefik network (garage_traefik_network, default proxy)
  • Internal network (garage_internal_network, default internal)

Example playbook

- hosts: storage_servers
  roles:
    - role: digitalboard.core.garage
      vars:
        garage_s3_domains:
          - "storage.example.com"
          - "storage.int.example.com"
        garage_rpc_secret: "{{ vault_garage_rpc_secret }}"
        garage_admin_token: "{{ vault_garage_admin_token }}"
        garage_metrics_token: "{{ vault_garage_metrics_token }}"
        garage_bootstrap_enabled: true
        garage_webui_authentik_forward_auth: true
        garage_webui_authentik_forward_auth_url: "https://auth.example.com/outpost.goauthentik.io/auth/traefik"
        garage_s3_keys:
          - name: nextcloud
            buckets:
              - name: nextcloud-data
                permissions: [read, write]

License

MIT-0