fips: route cryptography through AWS-LC, remove OpenSSL from the dependency tree - #6828
fips: route cryptography through AWS-LC, remove OpenSSL from the dependency tree#6828gz wants to merge 19 commits into
Conversation
mythical-fred
left a comment
There was a problem hiding this comment.
High-level feedback on the draft — architecture and scope; skipping nits by design.
Approach is sound. Pinning rdkafka-sys onto openssl-sys with the aws-lc-fips feature is the right lever to bring Kafka TLS inside the validated boundary, since rdkafka-sys builds librdkafka against whatever openssl-sys resolves. cargo tree -i openssl returning empty is the crisp verification that no path escapes.
Right calls on the touchy bits:
- API key hash equivalence verified byte-for-byte before swapping
openssl::base64::encode_block. This is stored inapi_key.hash— a silent change here would invalidate every existing key on every deployment, so calling it out in the PR body is exactly right. - Preserving both TLS escape hatches via two
ServerCertVerifierimplementations (disable_tls_verify,disable_tls_hostname_verify) rather than dropping features rustls does not natively expose. Users depending on these would be silently broken otherwise. rustls-native-certsinstead of a bundled root set keeps the set of trusted CAs identical to what operators have today.- Dropping the
opensslcrate rather than forking upstream, givenocsp.rs/pkey_ctx.rs/hash.rsreference AWS-LC-absent symbols withoutcfg(awslc)gates — pragmatic and clearly explained.
Behavioural change worth flagging in the changelog explicitly: verify_hostname = false on Postgres now warns and ignores the request rather than actually disabling hostname verification. The tls.rs diff spells this out in the log message, but users who set that flag (probably because their cert genuinely does not match) will find their pipelines start failing on upgrade. That is arguably the correct outcome — the escape hatch was a footgun — but it merits a prominent changelog entry, and possibly a config-validation warning at startup rather than only at connect time. disable_tls_hostname_verify on other surfaces is preserved via the custom verifier, so the inconsistency is inside Postgres alone. Consider whether the Postgres connector should learn the same custom-verifier trick, if only for parity.
The main open question is the one the PR body already names: whether rdkafka-sys genuinely builds librdkafka's C sources against the AWS-LC-FIPS headers on every target platform CI covers. The wiring resolves at the Rust level; the C build is where surprises usually live (missing EVP_* symbols, differing OpenSSL 1.1 vs 3.x API surface, static-vs-dynamic link edge cases on macOS). Would recommend a temporary CI matrix entry that builds dbsp_adapters --features fips on Linux + macOS + a musl target before flipping this out of draft, plus an integration test that actually does a TLS handshake to a Kafka broker under the fips build. Postgres TLS wants the same shape: a live TLS handshake test (client certs, chain, and both verification-disable knobs) under fips-enabled binaries.
Not blocking; this is a draft. Will do a proper line-level pass when marked ready.
|
CI finding: the It arrives transitively through Cargo unifies features, so pinning Finishing this needs
Until then this branch cannot compile with the backend pinned, which is what the draft status reflects. The librdkafka question is still unanswered because the build fails before reaching it. |
c65eae9 to
a0885fc
Compare
mythical-fred
left a comment
There was a problem hiding this comment.
Follow-up to my 2026-08-07 COMMENT on 4a96d2f9bc. The seven new commits address every concern I raised; still draft, so high-level only.
Swagger UI removal (5b6c4f337b) — justification is convincing: utoipa-swagger-ui was the only path pulling reqwest (and with it native-tls) via a build dep, the OpenAPI doc itself is untouched, and nothing external actually depended on the interactive UI. Fine to drop. Worth calling out in the changelog for anyone who bookmarked /swagger-ui/, but no API contract breaks.
Sentry removal (47d97911d9) — the PR body claims it's unused. If that's true this is pure debt removal (three hardcoded DSNs deleted, env vars gone, five crates lighter). The auto-injected trace header on every generated rest-api request also goes away, which is the only externally observable change; nothing downstream should have depended on it. Confirm one more time that no live deployment (cloud, self-managed) is still shipping events to those DSNs before this lands.
CI TLS-impl check (b38b85818d, scripts/validate-crypto-deps.sh) — this is the right shape. It enforces three things the human reviewer would otherwise have to eyeball on every dep bump: (a) openssl/native-tls/boring blocked outright, (b) openssl-sys is only tolerated when it resolves to an aws-lc(-fips)?-sys backend — exactly the invariant that keeps librdkafka on AWS-LC, (c) ring ratchets: the current parents are frozen in RING_ALLOWED and any new pull-in fails. Reads the lockfile, no compile, wired via .pre-commit-config.yaml which ci-pre-mergequeue.yml runs — so it's actually gating merges, not just a local hook. Nice, this is exactly what I was asking for.
Two small notes on the script that aren't blockers, just to think about before ready-for-review:
RING_ALLOWEDis a space-separated string parsed bycase. When you eventually shrink it to zero entries, the loop still works, but consider adding a comment saying "empty this list when the object_store 0.14 upgrade lands across delta-rs/datafusion/buoyant_kernel" so the next person knows the exit criterion.- The blocked-list regex includes
boring-sysin code but the header comment only listsboring. Minor doc/impl drift.
etl fork (ebc68f00fb) — pinned to a specific SHA (91260556931fba2f55db9ebc11775e295c453bc9) which is good, and the commit message states the upstream path: feldera/etl branch sqlx-aws-lc-rs-main carries the same change expressed as a feature (so upstream doesn't have to change their default), "ready to propose upstream." That's the answer I wanted — the fork is a bridge, not a destination. Would be worth opening the sqlx PR sooner rather than later; every week the fork lives is a week where a security fix on sqlx main requires a merge dance. If there's a tracking issue on our side for "unpin etl fork," link it here.
Remaining ring — clearly scoped in the PR body (all reached through object_store 0.13.2 consumers) and the ratchet check will prevent regression. Acceptable to defer.
API-key hash byte-identical check — good that this was explicit. That was the one silent-corruption path in the whole diff.
Approach and scope are sound. When you flip this out of draft I'll do the full checklist pass — the main open item then will be evidence that librdkafka actually links against AWS-LC on all CI targets (the "not yet verified" item you flagged yourself).
9df2590 to
a381617
Compare
Kafka, Postgres TLS and our own openssl calls used system OpenSSL, outside any validated boundary, so enabling FIPS for rustls only covered part of the data plane. The openssl crate advertises an aws-lc-fips feature but does not compile against it: ocsp.rs, pkey_ctx.rs and hash.rs reference OCSP, DH paramgen and EVP_DigestSqueeze, which AWS-LC does not implement, on 0.10.80 and 0.10.81 alike. Dropping the crate avoids that. SHA-256 and AES-256-GCM move to aws-lc-rs, Postgres TLS to tokio-postgres-rustls, self-signed certificate generation to rcgen, and the test RSA key to the rsa crate. openssl-sys stays, pinned to its aws-lc-fips backend, because rdkafka-sys builds librdkafka against whatever it resolves, which is what brings Kafka TLS inside the boundary. The fips feature now selects the validated module for rustls, aws-lc-rs and rcgen together. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
Nobody uses it, and it was the only consumer of utoipa-swagger-ui, whose build dependency on reqwest pulled native-tls and with it the OpenSSL crate. The OpenAPI document itself is unaffected and still served. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
native-tls links OpenSSL on Linux, giving a binary two cryptographic implementations that define the same symbols. Each of these dependencies reached it by defaulting to it, so each now selects rustls explicitly. refinery moves to 0.9.2 because 0.9.0's tokio-postgres feature pulls postgres-native-tls unconditionally, and 0.9.2 adds tokio-postgres-rustls. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
It is unused. Removing it drops three hardcoded DSNs, the FELDERA_SENTRY_ENABLED and SENTRY_ENVIRONMENT variables from integration CI, and the sentry dependency from five crates. The rest-api client is generated, and its build script injected a trace header call into every generated request plus the trait import that backed it; both are gone. feldera-observability keeps its logging, system and FIPS modules. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
AWS-LC keeps OpenSSL's symbol names, so a binary containing both resolves each name to whichever archive the linker reaches first, and memory allocated by one library gets freed by the other. That corrupts the heap and surfaces far from the cause, which is what made it expensive to diagnose. The hook blocks openssl, native-tls and boring, asserts that openssl-sys resolves to an AWS-LC backend rather than the system OpenSSL, and reports ring without failing, since rustls still pulls it through sqlx. It reads the lockfile only, so it compiles nothing and runs in about a second. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
sqlx's tls-rustls feature aliases tls-rustls-ring, and sqlx prefers ring whenever both backends are enabled, so a consumer cannot select aws-lc-rs by adding a feature; the choice has to be made where the dependency is declared. The fork pins the same upstream revision with that one line changed. feldera/etl branch sqlx-aws-lc-rs-main carries the same change against upstream main, expressed as a feature so consumers can pick, ready to propose upstream. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
0.1.2 declared reqwest without default-features = false, so it enabled default-tls, which pulls native-tls and with it the OpenSSL crate. 0.1.3 is built against reqwest 0.13 with rustls pinned explicitly. This was the last dependency reintroducing OpenSSL: the crate, native-tls and hyper-tls are now all absent from the tree, and openssl-sys resolves to the AWS-LC FIPS backend. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
async-nats defaults to ring and offers aws-lc-rs. aws-sigv4 1.4.5 dropped ring entirely, so a lockfile bump suffices. refinery needs no TLS backend at all, because migrations run on a client this crate creates, and its tokio-postgres-rustls feature pinned an older tokio-postgres-rustls that selects ring. reqwest builds without a provider and takes the process default, which every binary installs as aws-lc-rs; fda did not install one, so it does now. object_store 0.14 selects aws-lc-rs where 0.12 used ring. The upgrade renamed one error variant and deprecated Path::child in favour of Path::join, which consumes self rather than borrowing. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
rdkafka-sys otherwise compiles a vendored librdkafka against whatever OpenSSL pkg-config finds, which leaves Kafka TLS on a second cryptographic implementation no matter how the rest of the binary is built. scripts/install-librdkafka.sh builds AWS-LC with BUILD_LIBSSL, then librdkafka against it. Both container images run the script, and so must developers, since the next commit links librdkafka dynamically. Distribution packages are built against OpenSSL and are older than the version rdkafka-sys requires, so they are not a substitute. librdkafka 2.12.1 calls HMAC() without including <openssl/hmac.h>. OpenSSL supplies the declaration transitively through x509.h and AWS-LC does not, so the call would compile as an implicit declaration returning int, truncating the returned pointer. The script patches it; confluentinc/librdkafka#5552 fixes it upstream but is unmerged. The librdkafka version comes from Cargo.lock, where rdkafka-sys names it in its own version as 4.10.0+2.12.1, so the library cannot drift from the crate expecting it. The configure flags mirror the cargo features rdkafka-sys used, and the script asserts each one: losing one is otherwise silent, and a codec or authentication mechanism simply stops being offered. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
rdkafka's ssl feature depends on openssl-sys and configures the vendored librdkafka build. dynamic-linking consumes the library that scripts/install-librdkafka.sh builds against AWS-LC instead, so neither the feature nor the crate is needed, and Kafka TLS stops being the one part of the system on a different cryptographic implementation. The ssl, gssapi, zstd and libz features only ever configured that vendored build; those capabilities now come from the installed library, where the script asserts them. Dropping the default libz feature also removes libz-sys, which was compiling a zlib nothing used. default-features has to be set at the workspace root: a member cannot override it on an inherited dependency. openssl-sys stays listed in Cargo.lock as an optional dependency of rdkafka-sys, but it is no longer in the build graph, so it is neither compiled nor linked. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
openssl-sys can now be blocked outright rather than allowed with an assertion about its backend, since nothing needs it once rdkafka links librdkafka dynamically. The second check is new. aws-lc-sys and aws-lc-fips-sys are separate crates, so cargo cannot unify them, and a tree holding both compiles and links the whole library twice. It happened here: aws-lc-rs selects its backend from the fips feature while openssl-sys selected its own, and the two disagreed. Nothing failed, because the symbol prefixes differ, so the duplication was invisible at run time. Adopting the FIPS module means building AWS-LC with -DFIPS=1 and moving aws-lc-rs to aws-lc-fips-sys in one change. This check is what makes doing only half of it fail. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
1.18.0 is the first release depending on aws-lc-fips-sys 0.14, which added AWS_LC_FIPS_SYS_SYSTEM_DIR and USE_SYSTEM. Earlier versions always compiled AWS-LC from source, so enabling the fips feature would have put Go and cmake back into the image that compiles pipelines, undoing #6822. With 0.14 the validated module can be built once and linked, the way scripts/install-librdkafka.sh already builds AWS-LC for librdkafka. Nothing selects fips yet; this only makes that step possible without a toolchain in the shipped image. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
Nothing in this repository links OpenSSL now that rdkafka consumes a prebuilt librdkafka, so the package looked removable. The cloud repository runs its Rust build in this same image, and its cluster-control-plane crate uses the openssl crate directly, in kubernetes_runner/runner_config.rs. Dropping the package would break that build the moment cloud bumps its image pin, which is far from this change. Also names perl explicitly. scripts/install-librdkafka.sh needs it to patch librdkafka, and it was present only because Ubuntu marks it Essential. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
rdkafka links librdkafka dynamically, so the CI image has to supply it. The previous image predates scripts/install-librdkafka.sh and every Rust job fails in it with "librdkafka 2.12.1 cannot be found on the system". Built from 5b0f47e for amd64 and arm64 on native runners. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
perl arrives with git either way, but awk keeps the script on tools the POSIX base guarantees, and nothing else here used perl. The insertion was validated against librdkafka 2.12.1: one hmac.h include, directly above x509.h. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
The lockfile alone held this version, and a lockfile regenerated while rebasing resolved it back to 1.4.2 twice; the dependency check caught it both times. A declared floor is enforced by the resolver, so a regeneration cannot slide below it. No code here calls the crate, which is why cargo-machete ignores it. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
Nothing in this repository links OpenSSL, and the cloud repository's cluster-control-plane crate no longer uses the openssl crate either (feldera/cloud#1879), so the last consumer of the header package is gone. Also correct the --db-tls-certificate-path doc: without the flag the manager still negotiates TLS when the server offers it, verified against the system trust roots. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
aws-lc-sys 0.44.0 vendors AWS-LC 5.5.0, so the copy inside librdkafka now tracks the one the Rust side links instead of the older v1.68.0 line. Verified on macOS (otool clean) and Ubuntu 24.04 (readelf clean). The no-OpenSSL check now also runs on macOS through otool; readelf does not exist there, so the check silently skipped. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
The image carries librdkafka built against AWS-LC v5.5.0, so CI links the same AWS-LC release the Rust dependency tree pins. Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
Draft. Routes cryptography through AWS-LC so that one implementation serves the whole data plane, and removes OpenSSL from the dependency tree entirely.
Follow-up to #6822, which removed the Go toolchain from the runtime image but deliberately made no compliance claim.
Status
openssl,openssl-sys,native-tlsandhyper-tlsare all absent from the build graph. Kafka, Postgres, object stores and the HTTP clients now share a single AWS-LC.Two things remain open, both described below:
ringis still reached through third-party crates pinned toobject_store0.13.2, and thefipsfeature is selected nowhere, so this is AWS-LC but not its validated build.Why
Enabling FIPS for rustls covered only part of the data plane. Kafka, Postgres TLS and our own
openssl::calls used system OpenSSL, outside any boundary. A pipeline reading from Kafka over TLS did its cryptography in OpenSSL regardless of how the binary was built.The direct route,
openssl's ownaws-lc-fipsfeature, does not compile:ocsp.rs,pkey_ctx.rs:459,476andhash.rs:310referenceOCSP_*,EVP_PKEY_CTX_set_dh_paramgen_*andEVP_DigestSqueeze, which AWS-LC does not implement and which are notcfg(awslc)-gated. Dropping the crate avoids the problem rather than forking upstream.What changes
openssl::shaaws_lc_rs::digest, behind onesha256()helperopenssl::symmaws_lc_rs::aeadpostgres-openssltokio-postgres-rustlsopenssl::x509builderrcgenopenssl::rsarsaDBError::TlsConnectionopenssl::error::ErrorStackStringKafka took a different shape than the rest
rdkafka-syscompiles a vendored librdkafka against whatever OpenSSLpkg-configfinds. Pinning theopenssl-sysbackend does not reach it:rdkafka-sysonly learns AWS-LC's location throughDEP_OPENSSL_ROOT, andopenssl-sysemitscargo:root=solely from its vendored path, so that code never runs.scripts/install-librdkafka.shbuilds AWS-LC and then librdkafka against it, andrdkafkaswitches todynamic-linkingto consume the result. That dropsopenssl-sysfrom the tree altogether, since only rdkafka'ssslfeature required it.Two details that would otherwise bite:
HMAC()without including<openssl/hmac.h>. OpenSSL supplies it transitively throughx509.h; AWS-LC does not, so the call would compile as an implicit declaration returningint, truncating the returned pointer. Fixed upstream in support aws-lc FIPS version confluentinc/librdkafka#5552, unmerged, so the script patches it.rdkafka-sysused, and the script asserts each one. Losing one is silent: a codec or authentication mechanism simply stops being offered.The librdkafka version comes from
Cargo.lock, whererdkafka-sysnames it in its own version as4.10.0+2.12.1, so the library cannot drift from the crate expecting it.One AWS-LC, not two
aws-lc-sysandaws-lc-fips-sysare separate crates, so cargo cannot unify them, and a tree holding both compiles and links the whole library twice. This branch had exactly that for a while:aws-lc-rspicks its backend from thefipsfeature whileopenssl-syspicked its own, and the two disagreed. Nothing failed, because their symbol prefixes differ, so the duplication was invisible at run time. A pre-commit check now rejects it.Getting OpenSSL out took more than dropping our own dependency
native-tlslinks OpenSSL on Linux, and eight dependencies reached it by defaulting to it. Each was invisible until the previous one was removed:reqwestdefault-features = false, rustls selectedrefinerytokio-postgresfeature pullspostgres-native-tlsunconditionally; bumped to 0.9.2schema_registry_converterrustls_tlsrather than the defaultnative_tlsgcloud-pubsubrustls-tlsrather than the defaultdefault-tlspostgresql_embeddedrustlsrather than the defaultnative-tlssentrytransportfeature expands to reqwest plus native-tlsutoipa-swagger-uifeldera-cloud1-clientRemaining ring uses
ringis a second cryptographic implementation and is not FIPS-validated. Removed here:etltls-rustls-aws-lc-rs; sqlx prefers ring when both are enabledasync-natsdefault-features = falseplusaws-lc-rsaws-sigv4object_storeawsfeature selects aws-lc-rsreqwest,rustls-webpki-no-providervariants, deferring to the process defaultWhat remains is third-party and converges on one upgrade:
Moving these means upgrading the datafusion, delta-rs and buoyant_kernel cluster to
object_store0.14 together. A pre-commit check ratchets the list so the remainder shrinks and never grows.FIPS is not enabled
The
fipsfeature is selected by no workflow, Dockerfile or script. Cryptography runs on AWS-LC, but on its unvalidated build.aws-lc-rsis bumped to 1.18.0 to make that step reachable: it is the first release depending onaws-lc-fips-sys0.14, which addedAWS_LC_FIPS_SYS_SYSTEM_DIRandUSE_SYSTEM. Earlier versions always compiled AWS-LC from source, so enablingfipswould have put Go and cmake back into the image that compiles pipelines, undoing #6822. With 0.14 the validated module can be built once and linked, the wayscripts/install-librdkafka.shalready builds AWS-LC.Turning it on means building AWS-LC with
-DFIPS=1and movingaws-lc-rstoaws-lc-fips-sysin the same change; the duplicate-AWS-LC check makes doing only half of it fail.Behaviour preserved deliberately
API key hashes.
openssl::base64::encode_blockwas verified byte-identical to standard padded base64 before the swap. These hashes are stored inapi_key.hash, so a difference would silently invalidate every existing key.Both TLS verification escape hatches.
disable_tls_verifyanddisable_tls_hostname_verifyhave no direct rustls equivalent, so this adds twoServerCertVerifierimplementations: one accepting any certificate, one verifying the chain while tolerating a name mismatch.System trust store.
rustls-native-certsreplacesset_default_verify_paths(), so the certificates trusted by default do not change.Describe Manual Test Plan
librdkafka was built against AWS-LC and verified on macOS arm64, linux/arm64 and linux/amd64. On each, the resulting shared object carries AWS-LC statically, has no
libsslorlibcryptoNEEDED entry, and passes thepkg-config --atleast-versionproberdkafka-sysruns.The
librdkafkastage ofdeploy/Dockerfilewas built locally, and a Rust binary was linked and run against the copied library, reporting2.12.1at run time.cargo check, clippy and the full pre-commit pass.Still to verify in CI and on a cluster:
--precompilecompiling a pipeline inside the release image, Postgres TLS against a TLS-enabled server covering client certificates and both verification flags, connector TLS for Kafka, Delta and S3, and that API keys issued before the change still authenticate after it.Checklist
Breaking Changes?
Describe Incompatible Changes
Kafka and Postgres TLS switch cryptographic implementations. AWS-LC omits algorithms OpenSSL allows, so a connector negotiating a non-approved cipher, or relying on OCSP stapling, may fail where it previously worked.
verify_hostnameon the Postgres connector no longer disables hostname checking; it warns instead, because the rustls connector verifies the name.Building the workspace now requires librdkafka to be installed, via
scripts/install-librdkafka.sh. Distribution packages are built against OpenSSL and are older than the versionrdkafka-sysrequires, so they are not a substitute. Both container images run the script.Sentry is removed, along with the
FELDERA_SENTRY_ENABLEDandSENTRY_ENVIRONMENTvariables and three hardcoded DSNs. The Swagger UI is removed; the OpenAPI document is unaffected and still served.