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:
-
The Vaultwarden container — the self-hosted credential store itself.
-
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?
Description
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:The Vaultwarden container — the self-hosted credential store itself.
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:
The role must not be tied to any specific credential set — it should be driven by a mapping supplied by the caller, e.g.:
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-playbooksissue 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
Files / locations
Requirements
.env.examplewith sane defaults (admin token generation, public signups disabled by default for a lab context).bitwarden_sync_map(no hardcoded credential names).manifest.jsonunder thecontainer/admincategory.Open questions (to resolve later)
dani-garcia/vaultwarden) is an unofficial, API-compatible Rustreimplementation — 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.client_id/client_secretlogin.