Adding FIPS Variants
Overview
FIPS variants provide container images that use only
FIPS 140-3 validated cryptographic modules. FIPS is implemented as a
cross-cutting modifier variant (like builder), not a separate image — the
FIPS variant uses the same base image structure but layers FIPS configuration
on top via additional RPM packages.
FIPS variants are restricted to the Hummingbird distro only, because FIPS-validated packages are not available in Fedora Rawhide.
FIPS images must not contain any non-validated cryptographic libraries. The
presence of an unvalidated crypto library (such as libgcrypt or gnutls)
would undermine the FIPS compliance guarantee, even if the application itself
does not use that library. Global tests enforce this constraint.
FIPS Validation Scope
The cryptographic modules shipped in FIPS images are FIPS 140-3 validated through the NIST Cryptographic Module Validation Program (CMVP). This validation applies only when running on RHEL systems installed in FIPS mode. Running FIPS images outside this environment (e.g., on Fedora, on RHEL not in FIPS mode, or on other Linux distributions) is outside the validated configuration.
As a best-effort goal, FIPS images aim to behave similarly on both FIPS and non-FIPS hosts — restricting cryptographic operations to FIPS-approved algorithms regardless of host configuration. This is not a guarantee; some images (particularly NSS-based ones like OpenJDK) may behave differently depending on host FIPS mode.
Validated Cryptographic Modules
All validated modules in Hummingbird are pre-built binaries from RHEL 9.2, submitted to NIST for FIPS 140-3 validation:
| Module | Package | Verified by global test |
|---|---|---|
| OpenSSL FIPS provider | openssl-config-fips |
fips-provider-matches-ubi9 |
| NSS softokn | nss-softokn-fips |
fips-nss-modules-match-rhel92 |
| NSS freebl | nss-softokn-freebl-fips |
fips-nss-modules-match-rhel92 |
Global tests validate each module’s checksum against the known-good RHEL 9.2 validated binaries. Checksums are architecture-specific because the binaries differ per architecture.
All FIPS variants also install crypto-policies-config-fips, which sets the
system-wide crypto policy to FIPS.
Two FIPS Stacks
Two FIPS stacks exist depending on which crypto library the image’s software uses:
OpenSSL stack — crypto-policies-config-fips + openssl-config-fips.
The OpenSSL FIPS provider is enabled via a drop-in configuration file at
/etc/pki/tls/openssl.d/fips-provider-enable.cnf. Enforcement is
container-side: non-approved algorithms are rejected even on non-FIPS hosts.
Used by: Go, Nginx, Node.js, Python, Ruby.
NSS stack — crypto-policies-config-fips + nss-softokn-fips +
nss-softokn-freebl-fips. NSS checks the host kernel’s FIPS mode
(/proc/sys/crypto/fips_enabled), so enforcement behavior differs between
FIPS and non-FIPS hosts. The primary Java security provider switches to
SunPKCS11-NSS-FIPS. Used by: OpenJDK.
Both stacks share crypto-policies-config-fips for the system crypto policy,
but differ in which cryptographic library provides the validated
implementation.
Adding a FIPS Variant
1. Determine the FIPS Stack
Identify which crypto library the image’s software uses:
- OpenSSL — most languages and servers (Python, Node.js, Ruby, Nginx, Go)
- NSS — Java/OpenJDK (uses NSS via PKCS#11)
This determines which FIPS packages to install.
2. Update properties.yml
Add fips to additional_variants with a Hummingbird distro restriction,
and add the FIPS packages to rpm_packages.fips.
OpenSSL-based image:
additional_variants:
- name: fips
distros: [hummingbird]
rpm_packages:
fips:
- crypto-policies-config-fips
- openssl-config-fips
NSS-based image (OpenJDK):
OpenJDK has existing runtime and runtime-builder base variants. FIPS
is added as both fips (full JDK) and runtime-fips (headless JRE):
additional_variants:
- name: fips
distros: [hummingbird]
- name: runtime-fips
distros: [hummingbird]
rpm_packages:
fips:
- java-21-openjdk-devel # variant-specific packages
- crypto-policies-config-fips
- nss-softokn-fips
- nss-softokn-freebl-fips
runtime-fips:
- crypto-policies-config-fips
- nss-softokn-fips
- nss-softokn-freebl-fips
See the Image Configuration Reference for
complete properties.yml options, including
additional_variants
and rpm_packages.
3. Generate Files
Run make to generate the hummingbird/fips/ directory structure
(Containerfile, RPM lockfiles, VERSION, TAGS):
make
4. Write FIPS Tests
Add FIPS-specific tests to tests-container.yml using filters.variants to
target FIPS variants. A standard set of FIPS tests validates:
- Non-approved algorithm rejected — e.g., MD5 (for security use), Blowfish, RC2
- Approved algorithm works — e.g., SHA-256
- Non-approved cipher rejected — e.g., 3DES, CHACHA20-POLY1305
- Approved cipher works — e.g., AES-256-GCM (encrypt/decrypt roundtrip)
Example (Python):
fips-rejects-md5:
filters:
variants: ["*fips*"]
command: |
test_engine_run --rm "${TEST_IMAGE:?}" python3 -c "
import hashlib
try:
hashlib.new('md5')
print('MD5_ALLOWED')
except ValueError:
print('MD5_REJECTED')
" | grep -q MD5_REJECTED \
|| test_fail "FIPS mode should reject MD5"
fips-allows-sha256:
filters:
variants: ["*fips*"]
command: |
test_engine_run --rm "${TEST_IMAGE:?}" python3 -c "
import hashlib
print(hashlib.sha256(b'test').hexdigest())
" | grep -q 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 \
|| test_fail "FIPS mode should allow SHA-256"
Host FIPS mode considerations:
Where possible, write tests without filters.fips so they validate
behavior on both FIPS and non-FIPS hosts. This supports the best-effort goal
of consistent behavior across host types. Use filters.fips: true only for
tests that inherently require a FIPS-enabled host kernel — primarily
NSS-based images where enforcement depends on the host:
# NSS-based: rejection only works on FIPS hosts
fips-rejects-blowfish:
filters:
variants: ["*fips*"]
fips: true # requires FIPS-enabled host kernel
command: |
...
See the Test Configuration Reference for variant filters and FIPS mode selection.
5. Build and Test
# Build the FIPS variant
ci/build_images.sh <image>/hummingbird/fips
# Run tests
ci/run_tests_container.sh <image>/hummingbird/fips
# Run linters
make check
Go-Specific FIPS Requirements
Go FIPS uses a completely different compiler toolchain from the standard Go variant:
- Package:
golang-fips1.25instead ofgolang1.25— a FIPS-patched Go build that uses OpenSSL as the cryptographic backend - Environment variable:
GOLANG_FIPS=1must be set in the Containerfile (via the Jinja2 template) - Version package override: Because the FIPS variant installs a
differently-named package,
version_packagetells the version/tag macros which package to resolve from:
main_package: golang1.25
version_package:
fips: golang-fips1.25
rpm_packages:
fips:
- crypto-policies-config-fips
- golang-fips1.25
- openssl-config-fips
Go-specific FIPS tests verify:
crypto/boring.Enabled()returnstrue(OpenSSL FIPS backend is active)GOEXPERIMENT=strictfipsruntimeblocksCGO_ENABLED=0binaries on FIPS hosts (this test usesfilters.fips: true)
See version_package in the Image Configuration Reference.
OpenJDK-Specific FIPS Requirements
OpenJDK uses the NSS stack instead of OpenSSL, which has several implications:
- No
openssl-config-fips— usesnss-softokn-fips+nss-softokn-freebl-fipsinstead - Host FIPS mode dependency — NSS checks
/proc/sys/crypto/fips_enabled, so FIPS enforcement behavior differs between FIPS and non-FIPS hosts. Crypto rejection tests must usefilters.fips: true. - Compound variants — OpenJDK introduces
runtime-fipsalongsidefips, combining theruntimebase variant with the FIPS modifier. Both share the same NSS FIPS packages;fipsadditionally includesjava-*-openjdk-devel. - Security provider — in FIPS mode, the primary Java security provider
becomes
SunPKCS11-NSS-FIPSinstead of the defaultSUN/SunJCEproviders.
Global FIPS Tests
The following tests run automatically for all FIPS images via
ci/global-tests/tests-container.yml. Each test skips gracefully for images
using the other FIPS stack.
| Test | Validates | Applies to |
|---|---|---|
fips-provider-matches-ubi9 |
OpenSSL FIPS module checksum matches RHEL 9.2 binaries | OpenSSL stack |
fips-nss-modules-match-rhel92 |
NSS module checksums match RHEL 9.2 validated binaries | NSS stack |
fips-no-libgcrypt |
libgcrypt is not present (not a validated module) |
All FIPS |
openssl-fips-config-installed |
OpenSSL FIPS drop-in config exists | OpenSSL stack |
crypto-policy-is-fips |
System crypto policy is set to FIPS |
All FIPS |
Image-specific FIPS tests (algorithm rejection/acceptance) are defined in each
image’s tests-container.yml.
Testing on FIPS Hosts
The test runner detects host FIPS mode from /proc/sys/crypto/fips_enabled
and exports the TEST_FIPS environment variable (true or false). Tests
can filter on this using filters.fips:
# Runs only on FIPS-enabled hosts
fips-host-required:
filters:
fips: true
command: |
...
# Runs only on non-FIPS hosts
non-fips-only:
filters:
fips: false
command: |
...
# Runs regardless of host FIPS mode (default when filters.fips is omitted)
always-runs:
command: |
...
OpenSSL-based images: Most FIPS tests run regardless of host FIPS mode, because the OpenSSL FIPS provider enforces algorithm restrictions at the container level.
NSS-based images: Crypto rejection tests require filters.fips: true,
because NSS enforcement depends on the host kernel’s FIPS mode.
Next Steps
- Image Configuration Reference —
additional_variants,rpm_packages,version_package - Test Configuration Reference — variant filters, FIPS mode selection
- Testing Guide — how to run and write tests