FIPS Mode At Install
MOD supports and helps with cryptographic-protection controls (NIST SC-12 / SC-13). MOD does not provide, claim, certify or validate compliance with anything, and nothing in this page means the install is running FIPS-approved cryptography. Read the "Not available yet" section before you act on this page.
Where the install stands today
There is no FIPS-validated crypto path in a stock MOD install. Concretely, as grepped in this repo:
CORE/Worker/Dockerfilebuilds ongolang:1.25-alpinewith noGOEXPERIMENTand noboringcryptoreplace. Since GY.C2b the Dockerfile carries aGOFIPS140build arg — empty (the default, the stock build) or--build-arg GOFIPS140=v1.0.0for a FIPS build, which setsGODEBUG=fips140=onlyfor everygo buildin the image — but the FIPS worker release built that way is held and not shipped; stock workers reportfips140_mode: "off".CORE/API/Dockerfile:2isFROM python:3.11-slim— stock OpenSSL, no OpenSSL 3 FIPS provider, noFIPS_MODE=1inCORE/API.- The Keycloak image (
keycloak/keycloak:26.3inCORE/startup/docker-compose.yml) carries noKC_BOOTSTRAP_FIPS*; the Node frontends are stocknode:*-alpine. - There is no FIPS provider switch: MOD reports the crypto posture, it does
not install one.
GET /v1/platform/compliance/crypto-status(platform admin, GY.C2a) is the reporting surface — see "What MOD reports" below — and the platform settingcrypto.fips_mode_required(groupcrypto, defaultfalse) decides whether that report is informational or gates readiness. Thecrypto_statussection in the evidence bundle (CORE/API/services/evidence_bundle.py) is separate: it reports the declared compliance environment and which crypto controls that environment needs — it declares intent, it does not report an algorithm mode.
The register rows for this are docs/security-audit/REGISTER.md R-061
("No FIPS 140-2/140-3 path") and R-150 ("No component is FIPS 140-2/3
validated"), with the detail in
docs/security-audit/findings/GSEC-4-crypto.md.
What is actually in place
1. Non-approved digests are declared, not hidden
docs/security-audit/FIPS_CRYPTO_EXCEPTIONS.md is the standing inventory of
every non-test md5/sha1 use in CORE/API. Three sites carry an explicit
usedforsecurity=False annotation, which is what lets the code import and run
on a FIPS-mode Python build instead of raising:
| Site | Digest | Why it is annotated |
|---|---|---|
CORE/API/utils/docs_ingester.py:294 | MD5 | chunk id / cache key |
CORE/API/security/password_policy.py:111 | SHA-1 | HIBP k-anonymity lookup key (protocol-mandated) |
CORE/API/routers/support_chat/_shared.py:3647 | SHA-1 | dedup fingerprint key |
The annotation is a declaration that the digest is not a security control.
It is not a claim that the digest is FIPS-approved. Two entries in that
document are flagged for the lead and are not resolved: the Postmark
webhook HMAC (CORE/API/routers/postmark_webhooks.py:93, HMAC-SHA-1 — a
security use, cannot simply be switched because Postmark signs with SHA-1)
and the HIBP SHA-1 lookup if your posture requires all password hashing to be
FIPS-approved.
2. Audit integrity already uses HMAC-SHA-256
CORE/API/audit.py signs every audit row with
hmac.new(AUDIT_HMAC_KEY, ..., hashlib.sha256) and chains rows per tenant.
AUDIT_HMAC_KEY is generated at install (CORE/startup/docker-compose.yml
→ secrets/audit_hmac_key.env). SHA-256 is in the FIPS-approved set; the key
is a separate secret, so rotating it invalidates older rows' hashes.
3. Front TLS termination at install
MOD puts a Caddy front in front of the web tier and terminates TLS there
(--tls internal / public / off, CORE/startup/lib/front-tls.sh). See
Front TLS at install. Termination at a front you
control is the practical way to get encryption-in-transit (SC-8) with a
FIPS-validated OpenSSL in front of MOD — MOD does not itself supply a FIPS
OpenSSL build.
4. What MOD reports (GY.C2a) — a report, not a switch
GET /v1/platform/compliance/crypto-status (platform admin) reports the crypto
posture the API process actually runs on:
- the OpenSSL version and whether the FIPS provider is the default. There is no
public Python API for "is the FIPS provider active", so it is inferred from
behaviour: on a FIPS-enforced build
hashlib.md5(b"", usedforsecurity=True)raises./proc/sys/crypto/fips_enabledis read when readable (a FIPS-registered OS kernel) and reportedunknownotherwise — never guessed. - the
cryptographybackend version and its FIPS flag when that build exposes one (backend._fips_enabled), reportedunknownwhen it does not; - the standing exception inventory above, parsed from the markdown table into
rows, with
security_useflagging the security-classified entries; - per-worker crypto mode — since GY.C2b the Go worker reports a
cryptoblock on its heartbeat ({go_version, fips140_mode, fips140_enabled, boringcrypto}), stored on the worker'shardware_specsand shown per worker. A worker on a pre-GY.C2b binary omits the block and staysunknown— never guessed; - the boot known-answer self-test result: SHA-256, HMAC-SHA256, AES-256-GCM encrypt + decrypt round-trip, and ECDSA P-384 sign + verify against fixed vectors. ECDSA signatures are randomised, so the known answer is the derived public point and the signature is required only to verify. The result is cached in-process and runs once at startup.
Platform setting crypto.fips_mode_required (group crypto, bool, default
false) decides what a failure means. While false the report is
informational — a stock non-FIPS install is not unhealthy and the conformance
check crypto.fips_mode (SC-13) is not-applicable. While true, a failed
self-test or a non-enforced build marks the crypto_self_test readiness
component degraded in GET /v1/health?verbose=true and fails the check. The
self-test logs ERROR on failure and never crashes the process.
5. Non-approved algorithms refused + generated crypto inventory (GY.C2)
Two supporting pieces for the "non-approved algorithms refused" half of GY.C2:
- Postgres password storage (SCRAM only). The conformance check
crypto.pg_scram(SC-13) passes whenSHOW password_encryptionisscram-sha-256and zero roles still store an md5 verifier; it is read-only, falls back frompg_authidtopg_shadow, and reports unknown (manual) rather than failing when neither catalog is readable. Fresh installs setpassword_encryption=scram-sha-256at install time (thepostgres-appcompose command); existing long-lived roles still stored as md5 need one re-hash withbash CORE/startup/db-tls/rehash-scram.sh apply. MOD supports a SCRAM-only Postgres posture; it does not alter a running database by itself. - Generated crypto inventory.
cd CORE/API && python3 tools/crypto_inventory.pyscansCORE/API(Python AST) andCORE/Worker(Go imports) and rewritesdocs/security-audit/CRYPTO_INVENTORY.md— a deterministic file:line / algorithm / purpose / approval table (approved per FIPS 140-3; Ed25519 per FIPS 186-5; MD5/SHA-1 only withusedforsecurity=False).python3 tools/crypto_inventory.py --checkis the CI gate: exit 1 when a non-approved security use appears thatFIPS_CRYPTO_EXCEPTIONS.mddoes not list.
Planned, not available
These are on the backlog (docs/security-audit/CODE_READY_BACKLOG.md R-061,
docs/security-audit/VERTICAL_GAP_MATRIX.md "FIPS 140-3 mode", feature GY.C2)
and are not in a shipped install:
- a Go worker BUILT with the FIPS module: the build switch exists since
GY.C2b (
--build-arg GOFIPS140=v1.0.0inCORE/Worker/Dockerfile, which setsGODEBUG=fips140=onlyfor the build and makes the running worker reportfips140_mode: "only"), but the FIPS worker release is held — no worker in the fleet runs it yet; - an OpenSSL 3 FIPS provider image for the Python tier;
- refusing non-approved algorithms at runtime (the crypto-status endpoint reports whether the build refuses them; it cannot make a stock build do so);
- a boot self-test and a crypto-status endpoint — landed (GY.C2a, see "What MOD reports" above); what is still missing is the provider itself;
- Keycloak in FIPS mode.
CORE/LicenseServer/API/crypto/key_manager.py reads CRYPTO_FIPS_MODE
(default false) and uses it only to log "FIPS" vs "standard" in a
startup line — it does not change any algorithm. Do not treat that as a FIPS
switch.
What an operator can do today
- Host-level FIPS mode. On a FIPS-registered OS (RHEL/Alma/Rocky in
FIPS mode, or a FIPS kernel), userspace inherits the OS OpenSSL. MOD does
not configure this, but it does report what it finds — the crypto-status
endpoint's behavioural probe plus
/proc/sys/crypto/fips_enabled; verify on the host (openssl fipsinstall-style checks are the OS's, not MOD's). Expect the annotatedusedforsecurity=Falsesites above to keep working, and expect the Postmark HMAC-SHA-1 router to remain SHA-1 — that is the open exception in the inventory document. - FIPS-validated TLS termination in front of MOD. Put your own
FIPS-validated OpenSSL/mTLS terminator in front and set
--tls off, so MOD forwards to something that already does encryption in transit. Theoffmode is only correct when something else in front does the encryption. - Keep the audit chain on SHA-256 (already the case) and keep
AUDIT_HMAC_KEYin the secrets file rather than reusing a value across installs. - Read the exception inventory before you claim a FIPS posture. If you
add a digest use, add a row to
docs/security-audit/FIPS_CRYPTO_EXCEPTIONS.md— the inventory is the record, and MOD will not find new violations for you.
MOD provides features that support your FIPS 140-3 work; MOD itself is not a validated cryptographic module, and nothing on this page means that MOD is FIPS compliant, certified, validated, or that it satisfies a control. Describe what MOD does as supporting your work — the validation, and the claim of it, belongs to the module you run and to your assessment.
Related pages
- Secure configuration profiles — the
fipscontrol is a build-level capability, not a setting; the profile tables list what each profile actually locks. - SIEM hookup — the HMAC-SHA-256 audit chain described above, and how its per-row hash reaches your SIEM.
- Giving your assessor read-only access — the crypto-status report and the exception inventory are sections of the evidence bundle an assessor can export.
- Internal TLS — the internal-hop posture behind
gsec.transport.internal_tls_enforce. - Front TLS at install — the termination point discussed under item 3.