feat: add qualified preview HTTP and TLS profiles - #15
Merged
Merged
Conversation
Add tested static-serving and HTTP reverse-proxy configurations with bounded request handling, validated correlation IDs, query-free JSON access events, safe forwarding-header behavior, and explicit upstream failure. Add TLS 1.2/1.3 termination, mandatory mutual-TLS, and verified HTTPS-upstream profiles. An ephemeral CA-issued rehearsal generates all key material outside the repository and exercises protocol bounds, hostname and chain validation, client authentication, leaf renewal, CRL enforcement, overlapping-CA rotation, untrusted roots, missing keys, and secret-safe structured logging. Add a public-metadata TLS lifecycle checker with stable JSON output, 26 unit tests for the structured log schemas and lifecycle boundaries, and native Podman plus Docker compatibility execution under restricted runtime controls on AMD64 and ARM64. All five profiles remain preview/unqualified.
The first execution of the TLS suite failed: the TLS 1.1 and untrusted-chain negative cases make NGINX emit an access event for a connection that never produced a request, so `method`, `uri`, and `protocol` are empty and schema validation rejects the record. Suppressing the event is the correct fix rather than loosening the schema. The documented contract is one access event per application request, and a rejected handshake is not one. An access schema that tolerates empty required fields cannot be validated at all, which would defeat the purpose of the structured format. Gate access logging on a non-empty request method in all five profiles, not only the TLS ones: the same gap exists for any malformed request line or a client that disconnects before its request is read. Failed handshakes stay visible in the error stream, where connection-level failures belong.
The native Podman TLS leg passes, but the Docker compatibility leg could not start the first TLS container: no published port, because NGINX exited before binding. Cause is a genuine difference between the runtimes rather than a test defect. Rootless Podman maps the invoking user onto container GID 0, so a `0640` key owned by the host user is readable by the runtime identity. Rootful Docker preserves host ownership and its container GID 0 is host root, so the same file is unreadable to UID 11001. Setting group 0 on the host would fix Docker and break rootless Podman, because host GID 0 falls outside the rootless subgid mapping. Keep `0640` for the primary runtime and widen the mode only on the compatibility leg. The material is generated per run outside the repository, never committed, and destroyed with the evidence directory. The comment states plainly that this is a harness accommodation and not deployment guidance: deployments make keys readable by the runtime identity through ownership.
This was referenced Sep 16, 2026
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.
Opens Package 3 with five tested configuration profiles under
examples/profiles. All five are preview/unqualified and stay that wayuntil an immutable release and its platform evidence name them as supported.
Profiles
staticGET/HEADonly, no indexes, dot-path denialreverse-proxytls-terminationmutual-tlstls-upstreamShared contract: unprivileged
8080, unlogged/healthz, arbitrary non-rootUID in group
0, all capabilities dropped,no-new-privileges, read-only rootwith only
/tmpwritable,server_tokens off, bounded body size and timeouts.Logging
One JSON access event per request. The event records
$uriand never$request,$request_uri,$args, headers, or bodies, so query strings,credentials, cookies, referrers, user agents, and client-supplied forwarding
chains are structurally absent rather than filtered.
X-Request-IDis acceptedonly as 1-64 constrained ASCII characters and otherwise replaced with a
generated
$request_id. The reverse proxy overwrites any client-suppliedforwarding chain with the direct peer address.
TLS events add protocol, cipher, SNI, session reuse, and verification result,
and omit client certificate identity.
TLS rehearsal
tests/tls.shgenerates every CA and leaf key outside the repository at runtime and exercises protocol bounds, hostname and chain validation, client
authentication, leaf renewal, CRL revocation, overlapping-CA rotation,
untrusted roots, missing keys, and secret-safe diagnostics. No certificate or
private key is committed, and none is generated into the image.
scripts/tls_material.pychecks certificate and CRL deadlines from publicmetadata only, with stable JSON output and fail-closed behavior on malformed
or unreadable input.
Testing
40 new unit tests (51 total) plus native Podman and Docker compatibility
execution of both suites on AMD64 and ARM64 under restricted runtime controls.
Scope
Load balancing, WebSockets, ClickHouse, extended health endpoints, and rate
and connection limiting are not in this PR and remain open Package 3 items.
Production PKI operations, alert delivery, runtime log collection, and
exact-host cryptographic policy remain unqualified and operator-owned.
🤖 Generated with Claude Code