Skip to content

feat: Add self-hosted Bitwarden catalog element (container + generic get/set role) #201

Description

@romainkieffer

Description

Umbrella: range42/range42-playbooks#69
Wave: WAVE_04 (since 2026-06-05)
Cross-repo: range42/range42#198, range42/range42-ansible_roles-debug-devkit#114, #178

range42 needs a standardised way to distribute passwords, keys, and other credentials to students and admins. The server
is Vaultwarden, an API-compatible, self-hosted reimplementation of the Bitwarden server.

This issue delivers a single, self-contained catalog element under 03_container_layer/docker/admin/vaultwarden/. It bundles two things together, since scenarios should be able to consume both as one unit:

  1. The Vaultwarden container — the self-hosted credential store itself.

  2. A generic, mapping-driven Ansible role (get/set) that talks to this container's API to sync values between Vaultwarden and one or more

    Ansible vaults:

    • Setter — push vault variables into Vaultwarden as items/fields, scoped to a collection (e.g. per CODENAME-SCENARIO, per student vs per admin).
    • Getter — pull item/field values from Vaultwarden back into vault (e.g. shared/centrally-managed secrets).

The role must not be tied to any specific credential set — it should be driven by a mapping supplied by the caller, e.g.:

bitwarden_sync_map:
  - vault_var: misp_writer_api_key
    bw_item: "range42-{{ codename }}-{{ scenario }}-misp"
    bw_field: writer_api_key
    direction: set
  - vault_var: shared_service_token
    bw_item: "range42-org-shared"
    bw_field: token
    direction: get

A scenario that needs credential distribution spins up this catalog element (the Vaultwarden container) and calls its role to push/pull creds as part of its own provisioning flow (see the companion range42-playbooks issue for scenario-side wiring). The vault (default_vault.yml) remains the source of truth that playbooks consume — Vaultwarden is a distribution/sync layer, not a replacement for the vault.

Working branch

feature/bitwarden-catalog-element

Files / locations

range42-catalog/03_container_layer/docker/admin/bitwarden/Dockerfile          - or plain docker-compose if no build step is needed
range42-catalog/03_container_layer/docker/admin/bitwarden/docker-compose.yml
range42-catalog/03_container_layer/docker/admin/bitwarden/provisioning/       - init scripts: admin token, initial org/collections
range42-catalog/03_container_layer/docker/admin/bitwarden/.env.example        - admin token, domain, signups-allowed flag (default: disabled)
range42-catalog/03_container_layer/docker/admin/bitwarden/README.md           - deploy, admin panel access, API usage notes
range42-catalog/02_ansible_layer/admin/roles/credentials.bitwarden/tasks/main.yml
range42-catalog/02_ansible_layer/admin/roles/credentials.bitwarden/tasks/get.yml  - pull Vaultwarden -> vault vars
range42-catalog/02_ansible_layer/admin/roles/credentials.bitwarden/tasks/set.yml  - push vault vars -> Vaultwarden
range42-catalog/02_ansible_layer/admin/roles/credentials.bitwarden/defaults/main.yml - bitwarden_sync_map, connection vars
range42-catalog/02_ansible_layer/admin/roles/credentials.bitwarden/README.md      - usage, mapping format, auth setup

Note: paths above are workspace-level for now and may be adjusted later.

Requirements

  • Persistent volume for the Vaultwarden data directory.
  • Healthcheck on the container.
  • .env.example with sane defaults (admin token generation, public signups disabled by default for a lab context).
  • Generic get/set entry points, driven entirely by bitwarden_sync_map (no hardcoded credential names).
  • Fails loudly (not silently) if Vaultwarden is unreachable or an item/field in the mapping doesn't exist — this touches secret distribution, no silent fallbacks.
  • No secrets or session tokens logged or written to disk outside the existing vault/secrets handling conventions.
  • README(s) document: build/deploy of the container, retrieving the admin token, creating the API credentials the role consumes, the mapping format, and how a scenario wires the two together.
  • Register the element in manifest.json under the container / admin category.

Open questions (to resolve later)

  • Vaultwarden vs official Bitwarden self-hosted server: Vaultwarden (dani-garcia/vaultwarden) is an unofficial, API-compatible Rust
    reimplementation — single lightweight container, no external DB, no-key step, widely used in self-hosted contexts but not built or by Bitwarden Inc. The official bitwarden/server ("unified" image) vendor-supported and free to self-host (core + org features work with a self-host license key requested from Bitwarden), but is a heavier multi-container stack (web vault, API, identity, DB) and requires that one-time online license request. Both are self-hosted, no cloud/SaaS option is in scope either way — this issue currently assumes Vaultwarden, but the choice should be confirmed before implementation.
  • Auth method: Vaultwarden API key vs client_id/client_secret login.
  • Scoping: is student-vs-admin visibility enforced via Vaultwarden collections/org permissions (external to the role), or does the role itself need to know about student/admin scopes?
  • Conflict resolution: if a vault var already has a value and the getter pulls a different value from Vaultwarden, what wins?
  • Rotation/polling: one-shot sync at provisioning/deploy time, or periodic polling for drift?
  • Lifecycle: does one Vaultwarden container get shared across CODENAME-SCENARIOs, or does each scenario that opts in spin up its own?
  • Org/collection bootstrap: should container provisioning auto-create the student/admin collection split, or is that left to manual admin setup?

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions