key-cape/docs/optional-mfa.md

94 lines
5.6 KiB
Markdown
Raw Normal View History

# 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).
## Deployed policy and guarded onboarding
P06 is deployed for `vergabe-demo-company` and `user-engine-portal` only.
The issuer owns persistent runtime overrides in `KEYCAPE_POLICY_PATH` on the
`keycape-authentication-policy` PVC. Use the portal Authentication policy page
with a recent-MFA platform session for preview, acknowledged apply and rollback.
Explicit application AAL2 always wins. Administration requires MFA even when
ordinary portal sign-in is optional until factor activation.
privacyIDEA self-service allows TOTP enrollment and deletion only for unfinished
verification-state setup. Possession confirmation activates the factor. An active
factor cannot be deleted, disabled or replaced from a password-only provider
session; use audited platform recovery. Both native issuer and installed-provider
browser acceptance passed against disposable identities and databases.
Factor-read custody, automatic renewal and mounted-file delivery use
RPF-WP-0040 / CCR-2026-0023. No-factor decisions require the realm visibility
probe. Provider lookup/credential failures deny login; pending factor state does
not activate MFA. Validation requires actual TOTP/HOTP evidence, so provider
password passthrough cannot establish AAL2.
Release, exact image pins, positive test coverage and rollback order:
`../user-engine/docs/evidence/2026-09-13-p06-authentication-policy.md`.
Actual invited-person and application login remains KEY-WP-0034 and
VERGABE-WP-0019. Keep the policy PVC and provider guard during replacement.
Before reverting pre-P06 portal guards, restore and verify mandatory policy for
both clients in runtime and baseline registration.
## 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. The dedicated delivery lane is deployed; historical resolver credential repair
remains separately owned under NK-WP-0033.
## Distinguish missing factors from revoked reader access
The installed provider returns HTTP 200 with zero tokens after tokenlist policy
withdrawal. Before accepting a no-factor result, KeyCape therefore requires a
second exact-serial lookup for `privacyidea.readProbeSerial`. This must identify
a disabled, unassigned token in exactly the configured realm. Missing probe,
missing visibility, assignment, activation, realm mismatch or provider failure
denies the decision. Existing confirmed factors still require MFA.
The production probe is `KCFACTORSCOPE01`, created idempotently by NetKingdom's
`sso-mfa/k8s/privacyidea/keycape-scope-probe.py`. Its key is generated by the
provider and never delivered to KeyCape. Keep it disabled and unassigned; it
exists solely to prove realm visibility. Removing it intentionally makes
no-factor decisions unavailable until scope proof is restored.
`provider-onboarding-contract.py` uses a temporary in-memory database and no
production configuration or credentials. Against the installed provider it
proved password-only pass-through, pending-active enrollment, cancellation,
TOTP possession confirmation (explicit `type=totp` on confirmation), confirmed
state, empty-success on permission withdrawal, permission recovery and rejection
of a genuinely provider-issued expired JWT. Production expiry/revocation drills
remain separately recorded under RPF-WP-0040-T04.