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}/" \