diff --git a/.config/make/lint-fmt.mak b/.config/make/lint-fmt.mak index f473845e2..46f659440 100644 --- a/.config/make/lint-fmt.mak +++ b/.config/make/lint-fmt.mak @@ -66,8 +66,8 @@ s3s-footprint-check: ## Check the s3s dependency footprint ratchet stays frozen ./scripts/check_s3s_footprint.sh .PHONY: fips-wording-check -fips-wording-check: ## Check outward docs do not make unsupported FIPS claims - @echo "📣 Checking FIPS wording guard..." +fips-wording-check: ## Check docs and crates/kms do not over-claim crypto capabilities + @echo "📣 Checking cryptographic capability wording guard..." ./scripts/check_fips_wording.sh .PHONY: log-analyzer-rules-check diff --git a/.github/workflows/ci-docs-only.yml b/.github/workflows/ci-docs-only.yml index 17eca6cba..a156a61b9 100644 --- a/.github/workflows/ci-docs-only.yml +++ b/.github/workflows/ci-docs-only.yml @@ -117,6 +117,9 @@ jobs: - name: Check s3s footprint ratchet run: ./scripts/check_s3s_footprint.sh + - name: Check cryptographic capability wording + run: ./scripts/check_fips_wording.sh + - name: Check no planning docs committed run: ./scripts/check_no_planning_docs.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1988cb54d..412145661 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -152,6 +152,9 @@ jobs: - name: Check s3s footprint ratchet run: ./scripts/check_s3s_footprint.sh + - name: Check cryptographic capability wording + run: ./scripts/check_fips_wording.sh + - name: Check no planning docs committed run: ./scripts/check_no_planning_docs.sh diff --git a/crates/kms/src/config.rs b/crates/kms/src/config.rs index bd6c4a0e1..6e8cc1d06 100644 --- a/crates/kms/src/config.rs +++ b/crates/kms/src/config.rs @@ -1729,30 +1729,10 @@ mod tests { assert!(config.validate().is_ok(), "deprecated mount_path must not be required"); } - #[test] - fn test_vault_kv2_sources_do_not_claim_transit_wrapping() { - let sources = [ - ("config.rs", include_str!("config.rs")), - ("api_types.rs", include_str!("api_types.rs")), - ("backends/vault.rs", include_str!("backends/vault.rs")), - ("lib.rs", include_str!("lib.rs")), - ]; - // Assemble the needles at runtime so this guard does not match its own source. - let needles = [ - format!("wrapping via {}", "Transit"), - format!("KV v2 + {}", "Transit"), - format!("KV2+{}", "Transit"), - format!("you would use Vault's {} engine", "transit"), - ]; - for (name, source) in sources { - for needle in &needles { - assert!( - !source.contains(needle.as_str()), - "{name} still describes the Vault KV2 backend with `{needle}`" - ); - } - } - } + // The "VaultKv2 must not claim Transit wrapping" documentation-claim + // invariant is enforced by scripts/check_fips_wording.sh, which scans every + // file in crates/kms rather than a fixed include_str! list + // (rustfs/backlog#1884). #[test] fn test_legacy_persisted_vault_transit_config_uses_metadata_defaults() { diff --git a/docs/operations/kms-cryptographic-compliance.md b/docs/operations/kms-cryptographic-compliance.md index c9ce1a4d4..08dd593e3 100644 --- a/docs/operations/kms-cryptographic-compliance.md +++ b/docs/operations/kms-cryptographic-compliance.md @@ -53,6 +53,8 @@ Suggested boilerplate when the topic cannot be avoided: `README.md` and `CHANGELOG.md` currently contain no FIPS-related wording; `scripts/check_fips_wording.sh` is the grep guard for that public baseline. Any future occurrence of the banned strings in either file should be treated as a defect and either removed or brought under the qualifier rule above. This document intentionally contains the terminology needed to define the policy and is not part of that narrow outward-material scan. +The same script carries a second block for the adjacent over-claim: no file under `crates/kms` may describe the Vault KV2 backend as wrapping key material through Vault's Transit engine. `KmsBackend::VaultKv2` stores RustFS-wrapped key material in Vault's KV v2 engine and never calls Transit, so that wording would tell an operator their key material is cryptographically isolated inside Vault when it is not. Use the `VaultTransit` backend when that isolation is the requirement. + ## The `rustfs-crypto` `fips` feature: what it actually does `crates/crypto/Cargo.toml` declares `default = ["crypto", "fips"]`, so the feature is on in every normal build. Its entire effect is **which algorithm the write path selects**; the implementation is RustCrypto either way. diff --git a/scripts/check_fips_wording.sh b/scripts/check_fips_wording.sh index 49027119c..6221f6d4f 100755 --- a/scripts/check_fips_wording.sh +++ b/scripts/check_fips_wording.sh @@ -1,10 +1,30 @@ #!/usr/bin/env bash set -euo pipefail -# Guard: outward README and CHANGELOG material must not make an unsupported -# FIPS validation or certification claim. The detailed policy and permitted -# qualifiers live in docs/operations/kms-cryptographic-compliance.md; this -# check intentionally scans only the two public project-facing documents. +# Guard: cryptographic capability wording must not over-claim what RustFS +# actually does. Two independent blocks, both anchored to the policy in +# docs/operations/kms-cryptographic-compliance.md: +# +# 1. Outward README and CHANGELOG material must not make an unsupported +# FIPS validation or certification claim. This block intentionally scans +# only the two public project-facing documents; the permitted qualifiers +# live in the policy document. +# +# 2. Nothing in crates/kms may describe the Vault KV2 backend as wrapping +# key material through Vault's Transit engine. `KmsBackend::VaultKv2` +# stores RustFS-wrapped key material in Vault's KV v2 engine and never +# calls Transit (see crates/kms/src/config.rs and +# docs/operations/kms-backend-security.md), so such prose tells operators +# their key material is cryptographically isolated inside Vault when it is +# not. +# +# Block 2 replaces the unit test `test_vault_kv2_sources_do_not_claim_transit_wrapping` +# that used to live in crates/kms/src/config.rs (rustfs/backlog#1884). The +# invariant is a documentation-claim invariant, so it has no behavioral twin by +# construction and belongs in a wording guard rather than in a test. The test +# could only see four `include_str!`-pinned files and stopped compiling — +# rather than reporting a violation — the moment one of them was renamed; this +# block scans every file in the crate and reports a rename explicitly. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="${CHECK_FIPS_WORDING_ROOT:-$(cd "${SCRIPT_DIR}/.." && pwd)}" @@ -21,12 +41,34 @@ FORBIDDEN_PATTERNS=( '(meets|satisfies)[[:space:]]+FIPS' ) -status=0 +KMS_CRATE_DIR="crates/kms" + +# The four files the retired unit test pinned with include_str!. They stay +# listed so that moving one out of crates/kms is reported here instead of +# silently shrinking the scan; the scan itself is not limited to them. +KMS_PINNED_SOURCES=( + "crates/kms/src/config.rs" + "crates/kms/src/api_types.rs" + "crates/kms/src/backends/vault.rs" + "crates/kms/src/lib.rs" +) + +# Literal, case-sensitive, and byte-for-byte the needles the retired test built +# at runtime via format!("wrapping via {}", "Transit") and friends. +KMS_VAULT_KV2_FORBIDDEN=( + 'wrapping via Transit' + 'KV v2 + Transit' + 'KV2+Transit' + "you would use Vault's transit engine" +) + +fips_status=0 +kms_status=0 for target in "${TARGETS[@]}"; do if [[ ! -f "$target" ]]; then printf 'FIPS wording guard failed: %s is missing\n' "$target" >&2 - status=1 + fips_status=1 continue fi @@ -35,14 +77,41 @@ for target in "${TARGETS[@]}"; do if [[ -n "$matches" ]]; then printf 'FIPS wording guard failed: forbidden pattern /%s/ in %s:\n%s\n' \ "$pattern" "$target" "$matches" >&2 - status=1 + fips_status=1 fi done done -if [[ "$status" -ne 0 ]]; then +for source in "${KMS_PINNED_SOURCES[@]}"; do + if [[ ! -f "$source" ]]; then + printf 'KMS wording guard failed: %s is missing; update KMS_PINNED_SOURCES in scripts/check_fips_wording.sh after moving it\n' \ + "$source" >&2 + kms_status=1 + fi +done + +if [[ -d "$KMS_CRATE_DIR" ]]; then + for pattern in "${KMS_VAULT_KV2_FORBIDDEN[@]}"; do + matches="$(grep -r -F -n -- "$pattern" "$KMS_CRATE_DIR" || true)" + if [[ -n "$matches" ]]; then + printf 'KMS wording guard failed: forbidden Vault KV2 claim "%s" in %s:\n%s\n' \ + "$pattern" "$KMS_CRATE_DIR" "$matches" >&2 + kms_status=1 + fi + done +fi + +if [[ "$fips_status" -ne 0 ]]; then printf 'Remove unsupported FIPS validation wording from README.md or CHANGELOG.md.\n' >&2 - exit "$status" +fi + +if [[ "$kms_status" -ne 0 ]]; then + printf 'The Vault KV2 backend does not wrap key material through Vault Transit; fix the wording in crates/kms.\n' >&2 +fi + +if [[ "$fips_status" -ne 0 || "$kms_status" -ne 0 ]]; then + exit 1 fi printf 'FIPS wording guard passed (README.md and CHANGELOG.md contain no forbidden claims).\n' +printf 'KMS wording guard passed (crates/kms claims no Vault KV2 Transit wrapping).\n'