feat(fips): FIPS 140-3 build variant (arc-fips)#512
Conversation
Tier 2 defense-grade work, item 1: an optional FIPS build of Arc for US defense/federal and other regulated environments. Same source/version as the standard build — FIPS is a build variant, not a separate version line. Crypto changes (apply to both builds where noted): - Token hashing: bcrypt -> PBKDF2-HMAC-SHA256 (FIPS-approved, stdlib crypto/pbkdf2). Self-describing $pbkdf2-sha256$ format, constant-time compare. Legacy bcrypt/sha256 hashes verify in the default build; the fips build fails them closed with a "rotate this token" warning (bcrypt is not linked into the fips binary at all). - HKDF: golang.org/x/crypto/hkdf -> stdlib crypto/hkdf (inside the FIPS boundary). Argument order preserved; a known-answer test pins the derived key byte-for-byte so a cluster split cannot slip through. - TLS: explicit TLS 1.2 floor in both builds (pre-existing behavior, now pinned so it survives Go default changes and ignores GODEBUG=tls10server). The fips build additionally restricts cipher suites + curves to the FIPS-approved set (drops X25519); the standard build keeps Go's defaults unchanged. MQTT refuses InsecureSkipVerify in the fips build. - API listener: the fips build replaces Fiber's opaque ListenTLS with a hardened tls.NewListener so Arc owns the negotiated suites; the default build is unchanged. Runtime/build: - New internal/fips package: Enabled() (runtime), BuildTagged (compile-time, fips/!fips tagged), HardenTLSConfig(). - arc-fips bakes GODEBUG=fips140=only (//go:debug) and fails closed at startup if not actually in FIPS mode. Startup banner logs fips_mode. - Built with GOFIPS140=v1.0.0 (the CMVP-certified Go Cryptographic Module snapshot). Same duckdb_arrow performance tag as the standard build. Build/CI: - Makefile: build-fips, test-fips, fips-check (import-graph lint). - Dockerfile: FIPS=1 build arg. - release-build.yml: build-binaries matrix gains a variant dimension (standard/fips x amd64/arm64) producing signed arc-fips-* binaries; SLSA provenance covers them; new docker-build-fips job pushes a cosign-signed :VERSION-fips image to GHCR + Docker Hub. CI verifies the fips build links no x/crypto/bcrypt|hkdf and boots with fips_mode=true. Docs: release notes + arc README + enterprise product-definition. User guide lands in docs.basekick.net. Honest CMVP framing throughout: the Go module is certified; Arc itself is not a CMVP-listed module.
|
@gemini-code-assist please review Focus areas for this FIPS 140-3 build-variant PR:
|
There was a problem hiding this comment.
Code Review
This pull request introduces an optional FIPS 140-3 build variant (arc-fips) compiled against the CMVP-certified Go Cryptographic Module. Key changes include migrating API token hashing from bcrypt to PBKDF2-HMAC-SHA256, transitioning cluster replication key derivation to the standard library's crypto/hkdf, and enforcing FIPS-approved TLS configurations (cipher suites and curves) for the API, cluster, and MQTT paths. Feedback on the changes highlights a missing NextProtos configuration in the custom FIPS TLS listener, which disables HTTP/2 negotiation and could lead to performance regressions.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Code Review
This pull request introduces a FIPS 140-3 build variant (arc-fips) for Arc, migrating token hashing to PBKDF2, restricting TLS configurations to FIPS-approved primitives, and utilizing the standard library's crypto/hkdf. Feedback on these changes highlights a critical security issue where the deprecated CurvePreferences field is ignored in Go 1.23+, allowing non-approved curves like X25519 to persist; this must be updated to CurveIDs along with its associated tests. Additionally, the PBKDF2 verification logic should enforce upper bounds on iteration counts and salt/hash lengths to prevent potential CPU and memory denial-of-service attacks, and the fips-check Makefile target should be hardened to prevent silent success if the underlying go list command fails.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Gemini round 2 + CodeQL. Two valid findings hardened, three declined with evidence. PBKDF2 verify hardening (Gemini security-high + adversarial fix-check): - Upfront pbkdf2MaxEncodedLen (128) length guard BEFORE any Atoi/base64 decode. The adversarial check showed the original bounds validated semantic length only AFTER decoding — a 100MB base64 field decodes to ~78MB before rejection, and (verify is lazy on the persisted SQLite row) re-triggers on every matching auth. Guard input size first, then parse. - iter ceiling (pbkdf2MaxIterations = 10M) + salt/key length pins. - Raft validateTokenEntry caps TokenHash (<=512) and TokenPrefix (<=256) so a rogue cluster-proposed entry never persists into the FSM + SQLite. - Tests: abusive iter, wrong-length salt, multi-MB hash all fail closed; FSM rejects an oversized hash. API listener (Gemini medium): set NextProtos ["http/1.1"]. Declined the suggested "h2" — Fiber's engine (fasthttp) is HTTP/1.1-only and Fiber's own ListenTLS sets no h2 ALPN, so advertising h2 would let a client negotiate a protocol the server cannot speak. Makefile fips-check (Gemini medium): capture stdout+stderr and check go list's exit status, so a compile/list failure fails the target instead of silently printing OK. Declined (verified): - CurveIDs (Gemini security-high, x3): hallucinated. tls.Config.CurveIDs does not exist in Go 1.26 (unknown field); CurvePreferences is correct, not deprecated, and the stdlib doc states setting it restricts curves. - CodeQL #235 (weak SHA256, auth_hash_legacy.go): pre-existing verify-only backward-compat path relocated unchanged; dismissed won't-fix.
|
Addressed review round 2 (Gemini + CodeQL) in ✅ AcceptedPBKDF2 verify DoS (security-high) —
API listener ALPN (medium) — set ❌ Declined (verified)
CodeQL #235 (weak SHA256, @gemini-code-assist please review |
There was a problem hiding this comment.
Code Review
This pull request introduces an optional FIPS 140-3 build variant (arc-fips) compiled against the CMVP-certified Go Cryptographic Module. It updates the build system, centralizes FIPS posture and TLS hardening, migrates token hashing from bcrypt to standard library PBKDF2, and transitions replication key derivation to standard library HKDF. Feedback on the changes identifies two critical compilation errors in internal/auth/auth.go where the token string is passed directly to pbkdf2.Key instead of being converted to a byte slice.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
Round 3 — both "critical compilation error" findings on This PR uses the stdlib The Compiler evidence (Go 1.26):
CI on this PR builds No change made for these two. (This is the same shape as the round-2 |
…coverage Token validation (HIGH): - applyRotateToken previously accepted NewHash/NewPrefix with only a non-empty check, bypassing the length caps validateTokenEntry enforces on create/restore. A rogue cluster node could rotate a token to a multi-megabyte hash that lands in every node's FSM + SQLite and forces large allocations on each matching auth verify. Extracted a shared validateTokenHashAndPrefix (maxTokenHashLen 512, maxTokenPrefixLen 256) and applied it on BOTH the create and rotate paths so they cannot drift. Tests: rotate rejects an oversized hash and leaves the original unchanged. FIPS CI/release coverage: - FIPS Docker image smoke test: docker-build-fips now boots the pushed :VERSION-fips image and asserts "fips_mode":true (catches packaging defects the binary-level checks can't). - FIPS image Trivy scan: vuln-scan attaches a FIPS-image JSON report. - FIPS image SBOM: sbom emits a FIPS container SPDX (source SBOM is shared — identical Go module graph). - FIPS .deb/.rpm packages: debian-build + rpm-build gain a variant matrix producing arc-fips packages that Conflicts/Provides/Replaces (deb) and Conflicts/Obsoletes/Provides (rpm) the standard arc package — same /usr/bin/arc + arc.service, so an operator installs exactly one. Coexistence directives inserted via a targeted sed to avoid blank-line surgery on the spec/control body. Release notes updated for the new FIPS packages, SBOM, and Trivy artifacts.
The internal/fips package doc pointed at a non-existent "fips_enabled.go". The //go:debug fips140=only directive actually lives in cmd/arc/fips.go (package main, where //go:debug takes effect). Comment-only.
The FIPS-build legacy-hash rejection fired a Warn on every auth attempt that matched a legacy-hashed token's prefix. At Warn this (a) spams logs under repeated auth and (b) acts as a token-existence oracle for anyone observing logs — "token not found" is silent while "found but legacy, rejected" logged. Drop to Debug: still available when actively diagnosing a failed auth, but not emitted into normal log output. No behavior change (still fails closed).
…oS bounds - Legacy-token rejection is logged at debug (matches the code change), not as a warning. - Document the PBKDF2 verify bounds (length guard before decode, iteration cap, salt/key length pins) and the create+rotate length caps on the cluster replication path.
Summary
Tier 2 defense-grade work, item 1 of 4: an optional
arc-fipsbuild for US defense/federal and other regulated environments requiring validated cryptography.26.06.2); built with-tags=duckdb_arrow,fips+GOFIPS140=v1.0.0(the CMVP-certified Go Cryptographic Module snapshot), runningGODEBUG=fips140=only(baked in). FIPS-ness lives in the artifact name (arc-fips-*, image:VERSION-fips), never the version string.arc-fipsbinary; Enterprise FIPS =arc-fips+ license key.What runs through the FIPS module
crypto/pbkdf2). Legacy bcrypt/sha256 hashes verify in the default build; the fips build fails them closed with a "rotate this token" warning (bcrypt is not linked into the fips binary — CI enforces this via an import-graph check).x/crypto/hkdf→ stdlibcrypto/hkdf(inside the FIPS boundary). Arg order preserved; a known-answer test pins the derived key byte-for-byte so a cluster split can't slip through.InsecureSkipVerifyin the fips build. The fips API listener replaces Fiber's opaqueListenTLSso Arc owns the negotiated suites.fips_modein the banner.Build & release
make build-fips/test-fips/fips-check;DockerfileFIPS=1arg.release-build.yml:build-binariesmatrix gains avariantdimension (standard/fips × amd64/arm64) → signedarc-fips-*binaries + bundles; SLSA provenance covers them; newdocker-build-fipsjob pushes a cosign-signed:VERSION-fipsimage to GHCR + Docker Hub. CI verifies nox/crypto/bcrypt|hkdfin the fips build and that it boots withfips_mode=true.CMVP framing (important)
The Go Cryptographic module is CMVP-certified (v1.0.0). Arc itself is not a CMVP-listed module — this is not a claim that "Arc is FIPS 140-3 validated." Docs and release notes state this precisely.
Operator cutover
Moving an existing deployment to
arc-fipsrequires rotating API tokens once (existing bcrypt hashes fail closed; new tokens are PBKDF2). Documented in release notes + the FIPS guide.Test plan
go build+go testpass in both variants (-tags=duckdb_arrowand...,fips)gofmt/go vetclean on changed files, both variantsmake fips-check(import-graph) clean; standard build still pulls bcrypt (backward compat)arc-fipsbinary smoke-booted:fips_mode=true, reaches "Arc is ready!" (CGO + GOFIPS140 coexist)workflow_dispatch test_mode=true) to verifyarc-fips-*artifacts +:VERSION-fipsimageDocs
Release notes (
RELEASE_NOTES_2026.06.2.md), arcREADME.md, enterprise product-definition updated here. User-facing guide (configuration/fips.md) lands in the docs.basekick.net repo.🤖 Generated with Claude Code