Skip to content

feat: add qualified preview HTTP and TLS profiles - #15

Merged
joey-huckabee merged 5 commits into
mainfrom
feature/qualified-http-tls-profiles
Sep 16, 2026
Merged

joey-huckabee merged 5 commits into
mainfrom
feature/qualified-http-tls-profiles

Conversation

@joey-huckabee

Copy link
Copy Markdown
Contributor

Opens Package 3 with five tested configuration profiles under
examples/profiles. All five are preview/unqualified and stay that way
until an immutable release and its platform evidence name them as supported.

Profiles

Profile Covers
static Read-only tree, GET/HEAD only, no indexes, dot-path denial
reverse-proxy One explicit HTTP upstream, reviewed headers, explicit failure
tls-termination TLS 1.2/1.3 ingress
mutual-tls Mandatory client certificates with CRL enforcement
tls-upstream HTTPS upstream with chain and hostname verification

Shared contract: unprivileged 8080, unlogged /healthz, arbitrary non-root
UID in group 0, all capabilities dropped, no-new-privileges, read-only root
with only /tmp writable, server_tokens off, bounded body size and timeouts.

Logging

One JSON access event per request. The event records $uri and 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-ID is accepted
only as 1-64 constrained ASCII characters and otherwise replaced with a
generated $request_id. The reverse proxy overwrites any client-supplied
forwarding 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.sh generates every CA and leaf key outside the repository at run
time 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.py checks certificate and CRL deadlines from public
metadata 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

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.
@joey-huckabee
joey-huckabee merged commit 12c79f3 into main Sep 16, 2026
7 checks passed
@joey-huckabee
joey-huckabee deleted the feature/qualified-http-tls-profiles branch September 16, 2026 03:29
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