Skip to content

[snapshot] Stop the safe HTTP client from skipping TLS verification - #464

Open
kkozoriz wants to merge 12 commits into
mainfrom
fix/libsaferequest-tls-verification
Open

[snapshot] Stop the safe HTTP client from skipping TLS verification#464
kkozoriz wants to merge 12 commits into
mainfrom
fix/libsaferequest-tls-verification

Conversation

@kkozoriz

@kkozoriz kkozoriz commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Problem

SafeClient.SetTLSCAData (pkg/libsaferequest/client) merged a trust pool - system roots, the supplied CA, the kubeconfig CA - but never reset the caller's inherited insecure-skip-tls-verify / tls-server-name. With Insecure still set, Go skips certificate verification entirely no matter how large RootCAs is, so on the d8 data export/import transports the kubeconfig bearer token was handed to whatever endpoint answered that address.

This is the shared pkg/libsaferequest twin of the fix made for internal/snapshot/transport in #462. Stacked on #463.

Fix

  • Force Insecure = false and clear ServerName unconditionally - on the rest.Config and on the transport clone, because client-go bakes the insecure flag into the base transport before WrapTransport runs.
  • Chain a previously installed WrapTransport instead of clobbering it.
  • Return the input RoundTripper for non-*http.Transport types instead of a typed-nil *http.Transport that nil-panics on RoundTrip.

Before / After

Probe: a rest.Config with insecure-skip-tls-verify: true and a bearer token, SetTLSCAData(nil) exactly as the publish path calls it, pointed at a self-signed TLS server that is in no trust store.

BEFORE (f9063c791)

probe: safe HTTP client against an untrusted TLS endpoint
  kubeconfig    : insecure-skip-tls-verify: true, bearer token set
  publish path  : SetTLSCAData(nil)  (ingress cert, no CA to pin)
  endpoint      : https://127.0.0.1:61064  (self-signed, in no trust store)

  request       : HTTP 200 OK
  body          : impersonated exporter payload
  endpoint read : Authorization: Bearer d8-demo-toke...

  VERDICT: LEAKED - certificate verification never ran, the token was handed over

AFTER (68ba3d1f1)

  request       : rejected
  error         : basic/token auth do request: Get "https://127.0.0.1:61073/data":
                  tls: failed to verify certificate: x509: certificate signed by unknown authority

  VERDICT: SAFE - the certificate was verified and the token was never sent

Validation

The probe above is built from the same source in two isolated git worktrees, one at each commit, and run in both - so the only difference is this diff. The exploit was also reproduced by hand against both trees during review.

Tests

  • pkg/libsaferequest/client/http_test.go - verification forced on for nil / valid / kubeconfig CA data, invalid CA bytes, client cert survival, WrapTransport chaining, repeated calls, non-transport pass-through

… client

Add ValidateHTTPSURL (transport) and NewSafeClientForConfig (safe client)
as the foundational pieces needed to build an HTTPS client against a
published (ingress) DataImport endpoint.

Signed-off-by: Konstantin Kozoriz <konstantin.kozoriz@flant.com>
Add spec.publish to the DataImport built by d8 snapshot upload, align
it alongside spec.ttl on reuse, wait on status.publicURL when publish
is enabled, and switch to a merged TLS trust pool with 401/403
diagnostics on that path.

Signed-off-by: Konstantin Kozoriz <konstantin.kozoriz@flant.com>
Wire the flag through to the DataImport importer, auto-detecting the
upload mode when unset, and document the bearer-token requirement for
the published path.

Signed-off-by: Konstantin Kozoriz <konstantin.kozoriz@flant.com>
SetTLSCAData merged a trust pool but never reset the caller's inherited
insecure-skip-tls-verify/tls-server-name, so Go skipped certificate
verification entirely on that path — any endpoint could receive the
real Kubernetes bearer token. Force verification on unconditionally,
in both the rest.Config and the cloned transport, since client-go may
already have baked the insecure flag into a base transport before this
wrapper runs.

Signed-off-by: Konstantin Kozoriz <konstantin.kozoriz@flant.com>
Add spec.publish to the DataExport built by d8 snapshot download,
upgrade it (one-way, optimistic-locked) on an adopted CR, wait on
status.publicURL when publish is enabled, and switch to a merged TLS
trust pool with 401/403 diagnostics on that path.

Signed-off-by: Konstantin Kozoriz <konstantin.kozoriz@flant.com>
Signed-off-by: Konstantin Kozoriz <konstantin.kozoriz@flant.com>
Signed-off-by: Konstantin Kozoriz <konstantin.kozoriz@flant.com>
SetTLSCAData merged a trust pool but never reset the caller's inherited
insecure-skip-tls-verify, so certificate verification was a no-op on the
d8 data export/import transports and the kubeconfig bearer token could be
sent to any endpoint answering that address. Force verification on and
clear the inherited tls-server-name at both levels — the rest.Config and
the transport clone — since client-go bakes the insecure flag into the
base transport before WrapTransport runs.

Also chain a previously installed WrapTransport instead of clobbering it,
and return the input RoundTripper rather than a typed-nil *http.Transport
for non-transport RoundTrippers. Both mirror the already-fixed twin in
internal/snapshot/transport, which is now cross-referenced from each side.

Signed-off-by: Konstantin Kozoriz <konstantin.kozoriz@flant.com>
…uble-call chaining

SetTLSCAData's existing 4 tests already caught line-by-line regressions on
the Insecure/ServerName force and the prev-chain, but left three edge cases
unexercised: garbage/empty caData (AppendCertsFromPEM silently ignores bad
input, must not panic or leave verification off), CertData/KeyData/CertFile/
KeyFile client-cert fields (untouched by the function, worth pinning), and
a second SetTLSCAData call on the same client (prev-chaining must not turn
self-referential and recurse forever).

Signed-off-by: Konstantin Kozoriz <konstantin.kozoriz@flant.com>
…ubtest name

Signed-off-by: Konstantin Kozoriz <konstantin.kozoriz@flant.com>
@kkozoriz kkozoriz changed the title [snapshot] libsaferequest tls verification [snapshot] Stop the safe HTTP client from skipping TLS verification Aug 30, 2026
Signed-off-by: Konstantin Kozoriz <konstantin.kozoriz@flant.com>
@kkozoriz
kkozoriz marked this pull request as ready for review August 31, 2026 18:10
@kkozoriz
kkozoriz requested a review from ldmonster as a code owner August 31, 2026 18:10
@kkozoriz kkozoriz self-assigned this Aug 31, 2026
…463

The team merges with "Squash and merge", which drops the #462 -> #463 -> #464
ancestry. Git then falls back to origin/main as the merge base and sees #462's
changes -- already present here -- as competing additions, producing two
conflicts that a plain merge chain never hits:

  CONFLICT (content): internal/snapshot/transport/http.go
  CONFLICT (add/add): pkg/libsaferequest/client/http_test.go

Both were purely structural, so reshape them instead of changing behaviour:

  - http_test.go is created by #462 too, so an add/add conflicts unless both
    sides match byte for byte. Restore it to #462's exact content and move the
    7 SetTLSCAData tests and their 3 helpers to a new http_tls_test.go, which
    exists on neither side of main and merges as a clean single-side add.

  - The "keep in sync" note sat at the end of a doc comment block #462 had just
    added, so the two additions were adjacent and conflicted. Move it just
    inside SetTLSCAData, a region #462 does not touch.

No production code changes: pkg/libsaferequest/client/http.go is untouched and
internal/snapshot/transport/http.go now matches #463 apart from the relocated
comment. All 8 tests are preserved and still pass under -race.

Verified from origin/main: squash #462, squash #463, merge #464, merge #465 --
all four clean, then build, vet and go test -race clean over
./internal/snapshot/... and ./pkg/libsaferequest/...

Signed-off-by: Konstantin Kozoriz <konstantin.kozoriz@flant.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant