key-cape/docs/optional-mfa.md
tegwick 122a0d1369
All checks were successful
Authentication acceptance / acceptance (push) Successful in 1m10s
Build and Publish Container Image / build-and-push (push) Successful in 41s
Require confirmed enrollment and genuine OTP evidence for MFA
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-13 16:37:08 +02:00

93 lines
5.7 KiB
Markdown

# Optional MFA for browser clients
Set `mfaOptional: true` on a reviewed browser registration to allow AAL1 login
when privacyIDEA authoritatively reports no active enrolled factor. Once an
active factor exists, require AAL2. A previous AAL1 session cannot satisfy that
requirement. Explicit AAL2 requests still require MFA. Lookup errors deny login
through the existing account-recovery path. This setting overrides only the
provider `requireForAll` default for that client.
Do not combine this with `mfaRequired`. In particular, `mfaRequired: false`
means unconditional AAL1 and is not an opt-in enrollment policy. Unchanged
registrations keep their existing policy. Service clients reject mfaOptional.
The adapter sends the raw privacyIDEA JWT in Authorization, filters active
factors before pagination, and requires a successful, complete response before
concluding there is no active factor. The credential must have realm-scoped
administrative tokenlist permission: a user-role token only lists its own factors
regardless of the requested username. Never replace it with a self-service JWT.
See the [provider API](https://privacyidea.readthedocs.io/en/stable/modules/api/token.html).
## Deployment gate — optional client policy not yet enabled
Factor-read custody, automatic renewal and mounted-file delivery were restored
on 2026-09-13 through RPF-WP-0040 / CCR-2026-0023. KeyCape uses adminTokenFile;
per-user provider lookup and mounted renewal passed. Historical resolver incident
lanes remain separate; there is no missing-owner gate for this service lane.
The live provider has `mfa-passthru-phase1`, so a positive validation value alone
can mean directory-password success. KeyCape accepts AAL2 only when a successful
response identifies a TOTP/HOTP token by serial and type. Static-password tokens,
missing factor evidence and unsuccessful status cannot grant AAL2.
privacyIDEA's `active` flag does not imply completed enrollment. Its verification
policy sets `rollout_state=verify` while leaving `active` unchanged. KeyCape
therefore recognizes `enrolled` and the provider's legacy empty state as enrolled;
`verify`, `clientwait` and `pending` remain incomplete. Missing/unknown/broken
states and incomplete result pages fail closed. Existing verified factors still
require OTP while an additional token is pending. Mandatory and explicit AAL2
policies remain mandatory throughout onboarding.
Current provider self-service policy allows TOTP enrollment/deletion/disabling,
but possession confirmation is not yet required. Review and exercise that
transition plus authenticated recovery before enabling the optional client.
Remaining live acceptance:
1. Verify the deployed provider accepts the raw JWT and returns authoritative
count/tokens results for controlled accounts with and without a factor.
Confirm realm/resolver mapping and administrative tokenlist scope, including
visibility of another user's factor; an empty list alone is not proof.
2. Verify self-service at https://pink-account.coulomb.social: password login,
TOTP enrollment with possession confirmation before activation, cancellation,
and factor removal/recovery. This user-facing flow is not yet verified.
3. Build and pin the reviewed issuer image. Migrate only the exact
vergabe-demo-company registration to mfaOptional using the owner CAS rollout
lane; keep unrelated registrations and Secret bytes intact. The current
vergabe-client-rollout.py intentionally refuses registration differences and
needs an explicit migration before this policy can be applied.
4. Resolve account-portal access for unenrolled users without weakening privileged
access. Review portal policy and self-service permissions before expanding the
optional setting to this shared client. Add the verified enrollment link to
account management once that flow works.
5. Verify fresh password-only admission without a factor, enrollment confirmation,
then OTP enforcement (including reuse of an old AAL1 session). Verify explicit
AAL2 still challenges, lookup failures recover without issuing a code, logout
permits identity switching, and other clients retain their policies.
Rollback: restore the exact previous client registration and image pin via CAS.
This restores mandatory MFA for the demo client; it is not password-only access.
## Credential renewal consumer contract
KeyCape accepts either `privacyidea.adminToken` (existing startup configuration)
or `privacyidea.adminTokenFile` (an absolute protected mounted-file path). Configure
one source only. With a file, KeyCape reads the current value for each factor lookup
and OTP validation. Deliver renewal atomically; do not truncate a live file in
place. A projected Secret volume may update asynchronously; a `subPath` mount
will not provide live renewal. The deployment owner must verify propagation.
The file contains only the raw provider JWT with an optional trailing newline;
input is bounded to 16 KiB. Missing, empty, malformed, oversized or unreadable
files deny the operation without a cached or inline fallback. File contents and
paths are excluded from credential errors. Provider rejection also denies access;
file presence alone does not prove credential validity, scope or freshness.
Default provider requests have a ten-second timeout and do not follow redirects.
The credential must remain provider-issued, realm-scoped and renewed through
owner custody. This consumer feature neither mints credentials nor establishes
an OpenBao path. Deploying an approved file requires a separate protected mount,
read permission for KeyCape's runtime identity, renewal/expiry ownership, and
positive factor/no-factor plus failed-credential/recovery evidence. Existing live
configuration and policy have not been changed by implementing this feature.