PAM - Rotation daemon - #1232
Draft
Hinton wants to merge 18 commits into
Draft
Conversation
Generated from the server pam/rotation branch api.json (swagger internal group) via support/generate-api-bindings-ci.sh. Adds the rotation daemon, jobs, attempts, configs, daemons and target-systems API groups and their models.
New commercial crate bitwarden_license/bitwarden-rotation-daemon (bw-rotation-daemon binary). Implements the daemon token parser (0.daemon.<id>.<secret>:<key>), org-key unwrap + cipher password re-encryption on a standalone KeyStore, password-policy mapping onto bitwarden-generators (password() made pub), the bounded failure taxonomy with SafeDetail, and the IdentityClient + SessionManager session state machine per rotation-daemon.allium.
…ation, executor, CLI - api/: DaemonAuthMiddleware (bearer + single 401 refresh-retry, hard-fail on session loss) over the generated bitwarden-api-api rotation clients; error classification incl. daemon-route-404 -> NotEligible. - resolver/: CredentialResolver trait + env resolver (per-kind required vars). - integrations/: Integration trait/registry + custom-script runner (secrets over stdin only, stdout/stderr nulled, exit-code contract, timeout kill, script-root containment). - executor/: transient retry budget (gated variant re-checks the step-boundary gate before every try), 5-arm gate (execute_by lease, session phase, connectivity pause), 7-step rotation flow with Verified/CipherWritten proof tokens, poll loop with single-flight claim + heartbeat task, refresh-probe on NotEligible. - cli/config/main: clap surface with no token argv slot (env/file XOR), signal-wired cancellation, exit codes 0/1/2/3. 170 crate tests passing.
…lper - integrations/entra.rs: Microsoft Graph rotate (PATCH passwordProfile), directory-confirmation verify (lastPasswordChangeDateTime + optional ROPC probe), revokeSignInSessions; SP client-credentials auth (admin reset, never the rotated credential); account_identity encoded as a single URL path segment; detail carries HTTP status + Graph error.code only. - examples/register.rs: TEST-ONLY admin-side wrap emulator (org key from env/stdin, never argv) producing the register-request payload + token template for e2e. 191 crate tests passing.
Security: strip BWRD_TOKEN from the environment after read and .env_clear() custom-script children so the credential can't be inherited (no_leak.sh now asserts it); validate Graph error.code charset before it enters a failure report; redirect::Policy::none() on the identity/api/entra HTTP clients so a cross-host redirect can't leak the bearer. Spec: make VerifiedBeforeSuccess compile-time enforced via report_success_inner taking the Verified/CipherWritten proof tokens by value; report target_updated on unexpected step-5 cipher errors instead of silently leaving the attempt unreported. Deps: move anyhow to the workspace. 196 crate tests passing.
- Clear all crate clippy findings incl. the config.rs unwrap_used deny (is_some()+unwrap -> if let) and a test holding ENV_LOCK across await. - tests/daemon_flow.rs: 5 wiremock-driven end-to-end scenarios (happy path claim->cipher writeback->success, transient failure-budget exhaustion, claim 409 race, startup credential-refused, terminate-failed still succeeds) via a #[doc(hidden)] DaemonConfig::new_for_test hook. 201 tests passing (196 unit + 5 integration).
…d bindings - Make the crate pass the full CI lint suite: fmt, clippy (0 warnings incl. print-macro allow on the register example), cargo-sort, udeps, and rustdoc -D warnings (resolve/de-link intra-doc links to internal items). - Add anyhow to the workspace deps + renovate owner group (dep-ownership). - Reformat README (prettier). - bitwarden-importers: set the new access_rule_id: None on CollectionWithIdRequestModel (field added to the regenerated bindings from the server branch; None preserves prior behavior).
… flags Settings (URLs, intervals, retry tuning, script root/timeout, Entra probe) move to a TOML config file (--config <PATH> / BWRD_CONFIG); built-in defaults live in impl Default for FileConfig and fill missing keys via #[serde(default)]. deny_unknown_fields rejects a token key in the file: the daemon token is env-only (BWRD_TOKEN, stripped from the environment after read, empty treated as missing) and --token-file is removed. BWRD_API_URL / BWRD_IDENTITY_URL override the file's URLs (env > file > built-in defaults). Tests gain a process-wide TEST_ENV_LOCK so config and custom-script modules serialise all env mutation. README rewritten around the config file; renovate now pins toml.
…-level URL keys
Server URLs now live in an [environment] TOML table: base derives
{base}/api and {base}/identity, with optional explicit api/identity
overrides. Precedence per URL: BWRD_API_URL/BWRD_IDENTITY_URL env vars
> explicit keys > base derivation > startup error. The old top-level
api_url/identity_url keys are rejected via deny_unknown_fields.
Contributor
🔍 SDK Breaking Change DetectionSDK Version:
Breaking change detection uses the build of the SDK from this branch, including any incompatibities pre-existing on or merged into this branch. Check the workflow logs to confirm. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1232 +/- ##
==========================================
+ Coverage 85.09% 85.56% +0.46%
==========================================
Files 476 497 +21
Lines 65899 72012 +6113
==========================================
+ Hits 56080 61614 +5534
- Misses 9819 10398 +579 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…ging Session renewals log success/retry/terminal transitions; every rotation step logs an info milestone; all reported failures emit one warn with attempt_id, failure code, sync state, and safe detail (failure report sites now route through report_failure_absorb). No secret material is ever logged.
…redentials New optional [targets.<uuid>] TOML table supplying per-target credentials: script (CustomScript), tenant_id/client_id (Entra). Per-key precedence: config file wins, env vars fill gaps. client_secret is env-only and rejected in the file, same policy as the daemon token. Also avoids the POSIX shell limitation that env var names derived from digit-leading UUIDs cannot be exported. Includes the executor's claim/heartbeat/startup log lines and the startup configured_targets count, plus README docs for logging and the [targets] section.
CONTRACT C2 verified end-to-end against the dev server: the cipher data blob is flat PascalCase, but the server serializer omits null fields, so a login cipher that never had a password has no Password key at all. The write is now insert-or-replace; CipherDataShape is only returned when the blob root is not a JSON object. Fixes CipherEncryptFailed on first rotation of a fresh cipher.
…pagation Microsoft Graph is eventually consistent: lastPasswordChangeDateTime lags an administrative passwordProfile reset by seconds-to-minutes. The single-shot verify read ran ~0.5s after rotation and got the previous timestamp, falsely failing StalePasswordChangeTimestamp and leaving the target updated but the vault unwritten. verify now polls the directory for a fresh timestamp up to VERIFY_MAX_WAIT (60s) at VERIFY_POLL_INTERVAL (5s); timing is injectable so tests stay single-shot (zero) and a poll test can inject tiny values. The ROPC probe now runs first (authoritative, no replication lag). HTTP and body-parse errors still return immediately with their existing classification; only a 200 with a stale/missing timestamp is retried. All verify error paths keep effect=Applied.
The tracing setup fed `option_env!("RUST_LOG")` to `with_default_directive`,
which had two problems: option_env! captures RUST_LOG at compile time (not
runtime), and with_default_directive parses a single Directive, so a
comma-separated RUST_LOG (e.g. "info,bitwarden_rotation_daemon=debug") baked in
at build time panicked the daemon at startup.
Use a static INFO default directive instead; from_env_lossy already reads
RUST_LOG at runtime and parses full comma-separated filter strings leniently.
Conflicts were confined to Cargo.lock and generated bitwarden-api-api files. Resolved by taking main's side, then regenerating the API bindings from the server pam/rotation branch (69d020730, which includes main's binding source e899a9c9) via artifacts/api.json + support/generate-api-bindings-ci.sh. Regenerated output matches the merged tree exactly: main's bindings plus the PAM rotation endpoints. cargo check, full test suite (2035 passed, 0 failed) and clippy green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Generated from the server pam/rotation branch api.json (swagger internal
group) via support/generate-api-bindings-ci.sh. Adds the rotation daemon,
jobs, attempts, configs, daemons and target-systems API groups and their
models.## 🎟️ Tracking
📔 Objective
🚨 Breaking Changes