test: qualify TLS on the S3 listener, and pin a configuration hazard - #12
Merged
Merged
Conversation
Every other suite in this repository drives the S3 API over plain HTTP, so nothing had shown that the image can terminate TLS at all, let alone that a client verifying a private CA gets what it asked for. The check that matters is the negative one. A client trusting only an unrelated CA is refused at the handshake, which is what makes the successful round trip mean something: a connection that succeeds for a client trusting everything says nothing about the server's identity. A hostname the certificate does not cover is also refused, so the certificate needs a matching subjectAltName rather than a common name, which modern clients ignore. To test that honestly the client has to ask for the name on the certificate rather than for 127.0.0.1 with verification turned off, so the suite resolves the certificate's hostname to the published loopback port instead of sidestepping the check it exists to make. Supplying a certificate and key with no -port.https upgrades the listener: the same port stops answering plain HTTP. Adding -port.https does something else entirely, and this is the finding worth carrying forward. It does not move TLS to a second port. It starts TLS there and leaves the original port serving the S3 API in the clear. With a certificate, a key and -port.https=8334 the container listens on 8333, 8334 and 18333, and the plaintext port really does serve the API -- proven by driving it, not inferred from an open socket. An operator reaching for the flag to enable HTTPS ends up publishing both. Whether the entrypoint should refuse that combination is left as a decision for a human rather than settled here. It is a real footgun, and it is also a legitimate migration shape, and this project has a pattern for that tension: refuse by default with an opt-out. Choosing it is not mine to do unprompted.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the last gap in the client-facing security story. Until now every suite
drove the S3 API over plain HTTP, so nothing showed the image could terminate TLS
at all.
New:
tests/s3-tls.sh,tests/lib/tlschecks.py;S3Clientgained HTTPS with aconfigurable trust anchor.
The check that makes the others mean something
A client trusting only an unrelated CA is refused at the handshake. Without
that, a successful round trip proves only that bytes moved — a client trusting
everything connects to anything. A hostname the certificate does not cover is
also refused, so the certificate needs a matching
subjectAltName; a common namealone is ignored by modern clients.
Testing that honestly meant the client had to ask for the name on the
certificate, not
127.0.0.1with verification off. The suite resolves thecertificate's hostname to the published loopback port rather than sidestepping
the check it exists to make.
Supplying a certificate and key with no
-port.httpsupgrades the listener:the same port stops answering plain HTTP.
The hazard
With
-cert.file,-key.fileand-port.https=8334, the container listens on8333,8334and18333— and8333really does serve the API, proven bydriving it rather than inferred from an open socket. An operator reaching for the
flag to "enable HTTPS" publishes both.
I have not added a guard for this. It is a genuine footgun and a legitimate
migration shape, and this project has a pattern for that tension — refuse by
default with an opt-out. Choosing to apply it is a decision I have recorded for
you rather than made unprompted; it is
decision 2.
Validation
pre-commit run --all-files). Clean.evidence rather than relying only on green status checks. I also re-ran
tests/s3.shafter changing the shared client, to confirm the HTTPS supportdid not disturb the plain-HTTP path.
where a guard is supposed to refuse. Three of the five TLS checks are
negative: wrong CA, wrong hostname, and plaintext on the TLS port.
CHANGELOG.md, checked off the package4 item, and added the open decision.
threat, rationale, compensating control, owner, and expiry.
Security and release impact
Test and documentation only; no image or runtime change.
docs/TLS.mdnow carriesa working TLS configuration and a warning box for the hazard.
Accepted findings: the
-port.httpscombination, documented and left unguardedpending your decision.
One change to the shared client worth noting: a refused TLS handshake now returns
status
0with the reason in the body instead of raising. A handshake refusal isa result these checks care about, not a crash — but it means callers must treat
0as "no HTTP response", which the checks do explicitly.Still open in package 4
The Iceberg round trip against
lakekeeper-ubi, mTLS rejection behaviour,certificate rotation, HTTPS on the internal listeners, replication and durability,
backup and restore, and the filer backend decision.
Upstream verification
Verified against SeaweedFS
4.46inweed/command/s3.go:-cert.filewith-port.https == 0serves the main port over TLS and does not start a plaintextlistener, while a non-zero
-port.httpsstarts TLS there and leaves the plainport running. Also noted for later:
-iamdefaults totrue, putting an embeddedIAM API on the same port as the S3 API.