diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 8dcaf052..d377a215 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -261,6 +261,15 @@ jobs: source ~/venv/qa/bin/activate export CLICKHOUSE_TESTS_DIR=$(pwd)/test/testflows/clickhouse_backup ./test/testflows/run.sh + - name: Report FIPS testflows coverage + uses: coverallsapp/github-action@v2 + with: + fail-on-error: false + base-path: ./ + file: test/testflows/_coverage_/coverage.out + parallel: true + format: golang + flag-name: testflows-${{ matrix.clickhouse }} - name: Fix FIPS log permissions for artifact upload if: always() @@ -414,6 +423,7 @@ jobs: needs: - test - testflows + - testflows_fips name: coverage runs-on: ubuntu-24.04 steps: diff --git a/test/integration/containers.go b/test/integration/containers.go index 112063ac..210a1743 100644 --- a/test/integration/containers.go +++ b/test/integration/containers.go @@ -324,7 +324,7 @@ func (tc *TestContainers) RestartContainer(t *testing.T, name string) error { if err := tc.client.ContainerRestart(ctx, info.ID, container.StopOptions{Timeout: &timeout}); err != nil { return err } - return tc.waitHealthy(ctx, name, 10*time.Minute, t.Name()) + return tc.waitHealthy(ctx, name, 12*time.Minute, t.Name()) } func (tc *TestContainers) waitHealthy(ctx context.Context, name string, timeout time.Duration, testName string) error { diff --git a/test/testflows/clickhouse_backup/configs/clickhouse_nonfips_server/config.d/listeners-fips-cipher-stress.xml b/test/testflows/clickhouse_backup/configs/clickhouse_nonfips_server/config.d/listeners-fips-cipher-stress.xml new file mode 100644 index 00000000..4fa24d7b --- /dev/null +++ b/test/testflows/clickhouse_backup/configs/clickhouse_nonfips_server/config.d/listeners-fips-cipher-stress.xml @@ -0,0 +1,32 @@ + + + 9440 + + + /etc/clickhouse-server/ssl/server.crt + /etc/clickhouse-server/ssl/server.key + ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384 + TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384 + true + sslv2,sslv3,tlsv1,tlsv1_1 + relaxed + + + diff --git a/test/testflows/clickhouse_backup/regression.py b/test/testflows/clickhouse_backup/regression.py index 2db9447c..39311965 100755 --- a/test/testflows/clickhouse_backup/regression.py +++ b/test/testflows/clickhouse_backup/regression.py @@ -18,9 +18,7 @@ from clickhouse_backup.requirements.requirements import * -from clickhouse_backup.requirements.fips.requirements import ( - QA_SRS013_ClickHouse_Backup_Utility_FIPS_Compatibility, -) +from clickhouse_backup.requirements.fips.requirements import * from clickhouse_backup.tests.common import simple_data_types_columns # `--fips-godebug` choices mapped to the `GODEBUG` value exported on @@ -31,6 +29,7 @@ # * `only` - FIPS active with strict enforcement (default). # * `off` - FIPS disabled at runtime. FIPS_GODEBUG_VALUES = { + "empty": "", "unset": None, "on": "fips140=on", "only": "fips140=only", @@ -105,6 +104,9 @@ def regression(self, local, stress=False, fips=True, fips_godebug="only"): self.context.backup_config_origin = origin_path self.context.backup_config_file = config_path self.context.cluster = cluster + # `--stress` widens the FIPS cipher/suite coverage (see tests/fips_140_3.py). + # Default runs keep the documented minimum so they stay fast. + self.context.stress = stress self.context.nodes = [self.context.cluster.node(n) for n in ["clickhouse1", "clickhouse2"]] self.context.backup = self.context.cluster.node("clickhouse_backup") self.context.kafka = self.context.cluster.node("kafka") diff --git a/test/testflows/clickhouse_backup/requirements/fips/QA_STP_ClickHouse_Backup_FIPS.md b/test/testflows/clickhouse_backup/requirements/fips/QA_STP_ClickHouse_Backup_FIPS.md index a2e72df9..7b2347a3 100644 --- a/test/testflows/clickhouse_backup/requirements/fips/QA_STP_ClickHouse_Backup_FIPS.md +++ b/test/testflows/clickhouse_backup/requirements/fips/QA_STP_ClickHouse_Backup_FIPS.md @@ -12,7 +12,9 @@ * 1 [Introduction](#introduction) * 2 [Timeline](#timeline) * 3 [Configuration Requirements](#configuration-requirements) + * 3.1 [Supported TLS Protocol Versions and Cipher Suites](#supported-tls-protocol-versions-and-cipher-suites) * 4 [Build Verification](#build-verification) + * 4.1 [Pre-Publish Image Verification](#pre-publish-image-verification) * 5 [Human Resources And Assignments](#human-resources-and-assignments) * 6 [Release Notes](#release-notes) * 7 [FIPS-Compatible `clickhouse-backup-fips` Configuration](#fips-compatible-clickhouse-backup-fips-configuration) @@ -43,7 +45,7 @@ Test results ensure that `clickhouse-backup`: To validate this, the following items SHALL be checked: -* The FIPS-built `clickhouse-backup` binary starts with the Go FIPS 140-3 cryptographic module enabled and reports it in `--version` output under all three Go FIPS runtime modes (`GODEBUG` unset, `GODEBUG=fips140=on`, `GODEBUG=fips140=only`). +* The FIPS-built `clickhouse-backup` binary reports the correct FIPS posture (`enabled` / `enforced`) in `clickhouse-backup-fips --fips-info` output under every Go FIPS runtime mode (`GODEBUG` unset, empty, `fips140=off`, `fips140=on`, `fips140=only`). * The FIPS cipher policy is enforced for inbound and outbound TLS when running in strict mode (`GODEBUG=fips140=only`). * The binary aborts on startup if the FIPS integrity check or any startup cryptographic self-test fails. * The binary stays operational against both FIPS-compatible and non-FIPS-compatible ClickHouse server versions. @@ -72,9 +74,35 @@ For TLS policy validation, the test suite also uses OpenSSL probe tools: - `openssl s_client` (acts as a TLS client to test inbound API listener policy) - `openssl s_server` (acts as a TLS server to test outbound client policy) +### Supported TLS Protocol Versions and Cipher Suites + +All TLS endpoints of `clickhouse-backup-fips` (the inbound REST API listener and the outbound clients to ClickHouse and S3) follow a single FIPS profile. This profile is what every TLS scenario in this plan asserts. + +**Protocol versions:** + +| Protocol | Status | +| -------- | ------ | +| TLSv1.3 | Supported | +| TLSv1.2 | Supported | +| TLSv1.1 | Rejected (below FIPS minimum) | +| TLSv1.0 | Rejected (below FIPS minimum) | +| SSLv2 / SSLv3 | Rejected | + +**FIPS-approved cipher suites (the only ones that may negotiate):** + +| TLS version | Approved suites | +| ----------- | --------------- | +| TLSv1.3 | `TLS_AES_128_GCM_SHA256`, `TLS_AES_256_GCM_SHA384` | +| TLSv1.2 | `ECDHE-RSA-AES128-GCM-SHA256`, `ECDHE-RSA-AES256-GCM-SHA384` | + +Everything else MUST be rejected — including ChaCha20-Poly1305, CBC-mode, RC4, 3DES, CCM, DHE / plain-RSA key exchange, and the plain RSA-kx AES-GCM suites (`AES128-GCM-SHA256`, `AES256-GCM-SHA384`). + +> [!NOTE] +> The client cipher set above is narrower than the ClickHouse server `` in the [Altinity FIPS documentation](https://docs.altinity.com/altinitystablebuilds/fips-compatible-altinity-builds/#configuration-of-altinity-stable-builds-for-fips-compatible-operation): that server config also permits the non-ECDHE `AES128-GCM-SHA256` / `AES256-GCM-SHA384` suites, but `clickhouse-backup-fips` rejects them because the Go FIPS module approves only the forward-secret ECDHE AES-GCM suites listed above. + ## Build Verification -**Objective:** Verify binaries are FIPS builds and linked to Go Cryptographic Module v1.0.0. +**Objective:** Verify that `clickhouse-backup-fips` is a FIPS build linked to the Go Cryptographic Module v1.0.0 (reports `FIPS 140-3: true`, built with `GOFIPS140=v1.0.0`), and that the regular `clickhouse-backup` binary is not (reports `FIPS 140-3: false`). **Certificates:** - [CMVP #5247](https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/5247) @@ -83,11 +111,24 @@ For TLS policy validation, the test suite also uses OpenSSL probe tools: | Test Assertion | Description | Expected Result | | --- | --- | --- | -| FIPS indicator in binary version output | Run `clickhouse-backup-fips --version` (`clickhouse_backup_fips_version_output`) and run control check on non-FIPS binary (`clickhouse_backup_fips_version_output_negative_check`) | FIPS binary reports `FIPS 140-3: true`; non-FIPS binary does not report `true` | +| FIPS indicator in FIPS binary version output | Run `clickhouse-backup-fips --version` (`clickhouse_backup_fips_version_output`) | FIPS binary reports `FIPS 140-3: true` | +| FIPS indicator absent in non-FIPS binary (control check) | Run `clickhouse-backup --version` on the regular binary (`clickhouse_backup_fips_version_output_negative_check`) | Non-FIPS binary does not report `FIPS 140-3: true` (reports `false`) | | Build flag | Run `go version -m clickhouse-backup-fips` (`gofips140_build_flags_present`) | Output contains `build GOFIPS140=v1.0.0` | -| FIPS runtime behavior across Go modes | Run `godebug_fips140_modes` with `GODEBUG` unset, `fips140=on`, and `fips140=only` | For each mode, `--version` reports `FIPS 140-3: true`, and `tables` against the FIPS ClickHouse TLS endpoint succeeds (`exit 0`) | +| FIPS runtime posture across Go modes | Run `godebug_fips140_modes`, which runs `clickhouse-backup-fips --fips-info` with `GODEBUG` unset, empty, `fips140=off`, `fips140=on`, and `fips140=only` | For each mode, `--fips-info` reports the expected `enabled` / `enforced` flags (unset/empty/on → `true`/`false`; off → `false`/`false`; only → `true`/`true`) | + +Direct checks of `crypto/fips140.Version()` and `crypto/fips140.Enabled()` are not called as standalone assertions in the current `clickhouse-backup` TestFlows scenarios; their behavior is validated through `--version` and `--info` outputs and runtime connectivity checks above. -Direct checks of `crypto/fips140.Version()` and `crypto/fips140.Enabled()` are not called as standalone assertions in the current `clickhouse-backup` TestFlows scenarios; their behavior is validated through `--version` output and runtime connectivity checks above. +### Pre-Publish Image Verification + +In addition to the TestFlows scenarios above, the same FIPS build posture is enforced automatically in CI before any FIPS Docker image is published to the registry (Docker Hub). The `Verify FIPS 140-3 compatibility before push` step in both `.github/workflows/build.yaml` and `.github/workflows/release.yaml` builds the exact `image_fips` target locally and runs `.github/scripts/verify_fips_image.sh` against that image and the `clickhouse-backup-fips` binary. If any check fails the workflow stops, so a non-FIPS or mis-built image can never be pushed. + +This is an automation/release gate: it re-uses the same expectations already covered by the assertions in this section (no new requirement is introduced). The script verifies: + +| Check | Performed against | Expected Result | +| --- | --- | --- | +| Baked-in environment | `docker image inspect` of the `image_fips` image | `Config.Env` contains `GODEBUG=fips140=only` | +| Version FIPS indicator | `clickhouse-backup --version` run inside the image | Output contains `FIPS 140-3: true` | +| Build metadata | `go version -m clickhouse-backup-fips` (binary copied out of the image when `--binary` is omitted) | Reports `GOFIPS140=v1.0.0` (or a `v1.0.0-` snapshot), the `fips140v1.0` build tag, `DefaultGODEBUG=fips140=on` and `CGO_ENABLED=0` | ## Human Resources And Assignments @@ -135,7 +176,12 @@ The following artifacts and tools will be used: * `openssl` CLI tool on the test host for TLS client and server probes. > [!NOTE] -> The regression sets `GODEBUG` per command rather than at the FIPS container level. The suite covers all three modes documented in [GODEBUG fips140 Modes](#godebug-fips140-modes) (`unset`, `fips140=on`, `fips140=only`), and the forced-CAST scenario also injects `GODEBUG=failfipscast=,fips140=on`; a single container-level value would prevent the matrix and the negative-self-test path from running. The Altinity FIPS Docker image still ships with `GODEBUG=fips140=only` as documented in [FIPS Configuration](#fips-configuration); that default is honored when the image is run as-is. +> The FIPS backup container exports `GODEBUG` at the container level (the regression `--fips-godebug` option, default `fips140=only`), so it applies to every command by default. Two scenarios override `GODEBUG` per command because they require other values: +> +> * [GODEBUG `fips140` Modes](#godebug-fips140-modes) (`godebug_fips140_modes`) runs `--fips-info` under `GODEBUG` unset, empty, `fips140=off`, `fips140=on`, and `fips140=only`. +> * [Forced CAST Failures](#forced-cast-failures) (`forced_cast_failures`) runs `--version` under `GODEBUG=failfipscast=,fips140=only`. +> +> The Altinity FIPS Docker image ships with `GODEBUG=fips140=only` baked in (see [FIPS-Compatible `clickhouse-backup-fips` Configuration](#fips-compatible-clickhouse-backup-fips-configuration)); that default applies when the image is run as-is. ## Inputs and Outputs of `clickhouse-backup-fips` @@ -145,7 +191,7 @@ The following artifacts and tools will be used: * Outbound to ClickHouse: secure native TCP port `9440` (`clickhouse.secure: true`, `clickhouse.port: 9440`). Plain native TCP `9000` and plain HTTP `8123` MUST NOT be used by `clickhouse-backup-fips`. * Outbound to S3-compatible storage: HTTPS to the AWS FIPS hostname `s3-fips..amazonaws.com:443` when `s3.endpoint` is empty and `s3.region` is set. -The [Server Listening-Port Assertion](#server-listening-port-assertion) subsection below describes how the inbound surface is verified. +The [Server Listening-Port Assertion](#server-listening-port-assertion) section describes how the inbound surface is verified. ## Connectivity Against ClickHouse FIPS and Non-FIPS Servers @@ -181,98 +227,101 @@ Expected result: ## GODEBUG `fips140` Modes -Check that `clickhouse-backup-fips` behaves correctly under each of the three Go FIPS runtime modes listed below. - -For every mode run both `--version` and a basic `tables` command against the FIPS-compatible Altinity ClickHouse server `altinity/clickhouse-server:25.3.8.30001.altinityfips`. - -* `GODEBUG` not set — FIPS mode is enabled by build-time default (`GOFIPS140=v1.0.0`). +Check that `clickhouse-backup-fips` reports the correct FIPS posture under every Go FIPS `fips140` runtime mode. - Expected result: - * `--version` reports `FIPS 140-3: true`. - * `tables` returns the list of tables. +The binary exposes its build/runtime posture via `clickhouse-backup-fips --fips-info`, which prints a line-oriented `key: value` dump including, under the `fips_module:` block, `enabled:` and `enforced:` booleans (these map to Go's `crypto/fips140.Enabled()` and `crypto/fips140.Enforced()`). The binary is built with `DefaultGODEBUG=fips140=on`, so leaving `GODEBUG` unset (or empty) keeps FIPS enabled but not enforced. -* `GODEBUG=fips140=on` — FIPS mode is enabled explicitly without strict enforcement. This is the mode used for the forced CAST test below. +For each mode, run `clickhouse-backup-fips --fips-info` with the corresponding `GODEBUG` value and assert the reported `enabled` / `enforced` flags match the table below (scenario `godebug_fips140_modes`): - Expected result: - * `--version` reports `FIPS 140-3: true`. - * `tables` returns the list of tables. +| `GODEBUG` runtime | `enabled` | `enforced` | Notes | +| -------------------- | --------- | ---------- | ----- | +| unset | true | false | Build-time default (`DefaultGODEBUG=fips140=on`). | +| empty (`GODEBUG=`) | true | false | Same as unset. | +| `fips140=off` | false | false | FIPS disabled. | +| `fips140=on` | true | false | FIPS enabled, not enforced. Mode used for the forced CAST test below. | +| `fips140=only` | true | true | Strict enforcement; any non-approved cryptographic operation triggers an error or panic. Mode used for the TLS policy tests below and the default of the FIPS Docker image. | -* `GODEBUG=fips140=only` — FIPS mode is enabled with strict enforcement; any non-approved cryptographic operation triggers an error or panic. This is the mode used for the TLS policy tests below and the default of the FIPS Docker image. +To set each case explicitly (independent of any container-level `GODEBUG`): - Expected result: - * `--version` reports `FIPS 140-3: true`. - * `tables` against an approved TLS configuration returns the list of tables. - * Non-approved cryptographic operations cause the binary to fail. - * The full `clickhouse-backup` TestFlows regression suite runs in this mode without panics or strict-FIPS-only regressions. +* unset: `env -u GODEBUG clickhouse-backup-fips --fips-info` +* empty: `env GODEBUG= clickhouse-backup-fips --fips-info` +* off / on / only: `env GODEBUG=fips140= clickhouse-backup-fips --fips-info` > [!NOTE] -> No negative test exists for "the binary panics when `GODEBUG` is unset". `clickhouse-backup-fips` is built with `GOFIPS140=v1.0.0`, so the FIPS module is enabled by the build flag, not by `GODEBUG`. The "GODEBUG not set" mode above IS the production-default operation; the binary is expected to operate normally there. +> No negative test exists for "the binary panics when `GODEBUG` is unset". `clickhouse-backup-fips` is built with `GOFIPS140=v1.0.0`, so the FIPS module is enabled by the build flag (`DefaultGODEBUG=fips140=on`), not by the runtime `GODEBUG`. The "GODEBUG unset" mode above IS the production-default operation; the binary is expected to operate normally there. ## FIPS Integrity Self-test Failure on Tampered Binary Check that the FIPS startup integrity self-test stops the binary if the FIPS module bytes have been modified. -Take a copy of `clickhouse-backup-fips`, corrupt its `.go.fipsinfo` checksum section, and try to run the tampered copy. +Take a copy of `clickhouse-backup-fips`, corrupt its `.go.fipsinfo` checksum section, and try to run the tampered copy. The scenario (`fips_integrity_self_test_failure_on_tampered_binary`) runs `scripts/tamper_go_fips_checksum.sh`, which operates only on a temporary copy of the read-only original binary, so other scenarios are unaffected. Expected result: * The tampered binary panics on startup with `panic: fips140: verification mismatch` and exits with a non-zero exit code. -* The unmodified original binary continues to work normally. +* The tamper script prints its explicit success marker `== OK: FIPS integrity check failed as expected ==` and exits `0` (its success contract). +* The unmodified original binary continues to work normally (the script tampers only the copy). ## Forced CAST Failures +Check that the FIPS module aborts when a Cryptographic Algorithm Self-Test (CAST) fails. The Go FIPS module exposes a `GODEBUG=failfipscast=` hook that simulates a CAST failure for one named self-test. -Check that the FIPS module refuses to start if any startup self-test fails. - -Run the FIPS binary with the `GODEBUG=failfipscast` hook, substituting one self-test name at a time, for example: +The scenario `forced_cast_failures` forces one CAST at a time, for example: ``` -GODEBUG=failfipscast=SHA2-256,fips140=on clickhouse-backup-fips --version +env 'GODEBUG=failfipscast=SHA2-256,fips140=only' clickhouse-backup-fips --version ``` -`SHA2-256` in the command above can be replaced with any effective CAST name from the list below: +CASTs fall into two groups, and the behavior differs by group — the scenario asserts each accordingly: + +* **Startup CASTs** — always exercised during `clickhouse-backup-fips --version`. Forcing one MUST abort startup. +* **First-use CASTs** — run lazily, only the first time their algorithm is used. `--version` does not necessarily reach them, so forcing one is only expected to abort if and when it is actually exercised; otherwise startup stays clean. + +Startup CASTs (forcing any one MUST abort `--version`): ``` AES-CBC CTR_DRBG CounterKDF +HKDF-SHA2-256 +HMAC-SHA2-256 +PBKDF2 +SHA2-256 +SHA2-512 +TLSv1.2-SHA2-256 +TLSv1.3-SHA2-256 +cSHAKE128 +``` + +First-use CASTs (forcing one aborts only if `--version` happens to exercise that algorithm; otherwise startup stays clean): + +``` DetECDSA P-256 SHA2-512 sign ECDH PCT ECDSA P-256 SHA2-512 sign and verify ECDSA PCT Ed25519 sign and verify Ed25519 sign and verify PCT -HKDF-SHA2-256 -HMAC-SHA2-256 KAS-ECC-SSC P-256 ML-DSA sign and verify PCT ML-DSA-44 ML-KEM PCT -ML-KEM PCT ML-KEM-768 -PBKDF2 RSA sign and verify PCT RSASSA-PKCS-v1.5 2048-bit sign and verify -SHA2-256 -SHA2-512 -TLSv1.2-SHA2-256 -TLSv1.3-SHA2-256 -cSHAKE128 ``` -The list is taken directly from the Go FIPS test suite (file `crypto/internal/fips140test/cast_test.go` of the Go release in use). +The names are taken directly from the Go FIPS test suite (the `allCASTs` slice in `crypto/internal/fips140test/cast_test.go` of the Go release in use). -Expected result for every name in the list: - -* Baseline run with `GODEBUG=fips140=on clickhouse-backup-fips --version` succeeds. -* The process exits with a non-zero code. -* The output contains `fatal error: FIPS 140-3 self-test failed: : simulated CAST failure`. +Expected result: -How to obtain and refresh this list: +* **For every startup CAST:** the process exits with a non-zero code and the output contains both `self-test failed: ` and `simulated CAST failure` (Go emits the full line `fatal error: FIPS 140-3 self-test failed: : simulated CAST failure`). +* **For every first-use CAST:** either the same abort markers appear (if the algorithm was exercised), or — when the algorithm is not reached by `--version` — startup stays clean: the process exits `0` and the `simulated CAST failure` marker is absent. -* Open `$(go env GOROOT)/src/crypto/internal/fips140test/cast_test.go` and copy the entries from the `allCASTs` slice. +How to obtain and refresh this list: +* Open `$(go env GOROOT)/src/crypto/internal/fips140test/cast_test.go` and copy the entries from the `allCASTs` slice. Each entry's group (startup vs first-use) follows how Go registers it — keep `FIPS_FAILFIPSCAST_STARTUP_CASTS` and `FIPS_FAILFIPSCAST_FIRST_USE_CASTS` in `tests/fips_140_3.py` in sync. * The list should be refreshed when the Go version used to build `clickhouse-backup-fips` is upgraded, because new algorithms may add/rename/remove entries. ## Inbound TLS — REST API With `openssl s_client` @@ -299,6 +348,8 @@ Non-FIPS profiles (handshake MUST be rejected): * `openssl s_client -tls1` — expected result: handshake is rejected (TLSv1.0 is below the FIPS minimum protocol version). * `openssl s_client -tls1_1` — expected result: handshake is rejected (TLSv1.1 is below the FIPS minimum protocol version). +Additional non-FIPS TLSv1.2 ciphers exercised only under `--stress` (all MUST be rejected): `ECDHE-ECDSA-CHACHA20-POLY1305`, `DHE-RSA-CHACHA20-POLY1305`, `ECDHE-RSA-AES128-SHA`, `ECDHE-RSA-AES256-SHA`, `ECDHE-RSA-AES128-SHA256`, `ECDHE-RSA-AES256-SHA384`, `AES128-SHA`, `AES256-SHA`, `AES128-SHA256`, `AES256-SHA256` (RC4-SHA / DES-CBC3-SHA above are also kept). Additional non-FIPS TLSv1.3 suites under `--stress`: `TLS_AES_128_CCM_SHA256`, `TLS_AES_128_CCM_8_SHA256`. The default run probes the minimum set above; the wider `--stress` coverage is slower by design. + ## Outbound TLS to ClickHouse Server With `openssl s_server` @@ -318,10 +369,15 @@ Non-FIPS profiles (handshake MUST be rejected): * `openssl s_server -tls1_2 -cipher ECDHE-RSA-CHACHA20-POLY1305` — expected result: `clickhouse-backup-fips` fails with `remote error: tls: handshake failure` and `openssl s_server` reports `no shared cipher`. * `openssl s_server -tls1_2 -cipher DHE-RSA-AES256-GCM-SHA384` — expected result: handshake is rejected as above. * `openssl s_server -tls1_2 -cipher DHE-RSA-AES128-GCM-SHA256` — expected result: handshake is rejected as above. -* `openssl s_server -tls1_2 -cipher AES256-GCM-SHA384` — expected result: handshake is rejected as above. -* `openssl s_server -tls1_2 -cipher AES128-GCM-SHA256` — expected result: handshake is rejected as above. +* `openssl s_server -tls1_2 -cipher AES256-GCM-SHA384` — expected result: handshake is rejected as above (plain RSA key exchange, no forward secrecy). +* `openssl s_server -tls1_2 -cipher AES128-GCM-SHA256` — expected result: handshake is rejected as above (plain RSA key exchange, no forward secrecy). * `openssl s_server -tls1_3 -ciphersuites TLS_CHACHA20_POLY1305_SHA256` — expected result: handshake is rejected as above. +Additional non-FIPS TLSv1.2 ciphers exercised only under `--stress` (all MUST be rejected): `ECDHE-ECDSA-CHACHA20-POLY1305`, `DHE-RSA-CHACHA20-POLY1305`, `ECDHE-RSA-AES128-SHA`, `ECDHE-RSA-AES256-SHA`, `ECDHE-RSA-AES128-SHA256`, `ECDHE-RSA-AES256-SHA384`, `AES128-SHA`, `AES256-SHA`, `AES128-SHA256`, `AES256-SHA256`. Additional non-FIPS TLSv1.3 suites under `--stress`: `TLS_AES_128_CCM_SHA256`, `TLS_AES_128_CCM_8_SHA256`. The default run probes the minimum set above; the wider `--stress` coverage is slower by design. + +> [!NOTE] +> `AES128-GCM-SHA256` and `AES256-GCM-SHA384` appear in the Altinity ClickHouse server's `` (server side) but are rejected here by the `clickhouse-backup-fips` **client**. The Go FIPS cryptographic module approves a narrower TLSv1.2 set than the ClickHouse OpenSSL configuration: only ECDHE forward-secret AES-GCM suites (`ECDHE-RSA-AES128-GCM-SHA256`, `ECDHE-RSA-AES256-GCM-SHA384`) are approved on the client; plain RSA key-exchange suites are not. See [Supported TLS Protocol Versions and Cipher Suites](#supported-tls-protocol-versions-and-cipher-suites). + ## Outbound TLS to S3 Endpoint With `openssl s_server` @@ -341,6 +397,17 @@ FIPS-approved profiles (handshake MUST be accepted by `clickhouse-backup-fips` p * `openssl s_server -tls1_3 -ciphersuites TLS_AES_128_GCM_SHA256` — expected result: same as above. * `openssl s_server -tls1_3 -ciphersuites TLS_AES_256_GCM_SHA384` — expected result: same as above. +Non-FIPS profiles (handshake MUST be rejected by `clickhouse-backup-fips` policy): + +* `openssl s_server -tls1_3 -ciphersuites TLS_CHACHA20_POLY1305_SHA256` — expected result: the FIPS client refuses the handshake (`remote error: tls: handshake failure` / `no shared cipher`). +* `openssl s_server -tls1_2 -cipher ECDHE-RSA-CHACHA20-POLY1305` — expected result: rejected as above. +* `openssl s_server -tls1_2 -cipher DHE-RSA-AES256-GCM-SHA384` — expected result: rejected as above. +* `openssl s_server -tls1_2 -cipher DHE-RSA-AES128-GCM-SHA256` — expected result: rejected as above. +* `openssl s_server -tls1_2 -cipher AES256-GCM-SHA384` — expected result: rejected as above. +* `openssl s_server -tls1_2 -cipher AES128-GCM-SHA256` — expected result: rejected as above. + +The same `--stress` extension as the ClickHouse outbound section applies (the wider TLSv1.2 CBC / ChaCha20 and TLSv1.3 CCM sets). For the S3 probes only, if the AWS FIPS hostname resolves to public AWS instead of the local `openssl s_server` sidecar, a remote AWS auth error (e.g. `InvalidAccessKeyId`) with no TLS-rejection marker skips the negative check rather than failing it. + ## ACVP Tests Run the ACVP (Automated Cryptographic Validation Protocol) wrapper bundled with `clickhouse-backup`. This part is required for FIPS compatibity, but tests can be executed optionally. @@ -350,7 +417,7 @@ Invoke `pkg/acvpwrapper/run.sh` against the FIPS-built binary. Expected result: * The wrapper runs the algorithm test vectors and exits successfully with no failures across the run. -* This check is optional in automation and runs only when `RUN_ACVP_TESTS=1` is set. +* This check is optional in automation and runs only when `RUN_ACVP_TESTS=1` is set or when tests are being run is --stress mode. ## Server Listening-Port Assertion diff --git a/test/testflows/clickhouse_backup/requirements/fips/requirements.md b/test/testflows/clickhouse_backup/requirements/fips/requirements.md index c91c6213..c558dc11 100644 --- a/test/testflows/clickhouse_backup/requirements/fips/requirements.md +++ b/test/testflows/clickhouse_backup/requirements/fips/requirements.md @@ -18,9 +18,10 @@ * 4.1.1 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GoCryptographicModule](#rqsrs-013clickhousebackuputilityfipsgocryptographicmodule) * 4.1.2 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Build.GOFIPS140](#rqsrs-013clickhousebackuputilityfipsbuildgofips140) * 4.1.3 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Binary](#rqsrs-013clickhousebackuputilityfipsbinary) - * 4.1.4 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.TLSProtocolVersions](#rqsrs-013clickhousebackuputilityfipsapprovedtlsprotocolversions) - * 4.1.5 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.CipherSuites.TLSv12.Approved](#rqsrs-013clickhousebackuputilityfipsapprovedciphersuitestlsv12approved) - * 4.1.6 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.CipherSuites.TLSv13.Approved](#rqsrs-013clickhousebackuputilityfipsapprovedciphersuitestlsv13approved) + * 4.1.4 [RQ.SRS-013.ClickHouse.BackupUtility.non-FIPS.Binary](#rqsrs-013clickhousebackuputilitynon-fipsbinary) + * 4.1.5 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.TLSProtocolVersions](#rqsrs-013clickhousebackuputilityfipsapprovedtlsprotocolversions) + * 4.1.6 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.CipherSuites.TLSv12.Approved](#rqsrs-013clickhousebackuputilityfipsapprovedciphersuitestlsv12approved) + * 4.1.7 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.CipherSuites.TLSv13.Approved](#rqsrs-013clickhousebackuputilityfipsapprovedciphersuitestlsv13approved) * 4.2 [Connectivity](#connectivity) * 4.2.1 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Connectivity.FIPSEndpoint](#rqsrs-013clickhousebackuputilityfipsconnectivityfipsendpoint) * 4.2.2 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Connectivity.NonFIPSEndpoint](#rqsrs-013clickhousebackuputilityfipsconnectivitynonfipsendpoint) @@ -29,8 +30,10 @@ * 4.3.2 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Version.BuildSetting](#rqsrs-013clickhousebackuputilityfipsversionbuildsetting) * 4.4 [GODEBUG fips140 Modes](#godebug-fips140-modes) * 4.4.1 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Unset](#rqsrs-013clickhousebackuputilityfipsgodebugunset) - * 4.4.2 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.On](#rqsrs-013clickhousebackuputilityfipsgodebugon) - * 4.4.3 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Only](#rqsrs-013clickhousebackuputilityfipsgodebugonly) + * 4.4.2 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Empty](#rqsrs-013clickhousebackuputilityfipsgodebugempty) + * 4.4.3 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Off](#rqsrs-013clickhousebackuputilityfipsgodebugoff) + * 4.4.4 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.On](#rqsrs-013clickhousebackuputilityfipsgodebugon) + * 4.4.5 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Only](#rqsrs-013clickhousebackuputilityfipsgodebugonly) * 4.5 [Startup Integrity Self-Tests](#startup-integrity-self-tests) * 4.5.1 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.SelfTest.Integrity](#rqsrs-013clickhousebackuputilityfipsselftestintegrity) * 4.5.2 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.SelfTest.TamperedBinary](#rqsrs-013clickhousebackuputilityfipsselftesttamperedbinary) @@ -125,6 +128,12 @@ version: 1.0 The FIPS-compatible build of the [clickhouse-backup] utility SHALL be distributed as a separate binary named `clickhouse-backup-fips`, distinct from the standard `clickhouse-backup` binary. +#### RQ.SRS-013.ClickHouse.BackupUtility.non-FIPS.Binary +version: 1.0 + +The regular build of the [clickhouse-backup] utility SHALL be distributed as a +binary named `clickhouse-backup`, that SHALL report ``FIPS 140-3: false`` + #### RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.TLSProtocolVersions version: 1.0 @@ -190,29 +199,56 @@ The output of `go version -m $(which clickhouse-backup-fips)` SHALL contain the ### GODEBUG fips140 Modes +The [clickhouse-backup-fips] binary SHALL expose its FIPS build and runtime posture via +`clickhouse-backup-fips --fips-info`, which prints a line-oriented `key: value` dump including, +under the `fips_module:` block, `enabled: ` and `enforced: `. The binary +is built with `DefaultGODEBUG=fips140=on`, so the `fips140` runtime key SHALL produce the +following posture: + +| `GODEBUG` runtime | `enabled` | `enforced` | +| ----------------- | --------- | ---------- | +| unset | true | false | +| empty (`GODEBUG=`)| true | false | +| `fips140=off` | false | false | +| `fips140=on` | true | false | +| `fips140=only` | true | true | + #### RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Unset version: 1.0 -When `GODEBUG` is not set, the [clickhouse-backup-fips] binary SHALL operate with FIPS 140-3 mode -enabled by build-time default, `--version` SHALL report `FIPS 140-3: true`, and the basic -`clickhouse-backup-fips tables` command SHALL return the list of tables from a FIPS-configured -ClickHouse endpoint. +When `GODEBUG` is not set, the [clickhouse-backup-fips] binary SHALL rely on its build-time default +(`DefaultGODEBUG=fips140=on`) and operate with FIPS 140-3 mode enabled but not enforced. The +output of `clickhouse-backup-fips --fips-info` SHALL report `enabled: true` and `enforced: false`. + +#### RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Empty +version: 1.0 + +When started with an empty `GODEBUG` (i.e. `GODEBUG=`), the [clickhouse-backup-fips] binary SHALL +behave identically to the unset case, relying on its build-time default (`fips140=on`) with +FIPS 140-3 mode enabled but not enforced. The output of `clickhouse-backup-fips --fips-info` +SHALL report `enabled: true` and `enforced: false`. + +#### RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Off +version: 1.0 + +When started with `GODEBUG=fips140=off`, the [clickhouse-backup-fips] binary SHALL disable +FIPS 140-3 mode. The output of `clickhouse-backup-fips --fips-info` SHALL report `enabled: false` +and `enforced: false`. #### RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.On version: 1.0 When started with `GODEBUG=fips140=on`, the [clickhouse-backup-fips] binary SHALL operate with -FIPS 140-3 mode enabled without strict enforcement, `--version` SHALL report `FIPS 140-3: true`, -and the basic `clickhouse-backup-fips tables` command SHALL return the list of tables from a -FIPS-configured ClickHouse endpoint. +FIPS 140-3 mode enabled without strict enforcement. The output of +`clickhouse-backup-fips --fips-info` SHALL report `enabled: true` and `enforced: false`. #### RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Only version: 1.0 When started with `GODEBUG=fips140=only`, the [clickhouse-backup-fips] binary SHALL operate with strict FIPS 140-3 enforcement so that any non-approved cryptographic operation triggers an error -or panic, `--version` SHALL report `FIPS 140-3: true`, and `clickhouse-backup-fips tables` against -an approved [TLS] configuration SHALL return the list of tables. +or panic. The output of `clickhouse-backup-fips --fips-info` SHALL report `enabled: true` and +`enforced: true`. ### Startup Integrity Self-Tests diff --git a/test/testflows/clickhouse_backup/requirements/fips/requirements.py b/test/testflows/clickhouse_backup/requirements/fips/requirements.py index b34ba545..d89b8033 100644 --- a/test/testflows/clickhouse_backup/requirements/fips/requirements.py +++ b/test/testflows/clickhouse_backup/requirements/fips/requirements.py @@ -1,6 +1,6 @@ # These requirements were auto generated # from software requirements specification (SRS) -# document by TestFlows v2.1.240306.1133530. +# document by TestFlows v2.0.250110.1002922. # Do not edit by hand but re-generate instead # using 'tfs requirements generate' command. from testflows.core import Specification @@ -57,6 +57,23 @@ num='4.1.3' ) +RQ_SRS_013_ClickHouse_BackupUtility_non_FIPS_Binary = Requirement( + name='RQ.SRS-013.ClickHouse.BackupUtility.non-FIPS.Binary', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + 'The regular build of the [clickhouse-backup] utility SHALL be distributed as a\n' + 'binary named `clickhouse-backup`, that SHALL report ``FIPS 140-3:\tfalse``\n' + '\n' + ), + link=None, + level=3, + num='4.1.4' +) + RQ_SRS_013_ClickHouse_BackupUtility_FIPS_Approved_TLSProtocolVersions = Requirement( name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.TLSProtocolVersions', version='1.0', @@ -72,7 +89,7 @@ ), link=None, level=3, - num='4.1.4' + num='4.1.5' ) RQ_SRS_013_ClickHouse_BackupUtility_FIPS_Approved_CipherSuites_TLSv12_Approved = Requirement( @@ -96,7 +113,7 @@ ), link=None, level=3, - num='4.1.5' + num='4.1.6' ) RQ_SRS_013_ClickHouse_BackupUtility_FIPS_Approved_CipherSuites_TLSv13_Approved = Requirement( @@ -116,7 +133,7 @@ ), link=None, level=3, - num='4.1.6' + num='4.1.7' ) RQ_SRS_013_ClickHouse_BackupUtility_FIPS_Connectivity_FIPSEndpoint = Requirement( @@ -201,10 +218,9 @@ type=None, uid=None, description=( - 'When `GODEBUG` is not set, the [clickhouse-backup-fips] binary SHALL operate with FIPS 140-3 mode\n' - 'enabled by build-time default, `--version` SHALL report `FIPS 140-3: true`, and the basic\n' - '`clickhouse-backup-fips tables` command SHALL return the list of tables from a FIPS-configured\n' - 'ClickHouse endpoint.\n' + 'When `GODEBUG` is not set, the [clickhouse-backup-fips] binary SHALL rely on its build-time default\n' + '(`DefaultGODEBUG=fips140=on`) and operate with FIPS 140-3 mode enabled but not enforced. The\n' + 'output of `clickhouse-backup-fips --fips-info` SHALL report `enabled: true` and `enforced: false`.\n' '\n' ), link=None, @@ -212,6 +228,43 @@ num='4.4.1' ) +RQ_SRS_013_ClickHouse_BackupUtility_FIPS_GODEBUG_Empty = Requirement( + name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Empty', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + 'When started with an empty `GODEBUG` (i.e. `GODEBUG=`), the [clickhouse-backup-fips] binary SHALL\n' + 'behave identically to the unset case, relying on its build-time default (`fips140=on`) with\n' + 'FIPS 140-3 mode enabled but not enforced. The output of `clickhouse-backup-fips --fips-info`\n' + 'SHALL report `enabled: true` and `enforced: false`.\n' + '\n' + ), + link=None, + level=3, + num='4.4.2' +) + +RQ_SRS_013_ClickHouse_BackupUtility_FIPS_GODEBUG_Off = Requirement( + name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Off', + version='1.0', + priority=None, + group=None, + type=None, + uid=None, + description=( + 'When started with `GODEBUG=fips140=off`, the [clickhouse-backup-fips] binary SHALL disable\n' + 'FIPS 140-3 mode. The output of `clickhouse-backup-fips --fips-info` SHALL report `enabled: false`\n' + 'and `enforced: false`.\n' + '\n' + ), + link=None, + level=3, + num='4.4.3' +) + RQ_SRS_013_ClickHouse_BackupUtility_FIPS_GODEBUG_On = Requirement( name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.On', version='1.0', @@ -221,14 +274,13 @@ uid=None, description=( 'When started with `GODEBUG=fips140=on`, the [clickhouse-backup-fips] binary SHALL operate with\n' - 'FIPS 140-3 mode enabled without strict enforcement, `--version` SHALL report `FIPS 140-3: true`,\n' - 'and the basic `clickhouse-backup-fips tables` command SHALL return the list of tables from a\n' - 'FIPS-configured ClickHouse endpoint.\n' + 'FIPS 140-3 mode enabled without strict enforcement. The output of\n' + '`clickhouse-backup-fips --fips-info` SHALL report `enabled: true` and `enforced: false`.\n' '\n' ), link=None, level=3, - num='4.4.2' + num='4.4.4' ) RQ_SRS_013_ClickHouse_BackupUtility_FIPS_GODEBUG_Only = Requirement( @@ -241,13 +293,13 @@ description=( 'When started with `GODEBUG=fips140=only`, the [clickhouse-backup-fips] binary SHALL operate with\n' 'strict FIPS 140-3 enforcement so that any non-approved cryptographic operation triggers an error\n' - 'or panic, `--version` SHALL report `FIPS 140-3: true`, and `clickhouse-backup-fips tables` against\n' - 'an approved [TLS] configuration SHALL return the list of tables.\n' + 'or panic. The output of `clickhouse-backup-fips --fips-info` SHALL report `enabled: true` and\n' + '`enforced: true`.\n' '\n' ), link=None, level=3, - num='4.4.3' + num='4.4.5' ) RQ_SRS_013_ClickHouse_BackupUtility_FIPS_SelfTest_Integrity = Requirement( @@ -597,9 +649,10 @@ Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GoCryptographicModule', level=3, num='4.1.1'), Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Build.GOFIPS140', level=3, num='4.1.2'), Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Binary', level=3, num='4.1.3'), - Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.TLSProtocolVersions', level=3, num='4.1.4'), - Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.CipherSuites.TLSv12.Approved', level=3, num='4.1.5'), - Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.CipherSuites.TLSv13.Approved', level=3, num='4.1.6'), + Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.non-FIPS.Binary', level=3, num='4.1.4'), + Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.TLSProtocolVersions', level=3, num='4.1.5'), + Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.CipherSuites.TLSv12.Approved', level=3, num='4.1.6'), + Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.CipherSuites.TLSv13.Approved', level=3, num='4.1.7'), Heading(name='Connectivity', level=2, num='4.2'), Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Connectivity.FIPSEndpoint', level=3, num='4.2.1'), Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Connectivity.NonFIPSEndpoint', level=3, num='4.2.2'), @@ -608,8 +661,10 @@ Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Version.BuildSetting', level=3, num='4.3.2'), Heading(name='GODEBUG fips140 Modes', level=2, num='4.4'), Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Unset', level=3, num='4.4.1'), - Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.On', level=3, num='4.4.2'), - Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Only', level=3, num='4.4.3'), + Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Empty', level=3, num='4.4.2'), + Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Off', level=3, num='4.4.3'), + Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.On', level=3, num='4.4.4'), + Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Only', level=3, num='4.4.5'), Heading(name='Startup Integrity Self-Tests', level=2, num='4.5'), Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.SelfTest.Integrity', level=3, num='4.5.1'), Heading(name='RQ.SRS-013.ClickHouse.BackupUtility.FIPS.SelfTest.TamperedBinary', level=3, num='4.5.2'), @@ -638,6 +693,7 @@ RQ_SRS_013_ClickHouse_BackupUtility_FIPS_GoCryptographicModule, RQ_SRS_013_ClickHouse_BackupUtility_FIPS_Build_GOFIPS140, RQ_SRS_013_ClickHouse_BackupUtility_FIPS_Binary, + RQ_SRS_013_ClickHouse_BackupUtility_non_FIPS_Binary, RQ_SRS_013_ClickHouse_BackupUtility_FIPS_Approved_TLSProtocolVersions, RQ_SRS_013_ClickHouse_BackupUtility_FIPS_Approved_CipherSuites_TLSv12_Approved, RQ_SRS_013_ClickHouse_BackupUtility_FIPS_Approved_CipherSuites_TLSv13_Approved, @@ -646,6 +702,8 @@ RQ_SRS_013_ClickHouse_BackupUtility_FIPS_Version_Status, RQ_SRS_013_ClickHouse_BackupUtility_FIPS_Version_BuildSetting, RQ_SRS_013_ClickHouse_BackupUtility_FIPS_GODEBUG_Unset, + RQ_SRS_013_ClickHouse_BackupUtility_FIPS_GODEBUG_Empty, + RQ_SRS_013_ClickHouse_BackupUtility_FIPS_GODEBUG_Off, RQ_SRS_013_ClickHouse_BackupUtility_FIPS_GODEBUG_On, RQ_SRS_013_ClickHouse_BackupUtility_FIPS_GODEBUG_Only, RQ_SRS_013_ClickHouse_BackupUtility_FIPS_SelfTest_Integrity, @@ -663,7 +721,7 @@ RQ_SRS_013_ClickHouse_BackupUtility_FIPS_Configuration_SecureClickHouse, RQ_SRS_013_ClickHouse_BackupUtility_FIPS_Server_Listener, ), - content=''' + content=r''' # QA-SRS013 ClickHouse Backup Utility FIPS Compatibility # Software Requirements Specification @@ -684,9 +742,10 @@ * 4.1.1 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GoCryptographicModule](#rqsrs-013clickhousebackuputilityfipsgocryptographicmodule) * 4.1.2 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Build.GOFIPS140](#rqsrs-013clickhousebackuputilityfipsbuildgofips140) * 4.1.3 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Binary](#rqsrs-013clickhousebackuputilityfipsbinary) - * 4.1.4 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.TLSProtocolVersions](#rqsrs-013clickhousebackuputilityfipsapprovedtlsprotocolversions) - * 4.1.5 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.CipherSuites.TLSv12.Approved](#rqsrs-013clickhousebackuputilityfipsapprovedciphersuitestlsv12approved) - * 4.1.6 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.CipherSuites.TLSv13.Approved](#rqsrs-013clickhousebackuputilityfipsapprovedciphersuitestlsv13approved) + * 4.1.4 [RQ.SRS-013.ClickHouse.BackupUtility.non-FIPS.Binary](#rqsrs-013clickhousebackuputilitynon-fipsbinary) + * 4.1.5 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.TLSProtocolVersions](#rqsrs-013clickhousebackuputilityfipsapprovedtlsprotocolversions) + * 4.1.6 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.CipherSuites.TLSv12.Approved](#rqsrs-013clickhousebackuputilityfipsapprovedciphersuitestlsv12approved) + * 4.1.7 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.CipherSuites.TLSv13.Approved](#rqsrs-013clickhousebackuputilityfipsapprovedciphersuitestlsv13approved) * 4.2 [Connectivity](#connectivity) * 4.2.1 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Connectivity.FIPSEndpoint](#rqsrs-013clickhousebackuputilityfipsconnectivityfipsendpoint) * 4.2.2 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Connectivity.NonFIPSEndpoint](#rqsrs-013clickhousebackuputilityfipsconnectivitynonfipsendpoint) @@ -695,8 +754,10 @@ * 4.3.2 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Version.BuildSetting](#rqsrs-013clickhousebackuputilityfipsversionbuildsetting) * 4.4 [GODEBUG fips140 Modes](#godebug-fips140-modes) * 4.4.1 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Unset](#rqsrs-013clickhousebackuputilityfipsgodebugunset) - * 4.4.2 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.On](#rqsrs-013clickhousebackuputilityfipsgodebugon) - * 4.4.3 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Only](#rqsrs-013clickhousebackuputilityfipsgodebugonly) + * 4.4.2 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Empty](#rqsrs-013clickhousebackuputilityfipsgodebugempty) + * 4.4.3 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Off](#rqsrs-013clickhousebackuputilityfipsgodebugoff) + * 4.4.4 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.On](#rqsrs-013clickhousebackuputilityfipsgodebugon) + * 4.4.5 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Only](#rqsrs-013clickhousebackuputilityfipsgodebugonly) * 4.5 [Startup Integrity Self-Tests](#startup-integrity-self-tests) * 4.5.1 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.SelfTest.Integrity](#rqsrs-013clickhousebackuputilityfipsselftestintegrity) * 4.5.2 [RQ.SRS-013.ClickHouse.BackupUtility.FIPS.SelfTest.TamperedBinary](#rqsrs-013clickhousebackuputilityfipsselftesttamperedbinary) @@ -791,6 +852,12 @@ The FIPS-compatible build of the [clickhouse-backup] utility SHALL be distributed as a separate binary named `clickhouse-backup-fips`, distinct from the standard `clickhouse-backup` binary. +#### RQ.SRS-013.ClickHouse.BackupUtility.non-FIPS.Binary +version: 1.0 + +The regular build of the [clickhouse-backup] utility SHALL be distributed as a +binary named `clickhouse-backup`, that SHALL report ``FIPS 140-3: false`` + #### RQ.SRS-013.ClickHouse.BackupUtility.FIPS.Approved.TLSProtocolVersions version: 1.0 @@ -856,29 +923,56 @@ ### GODEBUG fips140 Modes +The [clickhouse-backup-fips] binary SHALL expose its FIPS build and runtime posture via +`clickhouse-backup-fips --fips-info`, which prints a line-oriented `key: value` dump including, +under the `fips_module:` block, `enabled: ` and `enforced: `. The binary +is built with `DefaultGODEBUG=fips140=on`, so the `fips140` runtime key SHALL produce the +following posture: + +| `GODEBUG` runtime | `enabled` | `enforced` | +| ----------------- | --------- | ---------- | +| unset | true | false | +| empty (`GODEBUG=`)| true | false | +| `fips140=off` | false | false | +| `fips140=on` | true | false | +| `fips140=only` | true | true | + #### RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Unset version: 1.0 -When `GODEBUG` is not set, the [clickhouse-backup-fips] binary SHALL operate with FIPS 140-3 mode -enabled by build-time default, `--version` SHALL report `FIPS 140-3: true`, and the basic -`clickhouse-backup-fips tables` command SHALL return the list of tables from a FIPS-configured -ClickHouse endpoint. +When `GODEBUG` is not set, the [clickhouse-backup-fips] binary SHALL rely on its build-time default +(`DefaultGODEBUG=fips140=on`) and operate with FIPS 140-3 mode enabled but not enforced. The +output of `clickhouse-backup-fips --fips-info` SHALL report `enabled: true` and `enforced: false`. + +#### RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Empty +version: 1.0 + +When started with an empty `GODEBUG` (i.e. `GODEBUG=`), the [clickhouse-backup-fips] binary SHALL +behave identically to the unset case, relying on its build-time default (`fips140=on`) with +FIPS 140-3 mode enabled but not enforced. The output of `clickhouse-backup-fips --fips-info` +SHALL report `enabled: true` and `enforced: false`. + +#### RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Off +version: 1.0 + +When started with `GODEBUG=fips140=off`, the [clickhouse-backup-fips] binary SHALL disable +FIPS 140-3 mode. The output of `clickhouse-backup-fips --fips-info` SHALL report `enabled: false` +and `enforced: false`. #### RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.On version: 1.0 When started with `GODEBUG=fips140=on`, the [clickhouse-backup-fips] binary SHALL operate with -FIPS 140-3 mode enabled without strict enforcement, `--version` SHALL report `FIPS 140-3: true`, -and the basic `clickhouse-backup-fips tables` command SHALL return the list of tables from a -FIPS-configured ClickHouse endpoint. +FIPS 140-3 mode enabled without strict enforcement. The output of +`clickhouse-backup-fips --fips-info` SHALL report `enabled: true` and `enforced: false`. #### RQ.SRS-013.ClickHouse.BackupUtility.FIPS.GODEBUG.Only version: 1.0 When started with `GODEBUG=fips140=only`, the [clickhouse-backup-fips] binary SHALL operate with strict FIPS 140-3 enforcement so that any non-approved cryptographic operation triggers an error -or panic, `--version` SHALL report `FIPS 140-3: true`, and `clickhouse-backup-fips tables` against -an approved [TLS] configuration SHALL return the list of tables. +or panic. The output of `clickhouse-backup-fips --fips-info` SHALL report `enabled: true` and +`enforced: true`. ### Startup Integrity Self-Tests diff --git a/test/testflows/clickhouse_backup/tests/fips_140_3.py b/test/testflows/clickhouse_backup/tests/fips_140_3.py index af913e5d..716ae453 100644 --- a/test/testflows/clickhouse_backup/tests/fips_140_3.py +++ b/test/testflows/clickhouse_backup/tests/fips_140_3.py @@ -46,27 +46,57 @@ "TLS_AES_256_GCM_SHA384", ) -# Single non-approved TLSv1.3 suite -NON_FIPS_TLS13 = ("TLS_CHACHA20_POLY1305_SHA256",) - -# Non-FIPS TLS1.2 suites for the outbound verification. -# The set covers three different reasons a TLS1.2 cipher must be rejected -# by the Go FIPS 140-3 outbound policy: -# -# 1. Non-approved bulk cipher (CHACHA20): -# ECDHE-RSA-CHACHA20-POLY1305 -# 2. Non-approved key exchange (DHE): -# DHE-RSA-AES256-GCM-SHA384 -# DHE-RSA-AES128-GCM-SHA256 -# 3. Plain RSA static key exchange (no forward secrecy): -# AES256-GCM-SHA384 -# AES128-GCM-SHA256 +# Non-approved TLSv1.3 suites the FIPS policy must reject. +# Default runs probe the single documented suite; the `_STRESS` list adds the +# remaining non-approved TLSv1.3 suites (CCM bulk ciphers, outside the GCM-only +# FIPS-approved set). The wider `--stress` coverage is slower by design. +NON_FIPS_TLS13 = ( + "TLS_CHACHA20_POLY1305_SHA256", +) +NON_FIPS_TLS13_STRESS = NON_FIPS_TLS13 + ( + "TLS_AES_128_CCM_SHA256", + "TLS_AES_128_CCM_8_SHA256", +) + +# Non-approved TLSv1.2 ciphers the FIPS outbound policy must reject, one per +# rejection reason: +# * non-approved bulk cipher (ChaCha20): ECDHE-RSA-CHACHA20-POLY1305 +# * non-approved key exchange (DHE): DHE-RSA-AES256-GCM-SHA384 / -AES128- +# * plain RSA key exchange (no forward secrecy): AES256-GCM-SHA384 / AES128-GCM-SHA256 +# The `_STRESS` list adds more ciphers from the same rejection classes +# (ECDSA/DHE ChaCha20 and CBC-mode ciphers outside the GCM-only approved set). NON_FIPS_TLS12_OUTBOUND = ( -"ECDHE-RSA-CHACHA20-POLY1305", -"DHE-RSA-AES256-GCM-SHA384", -"DHE-RSA-AES128-GCM-SHA256", -"AES256-GCM-SHA384", -"AES128-GCM-SHA256", + "ECDHE-RSA-CHACHA20-POLY1305", + "DHE-RSA-AES256-GCM-SHA384", + "DHE-RSA-AES128-GCM-SHA256", + "AES256-GCM-SHA384", + "AES128-GCM-SHA256", +) +NON_FIPS_TLS12_OUTBOUND_STRESS = NON_FIPS_TLS12_OUTBOUND + ( + "ECDHE-ECDSA-CHACHA20-POLY1305", + "DHE-RSA-CHACHA20-POLY1305", + "ECDHE-RSA-AES128-SHA", + "ECDHE-RSA-AES256-SHA", + "ECDHE-RSA-AES128-SHA256", + "ECDHE-RSA-AES256-SHA384", + "AES128-SHA", + "AES256-SHA", + "AES128-SHA256", + "AES256-SHA256", +) + +# Non-approved TLSv1.2 ciphers the inbound REST API listener must reject. The +# base adds RC4 / 3DES on top of the shared ChaCha20 reject; the `_STRESS` list +# reuses the outbound stress set (which already covers ChaCha20) and adds only +# the inbound-specific RC4 / 3DES. +NON_FIPS_TLS12_INBOUND = ( + "ECDHE-RSA-CHACHA20-POLY1305", + "RC4-SHA", + "DES-CBC3-SHA", +) +NON_FIPS_TLS12_INBOUND_STRESS = NON_FIPS_TLS12_OUTBOUND_STRESS + ( + "RC4-SHA", + "DES-CBC3-SHA", ) CLI_CMD_TIMEOUT_SEC = 15 # Timeout for clickhouse-backup-fips command runs. @@ -382,8 +412,7 @@ def clickhouse_backup_fips_version_output(self): @TestScenario @Requirements( - RQ_SRS_013_ClickHouse_BackupUtility_FIPS_Binary("1.0"), - RQ_SRS_013_ClickHouse_BackupUtility_FIPS_Version_Status("1.0"), + RQ_SRS_013_ClickHouse_BackupUtility_non_FIPS_Binary("1.0"), ) def clickhouse_backup_fips_version_output_negative_check(self): """Self-check for `clickhouse_backup_fips_version_output`. @@ -581,6 +610,131 @@ def connectivity_against_non_fips_clickhouse_server(self): cluster.stop_auxiliary_container(NON_FIPS_CH_SERVER_NAME) + + + +def _godebug_env_prefix(godebug): + """Return the `env ...` command prefix that applies one GODEBUG case. + + `None` strips any inherited `GODEBUG` (`env -u GODEBUG`), `""` sets it + empty (`env GODEBUG=`), and any other value sets `GODEBUG=fips140=`. + Setting it explicitly per case makes the test independent of the suite-wide + `--fips-godebug` selection that the container otherwise exports. + """ + if godebug is None: + return "env -u GODEBUG " + if godebug == "": + return "env GODEBUG= " + return f"env GODEBUG=fips140={godebug} " + + +def _read_fips_info_field(output, field): + """Return the value of a `:` line from `--fips-info` output.""" + for line in output.splitlines(): + stripped = line.strip() + if stripped.startswith(f"{field}:"): + return stripped.split(":", 1)[1].strip() + return None + + +@TestStep(Then) +def _check_fips_info_values(self, backup_fips, *, name, godebug, + expected_enabled, expected_enforced): + """Run `--fips-info` for one GODEBUG case and assert enabled/enforced. + + Asserts the command succeeds and that the `fips_module` block reports the + expected `enabled` / `enforced` booleans for the given GODEBUG mode. + """ + cmd = f"{_godebug_env_prefix(godebug)}{FIPS_BINARY_IN_CONTAINER} --fips-info" + result = backup_fips.cmd(cmd, no_checks=True) + output = result.output or "" + + assert result.exitcode == 0, error( + f"`--fips-info` failed for GODEBUG mode `{name}` " + f"(exit={result.exitcode}).\n{output}" + ) + + enabled = _read_fips_info_field(output, "enabled") + enforced = _read_fips_info_field(output, "enforced") + want_enabled = str(expected_enabled).lower() + want_enforced = str(expected_enforced).lower() + + assert enabled == want_enabled, error( + f"GODEBUG mode `{name}`: expected `enabled: {want_enabled}`, " + f"got `enabled: {enabled}`.\n{output}" + ) + assert enforced == want_enforced, error( + f"GODEBUG mode `{name}`: expected `enforced: {want_enforced}`, " + f"got `enforced: {enforced}`.\n{output}" + ) + + +@TestScenario +@Requirements( + RQ_SRS_013_ClickHouse_BackupUtility_FIPS_GODEBUG_Unset("1.0"), + RQ_SRS_013_ClickHouse_BackupUtility_FIPS_GODEBUG_Empty("1.0"), + RQ_SRS_013_ClickHouse_BackupUtility_FIPS_GODEBUG_Off("1.0"), + RQ_SRS_013_ClickHouse_BackupUtility_FIPS_GODEBUG_On("1.0"), + RQ_SRS_013_ClickHouse_BackupUtility_FIPS_GODEBUG_Only("1.0"), +) +def godebug_fips140_modes(self): + """Validate `--fips-info` FIPS posture for every GODEBUG fips140 mode. + + For each documented GODEBUG runtime mode (`unset`, empty, `fips140=off`, + `fips140=on`, `fips140=only`), run `clickhouse-backup-fips --fips-info` and + assert the reported `enabled` / `enforced` flags match the expected truth + table: + + GODEBUG runtime enabled enforced + unset true false + empty ("") true false + fips140=off false false + fips140=on true false + fips140=only true true + + Each mode is set explicitly per command, so the result does not depend on + the suite-wide `--fips-godebug` selection. + """ + # Expected `clickhouse-backup-fips --fips-info` posture for every GODEBUG + # fips140 runtime mode. The FIPS binary is built with `DefaultGODEBUG` set to + # `fips140=on`, so leaving GODEBUG unset or empty keeps FIPS *enabled* but not + # *enforced*; `fips140=on` is the same; `fips140=only` adds strict enforcement; + # `fips140=off` disables FIPS entirely. + # + # GODEBUG runtime enabled enforced + # ------------------- ------- -------- + # unset true false + # empty ("") true false + # fips140=off false false + # fips140=on true false + # fips140=only true true + # + # Each tuple is (case name, GODEBUG value, expected enabled, expected enforced), + # where the GODEBUG value is: + # None -> GODEBUG removed from the environment (the "unset" case), + # "" -> GODEBUG present but empty, + # else -> GODEBUG=fips140=. + FIPS_GODEBUG_INFO_CASES = [ + ("unset", None, True, False), + ("empty", "", True, False), + ("off", "off", False, False), + ("on", "on", True, False), + ("only", "only", True, True), + ] + backup_fips = _require_fips_container(self) + + for name, godebug, expected_enabled, expected_enforced in FIPS_GODEBUG_INFO_CASES: + with Check(f"GODEBUG mode `{name}` reports " + f"enabled={expected_enabled} enforced={expected_enforced}"): + _check_fips_info_values( + backup_fips=backup_fips, + name=name, + godebug=godebug, + expected_enabled=expected_enabled, + expected_enforced=expected_enforced, + ) + + @TestScenario @Requirements( RQ_SRS_013_ClickHouse_BackupUtility_FIPS_SelfTest_Integrity("1.0"), @@ -661,12 +815,18 @@ def inbound_tls_cipher_negotiation(self): (`/etc/clickhouse-server/ssl/server.{crt,key}`), inherited via `volumes_from_name="clickhouse1"` on the FIPS container. """ - # Non-FIPS TLS1.2 suites - NON_FIPS_TLS12_INBOUND_REST = ( - "ECDHE-RSA-CHACHA20-POLY1305", - "RC4-SHA", - "DES-CBC3-SHA", - ) + # Non-approved profiles the REST API listener must reject. The TLSv1.2 base + # adds RC4 / 3DES (specific to the inbound case); the TLSv1.3 base is the + # shared non-approved suite. `--stress` widens both with the broader stress + # sets so legacy / CBC ciphers are exercised here too; default keeps the + # minimum. `STRESS` lists already include their base, so they are assigned, + # not appended, to avoid probing the same cipher twice. + non_fips_tls12 = NON_FIPS_TLS12_INBOUND + non_fips_tls13 = NON_FIPS_TLS13 + + if self.context.stress: + non_fips_tls12 = NON_FIPS_TLS12_INBOUND_STRESS + non_fips_tls13 = NON_FIPS_TLS13_STRESS backup_fips = _require_fips_container(self) @@ -701,7 +861,7 @@ def inbound_tls_cipher_negotiation(self): ) with And("I try to connect using each non-FIPS TLSv1.3 cipher suite"): - for ciphersuite in NON_FIPS_TLS13: + for ciphersuite in non_fips_tls13: with Check(f"TLSv1.3 ciphersuite {ciphersuite} should be rejected"): _check_tls_handshake( node=backup_fips, target=target, tls_flag="-tls1_3", @@ -709,7 +869,7 @@ def inbound_tls_cipher_negotiation(self): ) with And("I try to connect using each non-FIPS TLSv1.2 cipher"): - for cipher in NON_FIPS_TLS12_INBOUND_REST: + for cipher in non_fips_tls12: with Check(f"TLSv1.2 cipher {cipher} should be rejected"): _check_tls_handshake( node=backup_fips, target=target, tls_flag="-tls1_2", @@ -772,6 +932,10 @@ def outbound_tls_cipher_negotiation(self): f"-c {FIPS_OUTBOUND_CH_CONFIG_PATH} tables 2>&1" # `2>&1` redirects stderr to stdout. ) + # `--stress` widens the non-approved coverage; default keeps the minimum. + non_fips_tls13 = NON_FIPS_TLS13_STRESS if self.context.stress else NON_FIPS_TLS13 + non_fips_tls12 = NON_FIPS_TLS12_OUTBOUND_STRESS if self.context.stress else NON_FIPS_TLS12_OUTBOUND + with When("I try each FIPS-approved TLSv1.3 cipher suite on the CH endpoint"): for ciphersuite in FIPS_TLS13_APPROVED: with Check(f"TLSv1.3 ciphersuite {ciphersuite} should be accepted"): @@ -791,7 +955,7 @@ def outbound_tls_cipher_negotiation(self): ) with And("I try each non-FIPS TLSv1.3 cipher suite on the CH endpoint"): - for ciphersuite in NON_FIPS_TLS13: + for ciphersuite in non_fips_tls13: with Check(f"TLSv1.3 ciphersuite {ciphersuite} should be rejected"): _check_outbound_tls_with_cipher( cluster=cluster, backup_fips=backup_fips, @@ -800,7 +964,7 @@ def outbound_tls_cipher_negotiation(self): ) with And("I try each non-FIPS TLSv1.2 cipher on the CH endpoint"): - for cipher in NON_FIPS_TLS12_OUTBOUND: + for cipher in non_fips_tls12: with Check(f"TLSv1.2 cipher {cipher} should be rejected"): _check_outbound_tls_with_cipher( cluster=cluster, backup_fips=backup_fips, @@ -853,6 +1017,10 @@ def outbound_tls_to_s3_endpoint_with_openssl_s_server(self): f"-c {FIPS_OUTBOUND_S3_CONFIG_PATH} list remote 2>&1" # `2>&1` redirects stderr to stdout. ) + # `--stress` widens the non-approved coverage; default keeps the minimum. + non_fips_tls13 = NON_FIPS_TLS13_STRESS if self.context.stress else NON_FIPS_TLS13 + non_fips_tls12 = NON_FIPS_TLS12_OUTBOUND_STRESS if self.context.stress else NON_FIPS_TLS12_OUTBOUND + with Check("I try each FIPS-approved TLSv1.3 cipher suite on the S3 endpoint"): for ciphersuite in FIPS_TLS13_APPROVED: with Check(f"TLSv1.3 ciphersuite {ciphersuite} should be accepted"): @@ -874,7 +1042,7 @@ def outbound_tls_to_s3_endpoint_with_openssl_s_server(self): ) with Check("I try each non-FIPS TLSv1.3 cipher suite on the S3 endpoint"): - for ciphersuite in NON_FIPS_TLS13: + for ciphersuite in non_fips_tls13: with Check(f"TLSv1.3 ciphersuite {ciphersuite} should be rejected"): _check_outbound_tls_with_cipher( cluster=cluster, backup_fips=backup_fips, @@ -885,7 +1053,7 @@ def outbound_tls_to_s3_endpoint_with_openssl_s_server(self): ) with Check("I try each non-FIPS TLSv1.2 cipher on the S3 endpoint"): - for cipher in NON_FIPS_TLS12_OUTBOUND: + for cipher in non_fips_tls12: with Check(f"TLSv1.2 cipher {cipher} should be rejected"): _check_outbound_tls_with_cipher( cluster=cluster, backup_fips=backup_fips, @@ -1139,6 +1307,11 @@ def outbound_tls_to_nonfips_clickhouse_with_cipher_profile(self): `clickhouse-backup-fips tables` end-to-end and asserts the command succeeds (TLS + native CH protocol). + With `--stress` the server instead offers the full documented + FIPS-approved cipher set (`listeners-fips-cipher-stress.xml`), so the + end-to-end run is exercised against the same cipher list a real + FIPS-compatible server advertises. + Cipher-policy rejection (FIPS binary refusing non-approved ciphers) is covered deterministically by `outbound_tls_cipher_negotiation` using `openssl s_server`; replaying the negative case here would be flaky @@ -1147,9 +1320,15 @@ def outbound_tls_to_nonfips_clickhouse_with_cipher_profile(self): """ backup_fips = _require_fips_container(self) cluster = self.context.cluster + + # Default: a single FIPS-approved cipher. `--stress`: the full documented set. + listeners_basename = ( + "listeners-fips-cipher-stress.xml" if self.context.stress + else "listeners-fips-cipher.xml" + ) listeners_xml = os.path.join( cluster.tests_dir, - "configs/clickhouse_nonfips_server/config.d/listeners-fips-cipher.xml", + f"configs/clickhouse_nonfips_server/config.d/{listeners_basename}", ) try: @@ -1195,7 +1374,8 @@ def acvp_tests(self): and asserts it exits 0 and prints the expected line tracked in `FIPS_ACVP_EXPECTED_OUTPUT`. - Opt-in: skipped unless `RUN_ACVP_TESTS=1` is set. + Opt-in: skipped unless `RUN_ACVP_TESTS=1` is set or the suite runs + with `--stress` option. """ # ACVP wrapper scenario opt-in. # Set `RUN_ACVP_TESTS=1` locally or in the CI workflow to enable it. @@ -1213,10 +1393,12 @@ def acvp_tests(self): # a boringssl clone, an acvptool build, and then the ACVP run itself. FIPS_ACVP_TIMEOUT_SEC = 30 * 60 flag = os.environ.get(FIPS_ACVP_ENV_FLAG, "").strip().lower() - if flag not in FIPS_ACVP_ENV_FLAG_VALUES: + # `--stress` runs the full FIPS coverage, so enable ACVP tests automatically + # there even when `RUN_ACVP_TESTS` is unset. + if flag not in FIPS_ACVP_ENV_FLAG_VALUES and not self.context.stress: skip( - f"set {FIPS_ACVP_ENV_FLAG}=1 to enable; the wrapper pulls " - f"Docker images and clones upstream repos." + f"set {FIPS_ACVP_ENV_FLAG}=1 (or run with `--stress`) to enable; " + f"the wrapper pulls Docker images and clones upstream repos." ) cluster = self.context.cluster @@ -1260,6 +1442,7 @@ def fips_140_3(self): Scenario(run=gofips140_build_flags_present, flags=TE) Scenario(run=connectivity_against_non_fips_clickhouse_server, flags=TE) Scenario(run=connectivity_against_fips_clickhouse_server, flags=TE) + Scenario(run=godebug_fips140_modes, flags=TE) Scenario(run=fips_integrity_self_test_failure_on_tampered_binary, flags=TE) Scenario(run=inbound_tls_cipher_negotiation, flags=TE) Scenario(run=outbound_tls_cipher_negotiation, flags=TE) diff --git a/test/testflows/run.sh b/test/testflows/run.sh index c7b373e4..804cc67b 100755 --- a/test/testflows/run.sh +++ b/test/testflows/run.sh @@ -141,6 +141,24 @@ if command -v tfs &>/dev/null && [[ -f "${RAW_LOG}" ]]; then tfs ${TFS_FLAGS} transform compact "${RAW_LOG}" "${CUR_DIR}/compact.log" || true tfs ${TFS_FLAGS} transform nice "${RAW_LOG}" "${CUR_DIR}/nice.log.txt" || true tfs ${TFS_FLAGS} transform short "${RAW_LOG}" "${CUR_DIR}/short.log.txt" || true + + # FIPS requirements coverage report (HTML artifact). The FIPS suite has its + # own specification backed by requirements/fips/requirements.py, distinct from + # the main suite. The requirements source is passed explicitly (instead of + # '-' = read specs from the log, which would merge both specifications that + # regression.py declares into one report) so the report is scoped to the FIPS + # specification only. Generated only for the FIPS run (its CI job sets + # RUN_TESTS to the FIPS feature); the main run is unaffected. This is the + # requirements-coverage HTML produced manually before via `tfs report + # coverage`; Go source-code coverage is reported separately to Coveralls. + FIPS_REQUIREMENTS="${CUR_DIR}/clickhouse_backup/requirements/fips/requirements.py" + if [[ "${RUN_TESTS}" == *FIPS* && -f "${FIPS_REQUIREMENTS}" ]]; then + tfs ${TFS_FLAGS} report coverage "${FIPS_REQUIREMENTS}" "${RAW_LOG}" \ + --confidential --copyright "Altinity LTD" --logo "${CUR_DIR}/altinity.png" \ + --title "ClickHouse Backup FIPS Requirements Coverage" \ + | tfs ${TFS_FLAGS} document convert > "${CUR_DIR}/fips_coverage.html" || true + fi + if [[ -n "${GITHUB_SERVER_URL}" && -n "${GITHUB_REPOSITORY}" && -n "${GITHUB_RUN_ID}" ]]; then tfs ${TFS_FLAGS} report results \ -a "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/" \