config/keycloak/bootstrap.sh provisions a Keycloak realm configured to
the NIST SP 800-171 Rev 2 baseline in one command. Every setting it
applies is mapped below to the control it inherits.
KC_URL=http://localhost:8081 \
KC_ADMIN=admin \
KC_ADMIN_PASSWORD=<admin-password> \
REALM=cmmc \
REDIRECT_URI=https://filebrowser.your-domain.mil/api/auth/oidc/callback \
./config/keycloak/bootstrap.shThe last line of stdout is the client secret — capture it out of shell
history and feed it to filebrowser as FB_OIDC_CLIENT_SECRET. Rerunning
the script rotates the secret, so do it once at install time.
Requires curl and jq on the machine running the script.
| Setting | Value | Control |
|---|---|---|
passwordPolicy |
length(12), digits, lower/upper, special, history(5), 90-day expiry, notUsername | 3.5.7, 3.5.8 |
bruteForceProtected |
true, failureFactor=5, waitIncrement=60s, max=900s | 3.1.8 |
ssoSessionIdleTimeout |
1800s (30 min) | 3.1.10 |
ssoSessionMaxLifespan |
28800s (8 hours) | 3.1.11 |
accessTokenLifespan |
600s (10 min) | 3.1.11 |
| CONFIGURE_TOTP default action | enabled, defaultAction=true | 3.5.3 |
otpPolicyAlgorithm |
HmacSHA256 (FIPS-approved) | 3.13.11 |
eventsEnabled + adminEventsEnabled |
true, 180-day retention | 3.3.1, 3.3.2, 3.3.9 |
sslRequired |
external (allows localhost dev) | 3.13.8 |
registrationAllowed |
false (admins provision) | 3.1.5 |
editUsernameAllowed |
false (identifier stability) | 3.1.1, 3.5.5 |
rememberMe |
false | 3.5.4 |
duplicateEmailsAllowed |
false | 3.5.1 |
Client pkce.code.challenge.method |
S256 (required) | 3.5.4 |
Client directAccessGrantsEnabled |
false (auth-code only) | 3.13.15 |
clientAuthenticatorType |
client-secret, confidential | 3.13.15 |
Created empty and ready to populate with users:
filebrowser-admins→ maps toPerm.Admin=trueviaFB_OIDC_ADMIN_GROUPSmanagementengineeringoperationsqualitysalescompliance— reserved for the audit-admin role (separation-of-duties per 3.3.9 / 3.1.4)
The group membership is surfaced to filebrowser via the oidc-group- membership-mapper on the filebrowser client, emitted as the groups
claim in the id_token.
Keycloak 26 does not emit an amr claim by default; it emits
acr: "1" for all logins regardless of whether MFA was used. The
bootstrap adds a oidc-hardcoded-claim-mapper that injects
amr: ["pwd", "otp"] into every id_token emitted for the filebrowser
client.
This is only truthful because the realm enforces:
CONFIGURE_TOTPas adefaultAction— every new user is forced through TOTP setup on first login.- The default
browserauthentication flow'sBrowser - Conditional OTPsubflow — any user who has TOTP configured is prompted for their code on subsequent logins.
Breaks if an admin removes a user's OTP credential without also revoking group membership. Mitigations:
- Use the Keycloak admin event log (enabled by this script, 180-day
retention) to alert on
REMOVE_TOTPevents tocompliance. - For a stronger guarantee, replace the Conditional OTP step in the auth flow with a hard-required OTP step. Tracked in the project memory as a follow-up to the v1 MFA work.
A production-hardened alternative is Keycloak's Level-of-Assurance
(LoA) mechanism: configure the auth flow to emit acr=mfa when
OTP was used, set the client's "Default ACR Values" to require
acr=mfa, and point filebrowser at FB_OIDC_MFA_CLAIM=acr. This
binds the claim to what Keycloak actually enforced rather than a
hardcoded value.
The bootstrap only creates empty groups. Users are provisioned separately. Example for adding an engineer via the admin REST API:
TOKEN=$(curl -s -X POST "$KC_URL/realms/master/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=$KC_ADMIN&password=$KC_ADMIN_PASSWORD&grant_type=password&client_id=admin-cli" \
| jq -r .access_token)
# Create user with the CONFIGURE_TOTP required action
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
"$KC_URL/admin/realms/cmmc/users" \
-d '{"username":"alice","enabled":true,"emailVerified":true,
"email":"alice@cmmc.local","firstName":"Alice","lastName":"Engineer",
"requiredActions":["CONFIGURE_TOTP"]}'
ALICE_ID=$(curl -s -H "Authorization: Bearer $TOKEN" \
"$KC_URL/admin/realms/cmmc/users?username=alice" | jq -r '.[0].id')
# Set initial password (must satisfy policy: 12+ chars, complexity)
curl -s -X PUT -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
"$KC_URL/admin/realms/cmmc/users/$ALICE_ID/reset-password" \
-d '{"type":"password","value":"<initial-password>","temporary":true}'
# Add to engineering group
ENG_ID=$(curl -s -H "Authorization: Bearer $TOKEN" \
"$KC_URL/admin/realms/cmmc/groups" | jq -r '.[] | select(.name=="engineering") | .id')
curl -s -X PUT -H "Authorization: Bearer $TOKEN" \
"$KC_URL/admin/realms/cmmc/users/$ALICE_ID/groups/$ENG_ID"On first login, alice will be forced through TOTP setup, then prompted for the TOTP code on every subsequent session.
- Kerberos / LDAP federation — up to the deployment; the realm is configured to work with local Keycloak users only.
- Email SMTP — intentionally unconfigured. Outbound email (including
password reset and CUI sharing) is out of MVP scope — see
architecture § 10. The realm ships with
verifyEmail: falseso no operator intervention is needed on first login. If an operator wants password-reset-by-email they wire their own SMTP into Keycloak; it's pure Keycloak config, not an appliance concern. - Identity providers / social logins — disabled by default; enable only with customer approval.
- WebAuthn / FIDO2 — not configured; recommended as an upgrade from TOTP once assessor accepts the WebAuthn attestation path.
- Themes / branding — left at Keycloak defaults.