Skip to content

feat(tls): single-source and activate neutral TLS + management surface - #138

Merged
cuioss-oliver merged 22 commits into
mainfrom
feature/plan-31b-neutral-tls-config-surface
Aug 1, 2026
Merged

feat(tls): single-source and activate neutral TLS + management surface#138
cuioss-oliver merged 22 commits into
mainfrom
feature/plan-31b-neutral-tls-config-surface

Conversation

@cuioss-oliver

@cuioss-oliver cuioss-oliver commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

The gateway's declared TLS floor and cipher-suite allowlist were inert in production — the
neutral gateway.yaml tls keys had zero production consumers, so neither side of the apparent
"double source" was actually in force. This PR makes the neutral surface the single effective
source: gateway.yaml's TLS policy is now bound onto the terminated listener through the
HttpServerOptionsCustomizer seam (TlsServerCustomizer, sibling of the existing
MtlsServerCustomizer), and the raw quarkus.* duplicates that were never doing anything are
deleted outright. The management interface gets a first-class neutral management: policy block
projected onto quarkus.management.* by a single-ordinal (>300) SmallRye ConfigSource
(NeutralTlsConfigSource) — the seam the management path actually enumerates, since
VertxHttpRecorder.initializeManagementInterface never consults HttpServerOptionsCustomizer
beans. The plain-HTTP management opt-out is a loud WARN, never a boot refusal, and is proven on a
dedicated sixth compose gateway instance (api-sheriff-plain-mgmt). ADR-0025 extends ADR-0011's
neutral-name ruling from the JWKS trust surface to the whole server-TLS surface, with a three-way
policy / deployment-bound / build-time classification.

Changes

  • D1 — Bind the neutral gateway.yaml TLS block to the terminated listener, delete the inert raw keys, relocate the build-time knob

    • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizer.java (new): maps tls.min_version, tls.cipher_suites, tls.alpn onto the real HttpServerOptions for the terminated main listener; every mapping is a no-op when its key is absent.
    • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/model/TlsConfig.java: adds the cipherSuites component.
    • api-sheriff/src/main/resources/schema/gateway.schema.json: adds tls.cipher_suites.
    • api-sheriff/src/main/resources/application.properties: deletes quarkus.tls.protocols, quarkus.tls.cipher-suites, quarkus.tls.alpn, quarkus.http.ssl.client-auth (all proven inert or redundant against the shipped quarkus-vertx-http-3.37.4 bytecode); relocates quarkus.ssl.native to api-sheriff/pom.xml's native profile (it is a build-time GraalVM knob); retains quarkus.http.ssl-port and quarkus.http.insecure-requests as deliberately deployment-bound.
    • Tests: TlsServerCustomizerTest (new), MtlsServerCustomizerTest, ConfigModelContractTest, ConfigLoaderTest updated for the new arity/coverage; config/valid/gateway.yaml and the test application.properties fixture reconciled so tests and production take the same branch through HttpServerOptionsUtils.getTlsConfiguration's registry-default guard.
  • D2 — Neutral management policy block and the projecting ConfigSource

    • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/model/ManagementConfig.java (new): neutral management: block, TLS policy only — no port component (see Scope Deviation Accepted below).
    • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/NeutralTlsConfigSource.java (new) + META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider (new): single-ordinal (>300) SmallRye ConfigSource parsing gateway.yaml's tls:/management: blocks directly (it runs before CDI).
    • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/model/GatewayConfig.java: adds the management component.
    • api-sheriff/src/main/resources/schema/gateway.schema.json: adds the management object; port is declared solely to be refused ("not": {} plus a sibling errorMessage), landing the actionable rejection at the exact /management/port JSON Pointer.
    • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/load/ConfigLoader.java: enables the validator's errorMessage extension.
    • Tests: ConfigLoaderTest gains the R7 gate (rejectsManagementPortNamingTheDeploymentKnobAtItsOwnPointer plus its matched positive control); NeutralTlsConfigSourceTest (new); ConfigModelContractTest, SheriffMetricsTest updated for the new arity.
  • D3 — Audited plain-HTTP management opt-out, proven on a sixth compose instance

    • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/ConfigLogMessages.java: adds WARN.MANAGEMENT_PLAIN_HTTP (ApiSheriff-115).
    • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/ManagementPlainHttpAudit.java (new): @ApplicationScoped StartupEvent observer emitting the WARN on the observed effective state, eagerly activated and actively invoking its collaborator to defeat the lazy-CDI-proxy trap (lesson 2026-07-20-18-002).
    • api-sheriff/src/main/resources/application.properties: declares the key-less named TLS bucket the opt-out selects, unconditionally (not %it-scoped).
    • integration-tests/docker-compose.yml: adds the sixth gateway service api-sheriff-plain-mgmt (publishing 19005:9000, setting QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME, omitting the management cert env pair); updates the "all five instances" lockstep comments to describe the deliberate exception.
    • integration-tests/scripts/start-integration-container.sh: adds a dedicated http:// readiness gate block (not folded into the existing https://-hard-coded loop).
    • Tests: ManagementPlainHttpAuditTest (new, real startup-event path), ManagementPlainHttpOptOutIT (new), ManagementPlainHttpActivationWiringTest (new, no-Docker descriptor-parsing test with the five HTTPS instances as the matched negative control).
    • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizer.java (commit 50abc37, post-outline finalize finding): tls.cipher_suites now validates every declared suite against SSLContext.getDefault().getSupportedSSLParameters().getCipherSuites() and aborts boot on an unsupported/mistyped entry, symmetric with the existing min_version/alpn fail-closed checks.
  • D4 — ADR-0025

    • doc/adr/0025-The_whole_server-TLS_surface_is_neutral_in_gatewayyaml_and_bound_by_exactly_two_seams.adoc (new): extends ADR-0011's neutral-name ruling to the whole server-TLS surface; records the policy / deployment-bound / build-time three-way classification, both binding seams (HttpServerOptionsCustomizer for the terminated listener, the projecting ConfigSource for management), and the rejected ManagementConfig.tlsConfigurationName()-as-fallback candidate.
  • D5 — Documentation and the machine-asserted completion bar

    • doc/configuration.adoc, doc/architecture.adoc, doc/LogMessages.adoc (adds ApiSheriff-115 and the previously-undocumented ApiSheriff-106), doc/user/tls-edge.adoc, doc/user/README.adoc, doc/user/environment-variable-overrides.adoc (new): the three-layer documentation of the neutral TLS surface, the management opt-out, and the secure-default-vs-override-route audit of the config surface.
    • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/SingleSourceTlsContractTest.java (new): parses the shipped application.properties and mechanically asserts no raw server-TLS quarkus.* policy key remains, with an explicit allowlist for the two named exceptions, plus the quarkus.ssl.native absent-here/present-in-pom.xml-native-profile pair.
    • integration-tests/src/main/docker/sheriff-config/gateway.yaml: the primary IT instance now declares min_version and cipher_suites so the stack exercises a gateway.yaml-sourced protocol/cipher setting end-to-end.

Scope Deviation Accepted

Two scope deviations from the original outline, both recorded in this plan's decision.log under
the (scope-deviation:accept) marker and reflected in the code as shipped:

  1. D2/D3 reshaped after research. The outline originally called for BOTH a schema
    hard-rejection of management.port AND a ConfigValidator rule with an actionable message.
    ConfigLoader aborts on schema failure before binding ever runs, so the validator rule would
    have been unreachable dead code. Research against the quarkusio/quarkus 3.37 branch
    established that (a) quarkus.management.tls-configuration-name pointed at a key-less named
    TLS bucket is a genuine, runtime-overridable, native-safe off-switch for management TLS with a
    legible ConfigurationException on misuse, and (b) the actionable rejection message belongs in
    the JSON schema at its exact JSON Pointer via the networknt errorMessage extension.
    Resolution: management HTTPS stays the default on deployment-bound port 9000; the
    originally-specified bespoke gateway.yaml acknowledgement key was dropped in favour of the
    Quarkus-native QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME; WARN 115 is retained but triggers
    on observed effective state at startup, not on a declared key.
  2. The neutral management block declares no port. Its only route into Quarkus would be the
    same single-ordinal (>300) config source, where it would silently outrank a deployment-set
    QUARKUS_MANAGEMENT_PORT. ConfigValidator/schema instead refuses a management.port key with
    a message naming the real knob (quarkus.management.port / QUARKUS_MANAGEMENT_PORT).

Two Corrections to the Original Spec, Established by Evidence

  • The spec framed the problem as a "double source" where gateway.yaml and quarkus.* both
    configured the same concepts. In fact the neutral tls: keys had zero production consumers
    — parsed, schema-validated, then never applied. The declared TLS 1.2 floor and cipher allowlist
    were not in force in production. This was a live security defect, not a
    configuration-tidiness problem. It hid because the test fixture supplied its certificate through
    quarkus.tls.key-store.pem.* (selecting the TLS-registry listener path), while production
    supplies it through quarkus.http.ssl.certificate.* — so tests and production took opposite
    branches through HttpServerOptionsUtils.getTlsConfiguration's registry-default guard.
  • The spec named HttpServerOptionsCustomizer as the binding seam for the management interface.
    Verified against quarkus-vertx-http-3.37.4 bytecode: VertxHttpRecorder enumerates those beans
    only on the main-server path, never for the management interface. ADR-0025 records the correct
    seam (the projecting ConfigSource).

Verification

  • verify -Ppre-commit and verify -Pcoverage are green.
  • The full unit suite is green.
  • The containerised Docker IT suite was run against a freshly-built native image: 89 completed, 0
    failures, 0 errors, 0 skipped. It was run twice more after later commits, green each time.
  • The first containerised run found a real defect that every other gate missed: the new
    api-sheriff-plain-mgmt instance crash-looped with "Port 8443 seems to be in use" because it
    mounts the shared sheriff-config (which declares passthrough_sni, starting the SNI front
    listener on 8443) without the QUARKUS_HTTP_SSL_PORT=8444 move. Fixed in commit bc3d622.
  • The finalize security audit found that tls.cipher_suites had no fail-closed validation. A probe
    against a real Vert.x HTTPS listener established that a bogus suite name lets server.listen()
    succeed — the gateway boots green and then resets every TLS handshake. Fixed in commit 50abc37
    with symmetric boot-failure coverage for all three TLS knobs.

Known-Open Findings (out of this plan's footprint, not fixed here)

Both are recorded as findings and documented in doc/user/environment-variable-overrides.adoc
rather than fixed, because both are outside this plan's server-TLS-configuration footprint:

  • 4b0102QUARKUS_HTTP_SSL_PROTOCOLS on the api-sheriff-mtls IT instance works only
    while that overlay's gateway.yaml declares no tls.min_version; adding one there would
    silently shadow the environment pin.
  • df7bd1 — what the token-sheriff extension's readiness check reports in a deployment that
    configures gateway.yaml issuers but no sheriff.token.issuers.* was not established by reading
    and is flagged as explicitly inconclusive.

Related Issues

None.


Generated by plan-finalize skill

Summary by CodeRabbit

  • New Features

    • Added configurable TLS policies for the public listener, including minimum versions, cipher suites, ALPN, and mTLS.
    • Management interfaces now use HTTPS by default, with an explicit plain-HTTP opt-out and security warning.
    • Added validation for unsupported TLS settings and rejected management-port configuration.
  • Documentation

    • Added TLS edge, environment override, architecture, and configuration guidance.
    • Documented deployment ownership of ports and certificate material.
  • Bug Fixes

    • Improved operator-facing configuration validation messages and sanitized WebSocket security logging.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @cuioss-oliver, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cuioss-oliver, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 44 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository: cuioss/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0c4e1ffe-c86e-496b-ac9a-04f408d87af6

📥 Commits

Reviewing files that changed from the base of the PR and between be0271a and dd502ba.

📒 Files selected for processing (5)
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/NeutralTlsConfigSource.java
  • doc/adr/0025-The_whole_server-TLS_surface_is_neutral_in_gatewayyaml_and_bound_by_exactly_two_seams.adoc
  • integration-tests/docker-compose.yml
  • integration-tests/scripts/start-integration-container.sh
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/CipherSuiteFixtureWiringTest.java
📝 Walkthrough

Walkthrough

The gateway now defines TLS policy in gateway.yaml, applies it to the terminated HTTPS listener, and projects management TLS policy through MicroProfile Config. It adds management HTTP auditing, schema validation, native SSL configuration, documentation, deployment wiring, and integration coverage.

Changes

TLS and management configuration

Layer / File(s) Summary
Configuration contract and validation
api-sheriff/src/main/java/..., api-sheriff/src/main/resources/..., api-sheriff/src/test/java/...
Adds management TLS and cipher-suite model fields, schema validation, deployment-bound port rejection, and native-profile SSL configuration.
Terminated-listener TLS binding
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizer.java, api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/*
Applies TLS version, cipher-suite, and ALPN settings with fail-closed validation.
Management TLS projection and audit
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/NeutralTlsConfigSource.java, api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/ManagementPlainHttpAudit.java, api-sheriff/src/test/java/...
Projects the plain-management configuration and audits the effective management HTTP or HTTPS state at startup.
Plain-management deployment coverage
integration-tests/*
Adds a dedicated plain-management Compose service, readiness polling, wiring checks, HTTP health checks, HTTPS rejection checks, and warning-log assertions.
TLS architecture and operating documentation
doc/*, .plan/project-architecture/api-sheriff/enriched.json
Documents TLS ownership, precedence, validation, management operation, environment overrides, and build-time settings.

WebSocket log catalogue

Layer / File(s) Summary
WebSocket log message updates
doc/LogMessages.adoc
Removes upstream detail, replaces raw WebSocket origins with bounded dispositions, and moves the idle-timeout message to warning level.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: single-source TLS configuration and activation of the management surface.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cuioss-review-bot

cuioss-review-bot Bot commented Jul 31, 2026

Copy link
Copy Markdown

PR Reviewer Guide 🔍

(Review updated until commit dd502ba)

🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

False Boot Refusal

negotiableCipherSuites(protocol) attempts to isolate suites unique to a protocol by subtracting cipher suites supported by lower TLS protocols. For TLSv1.2, subtracting suites supported in TLSv1.1 removes valid TLS 1.2 CBC cipher suites (such as TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384). If tls.min_version is set to 1.2 and tls.cipher_suites contains valid TLS 1.2 CBC suites alongside TLS 1.3 suites, Collections.disjoint(negotiable, declaredSuites) evaluates to true. This causes assertEveryEnabledProtocolNegotiates to throw an IllegalStateException and abort application boot with a false-positive error despite the configuration being valid and negotiable.

private static Set<String> negotiableCipherSuites(String protocol) {
    Optional<SSLParameters> parameters = defaultParametersOf(protocol);
    if (parameters.isEmpty()) {
        return Set.of();
    }
    Set<String> suites = new LinkedHashSet<>(Arrays.asList(parameters.get().getCipherSuites()));
    for (String lower : parameters.get().getProtocols()) {
        if (!lower.equals(protocol)) {
            defaultParametersOf(lower)
                    .ifPresent(lowerParameters -> Arrays.asList(lowerParameters.getCipherSuites())
                            .forEach(suites::remove));
        }
    }
    return suites;
}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (2)
api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/MtlsServerCustomizerTest.java (1)

107-124: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

This test duplicates enabledRequiresClientAuthWithTrustAnchor and cannot observe the property it names.

The act and the two assertions at lines 118-123 are identical to enabledRequiresClientAuthWithTrustAnchor (lines 48-61). The only addition is the pre-assertion at lines 114-115. That pre-assertion reads a bare Vert.x HttpServerOptions, which never carries a Quarkus property in a plain unit test. So it asserts the Vert.x library default, not that quarkus.http.ssl.client-auth was deleted.

The stated behaviour is "no raw quarkus.http.ssl.client-auth default is needed". The mechanism that would prove it is the content of api-sheriff/src/main/resources/application.properties and api-sheriff/src/test/resources/application.properties. Neither is reachable from here.

Assert the absence where it is observable. A contract test that scans those two property files for quarkus.http.ssl.client-auth proves the single-source claim; this test does not. The PR already has SingleSourceTlsContractTest, which looks like the correct home.

As per path instructions: "Report a stated behaviour whose mechanism is absent, and name the file that would have to contain it. Prose, a code comment, or a summary table is not a mechanism."

Source: Path instructions

api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/NeutralTlsConfigSource.java (1)

88-94: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Derive the plain-management bucket reference from a single definition. PLAIN_MANAGEMENT_BUCKET, quarkus.tls.plain-management.reload-period, QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME=plain-management, and the integration test string are each written independently. The opt-out contract only needs one change to name a missing bucket if they drift; source them from the Java constant or the application.properties bucket key instead.

Source: Path instructions


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: cuioss/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d230750c-030a-4dad-8c54-c9aa59adfc84

📥 Commits

Reviewing files that changed from the base of the PR and between b903526 and d73807e.

📒 Files selected for processing (37)
  • .plan/project-architecture/api-sheriff/enriched.json
  • api-sheriff/pom.xml
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/ConfigLogMessages.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/NeutralTlsConfigSource.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/load/ConfigLoader.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/model/GatewayConfig.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/model/ManagementConfig.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/model/TlsConfig.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/ManagementPlainHttpAudit.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizer.java
  • api-sheriff/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider
  • api-sheriff/src/main/resources/application.properties
  • api-sheriff/src/main/resources/schema/gateway.schema.json
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/NeutralTlsConfigSourceTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/SingleSourceTlsContractTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/load/ConfigLoaderTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/model/ConfigModelContractTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/quarkus/SheriffMetricsTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/ManagementPlainHttpAuditTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/MtlsServerCustomizerTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizerTest.java
  • api-sheriff/src/test/resources/application.properties
  • api-sheriff/src/test/resources/config/testboot/gateway.yaml
  • api-sheriff/src/test/resources/config/valid/gateway.yaml
  • doc/LogMessages.adoc
  • doc/adr/0025-The_whole_server-TLS_surface_is_neutral_in_gatewayyaml_and_bound_by_exactly_two_seams.adoc
  • doc/architecture.adoc
  • doc/configuration.adoc
  • doc/user/README.adoc
  • doc/user/environment-variable-overrides.adoc
  • doc/user/tls-edge.adoc
  • integration-tests/docker-compose.yml
  • integration-tests/pom.xml
  • integration-tests/scripts/start-integration-container.sh
  • integration-tests/src/main/docker/sheriff-config/gateway.yaml
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/ManagementPlainHttpActivationWiringTest.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/ManagementPlainHttpOptOutIT.java

Comment thread api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizer.java Outdated
Comment thread api-sheriff/src/test/resources/config/testboot/gateway.yaml
Comment thread doc/architecture.adoc
Comment thread doc/configuration.adoc Outdated
Comment thread doc/configuration.adoc Outdated
@cuioss-oliver

Copy link
Copy Markdown
Collaborator Author

Triage dispositions

In reply to comment_id: PRR_kwDOPatrT88AAAABH77Nmw

Both nitpicks reviewed; one is being fixed and one is declined with reason. (1) MtlsServerCustomizerTest - agreed, and your diagnosis is exact: the pre-assertion reads a bare Vert.x HttpServerOptions and therefore asserts the Vert.x default, not that quarkus.http.ssl.client-auth was deleted. You also identified the right home. Worth noting it is already covered there: SingleSourceTlsContractTest lists 'quarkus.http.ssl' in SERVER_TLS_NAMESPACES deliberately without a trailing dot, and quarkus.http.ssl.client-auth is neither material nor a sanctioned exception - so reintroducing it into the default profile already fails that test today. The remaining method is therefore a pure duplicate of enabledRequiresClientAuthWithTrustAnchor and is deleted in TASK-15; no new assertion is added, since that would duplicate the existing bar. (2) plain-management bucket reference - declining. Unlike the hardcoded lists elsewhere in this PR (which we are fixing), this duplication is not silent: ManagementPlainHttpActivationWiringTest reads the shipped application.properties AND docker-compose.yml at test time and fails the build if the bucket declaration or the per-service selection drifts. The mechanism that would catch the drift already exists, so sourcing the test literal from the Java constant would add a compile-classpath coupling from integration-tests to api-sheriff for no additional guarantee. See the follow-up commit on this branch.

In reply to comment_id: IC_kwDOPatrT88AAAABMnhqQg

Valid. Set.of(E...) does throw IllegalArgumentException on a duplicate element, and getSupportedSSLParameters().getCipherSuites() is a read across a pluggable-provider boundary - a custom or aliasing JSSE provider can return duplicates. The failure mode is the wrong one: a context-free IllegalArgumentException from inside boot instead of either working normally or producing the intended actionable IllegalStateException. Will be addressed by TASK-13 (same file, same method): the construction becomes a duplicate-tolerant copy, with the existing NoSuchAlgorithmException branch and its message left unchanged. See the follow-up commit on this branch.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
doc/user/environment-variable-overrides.adoc (1)

374-379: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add an automated TLS-key drift check.

doc/user/environment-variable-overrides.adoc says reused bare TLS keys now fail the build, but the build is ./mvnw clean install / verify, and no CI workflow or test runs an assertion that those keys must be absent or covered in gateway.yaml. Keep the text accurate by adding that check or rewriting it as a manual requirement. Apply the same check or wording change to the parallel conclusion in doc/configuration.adoc.

Source: Path instructions

integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/ManagementPlainHttpActivationWiringTest.java (1)

123-126: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject bare Compose environment entries.

The predicate misses a bare QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME list item. Docker Compose resolves a bare key from the host shell, so a set host value can activate plain management while this negative test still passes. Also reject bare entries before checking for KEY=value.

Proposed matcher
-            assertFalse(environment.stream().anyMatch(e -> e.startsWith("QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME=")),
+            assertFalse(environment.stream().anyMatch(e ->
+                    e.equals("QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME")
+                            || e.startsWith("QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME=")),

Sources: Path instructions, MCP tools

🧹 Nitpick comments (1)
api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizer.java (1)

211-216: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Extend the cipher-suite error path to supported-but-not-default-enabled suites.

applyCipherSuites() accepts suite names that this JDK supports, then rejects configs where no enabled protocol can negotiate any declared suite. This can happen for TLSv1.2 on JDK 17 when tls.cipher_suites contains only suites supported-but-not-default-enabled by TLSv1.2 defaults, such as TLS_AES_256_GCM_SHA384 or TLS_CHACHA20_POLY1305_SHA256. The current fix text only suggests tls.min_version or adding another negotiable suite, which omits tls.cipher_suites fallbacks like switching to a default-enabled suite or tightening to TLSv1.3 only if the allowlist should be TLS 1.3 only.

Source: Path instructions


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: cuioss/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c083a1e5-db7a-46f3-b520-0ccd8e18c703

📥 Commits

Reviewing files that changed from the base of the PR and between d73807e and 37852f7.

📒 Files selected for processing (11)
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/NeutralTlsConfigSource.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizer.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/MtlsServerCustomizerTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizerTest.java
  • doc/architecture.adoc
  • doc/configuration.adoc
  • doc/user/environment-variable-overrides.adoc
  • doc/user/tls-edge.adoc
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/CipherSuiteFixtureWiringTest.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/ManagementPlainHttpActivationWiringTest.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/ManagementPlainHttpOptOutIT.java
🚧 Files skipped from review as they are similar to previous changes (4)
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/MtlsServerCustomizerTest.java
  • doc/architecture.adoc
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/NeutralTlsConfigSource.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/ManagementPlainHttpOptOutIT.java

@cuioss-oliver

Copy link
Copy Markdown
Collaborator Author

/review

@cuioss-oliver

Copy link
Copy Markdown
Collaborator Author

Triage dispositions

In reply to comment_id: PRR_kwDOPatrT88AAAABH77Nmw

Both nitpicks reviewed; one is being fixed and one is declined with reason. (1) MtlsServerCustomizerTest - agreed, and your diagnosis is exact: the pre-assertion reads a bare Vert.x HttpServerOptions and therefore asserts the Vert.x default, not that quarkus.http.ssl.client-auth was deleted. You also identified the right home. Worth noting it is already covered there: SingleSourceTlsContractTest lists 'quarkus.http.ssl' in SERVER_TLS_NAMESPACES deliberately without a trailing dot, and quarkus.http.ssl.client-auth is neither material nor a sanctioned exception - so reintroducing it into the default profile already fails that test today. The remaining method is therefore a pure duplicate of enabledRequiresClientAuthWithTrustAnchor and is deleted in TASK-15; no new assertion is added, since that would duplicate the existing bar. (2) plain-management bucket reference - declining. Unlike the hardcoded lists elsewhere in this PR (which we are fixing), this duplication is not silent: ManagementPlainHttpActivationWiringTest reads the shipped application.properties AND docker-compose.yml at test time and fails the build if the bucket declaration or the per-service selection drifts. The mechanism that would catch the drift already exists, so sourcing the test literal from the Java constant would add a compile-classpath coupling from integration-tests to api-sheriff for no additional guarantee. See the follow-up commit on this branch.

In reply to comment_id: IC_kwDOPatrT88AAAABMnhqQg

Valid. Set.of(E...) does throw IllegalArgumentException on a duplicate element, and getSupportedSSLParameters().getCipherSuites() is a read across a pluggable-provider boundary - a custom or aliasing JSSE provider can return duplicates. The failure mode is the wrong one: a context-free IllegalArgumentException from inside boot instead of either working normally or producing the intended actionable IllegalStateException. Will be addressed by TASK-13 (same file, same method): the construction becomes a duplicate-tolerant copy, with the existing NoSuchAlgorithmException branch and its message left unchanged. See the follow-up commit on this branch.

In reply to comment_id: PRR_kwDOPatrT88AAAABICoIgg

Triaged all three items in this review body.

  1. ManagementPlainHttpActivationWiringTest.java:123-126 (Major) - AGREED, fix incoming. The negative control's per-entry predicate is =-anchored, so a bare compose entry (- QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME, resolved from the host shell) would activate plain-HTTP management on an HTTPS instance while the control still passed green. Will be addressed by TASK-18; see follow-up commit on this branch. Note the sibling assertion in optOutInstanceCarriesNoManagementCertificate is deliberately left alone - it matches the family prefix QUARKUS_MANAGEMENT_SSL_CERTIFICATE_ with no = anchor, so a bare entry already matches there.

  2. doc/user/environment-variable-overrides.adoc:374-379 (Minor) - DECLINED, premise refuted. The claim that reappearance of the four inert keys is 'now a failing build' is already machine-asserted: api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/SingleSourceTlsContractTest#noRawServerTlsPolicyKeyRemainsInTheDefaultProfile scans the shipped application.properties and fails on any default-profile key under quarkus.tls. / quarkus.ssl. / quarkus.http.ssl / quarkus.management.ssl that is neither material nor an explicitly sanctioned exception. All four named keys (quarkus.tls.protocols, quarkus.tls.cipher-suites, quarkus.tls.alpn, quarkus.http.ssl.client-auth) fall inside that bar. It is a surefire test, so it runs under both mvnw verify and mvnw clean install. Sibling tests everySanctionedExceptionIsLoadBearing and theBarAppliesToTheDefaultProfile close the allowlist-decay and profile-prefix escapes. The test landed earlier on this branch, outside the incremental diff this review covered. The doc sentence and the parallel conclusion in doc/configuration.adoc are accurate as written; no change made.

  3. TlsServerCustomizer.java (Trivial nitpick) - DECLINED. The abort message in assertEveryEnabledProtocolNegotiates already names both remediation routes the suggestion asks for, in terms of the two keys an operator actually edits: exclude the protocol from the enabled set via tls.min_version (which IS 'tighten to TLSv1.3 only'), or add a negotiable suite to tls.cipher_suites (which IS 'switch to a default-enabled suite'), plus a concrete sample of up to three suites derived live from this JVM's SSLContext rather than a hardcoded list. Restating the same two routes in suite-level vocabulary would lengthen the message without adding an action.

cuioss-oliver added a commit that referenced this pull request Aug 1, 2026
… negative control

The negative control asserted no non-opt-out gateway instance activates plain
management via an `=`-anchored prefix match on
QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME. Docker Compose also accepts a BARE
list entry, whose value resolves from the host shell at `compose up` time — such
an entry on an HTTPS-management instance would activate plain-HTTP management
while the control still passed green.

Match the bare variable name as well as the KEY=value form, and add a matched
positive/negative control over the predicate itself so a regression that drops
the bare form cannot pass silently.

Addresses CodeRabbit review_body finding 7642e4 on PR #138.

Co-Authored-By: Claude <noreply@anthropic.com>
@cuioss-oliver

Copy link
Copy Markdown
Collaborator Author

/review

cuioss-oliver and others added 18 commits August 1, 2026 15:39
…tener

Add TlsServerCustomizer mapping tls.min_version, tls.cipher_suites and tls.alpn from gateway.yaml onto the terminated HTTPS listener, making gateway.yaml the single effective source of terminated-listener TLS policy. Delete the inert raw quarkus.tls.protocols/.cipher-suites/.alpn keys, which never reached the listener because the TLS registry is consulted only when the certificate comes from quarkus.tls.key-store.*, while this gateway supplies it via quarkus.http.ssl.certificate.*. Relocate the build-time quarkus.ssl.native knob to the pom native profile. Add the cipherSuites component to TlsConfig and align the test profile onto quarkus.http.ssl.certificate.* so tests exercise the production listener path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01TFEs53xjkecC5nX6XRdEMo
…gement.port in-schema

gateway.yaml gains a first-class neutral `management:` policy block, bound through
a new single-ordinal ConfigSource seam, and the deliberate absence of a port key is
made discoverable instead of silent.

- ManagementConfig: the neutral management policy record (tls.enabled), declaring no
  port component. The management port stays deployment-bound at quarkus.management.port
  (default 9000, QUARKUS_MANAGEMENT_PORT).
- NeutralTlsConfigSource + its ConfigSourceProvider registration: the seam that reaches
  the management listener, which never enumerates HttpServerOptionsCustomizer beans. Its
  ordinal sits above the environment source's 300 so a policy named in gateway.yaml is
  not silently overridden; that is safe only because it projects policy keys only — never
  a port, never trust material, and never key material into the default TLS registry
  bucket, which would make management inherit HTTPS invisibly through
  registry.getDefault() (upstream quarkus issue 43380).
- gateway.schema.json: the management object declares `port` solely in order to refuse it,
  via an always-failing subschema plus the networknt errorMessage extension, so the
  actionable sentence naming quarkus.management.port / QUARKUS_MANAGEMENT_PORT and ADR-0025
  lands at the /management/port pointer rather than surfacing as a generic unknown-key
  error against the parent object. ConfigLoader enables the extension, which is off unless
  the keyword is named on the registry configuration.

No ConfigValidator rule is added: loadGateway returns on the first schema failure and never
reaches validation, so a validator rule for a schema-rejected key would be unreachable dead
code. The errorMessage adoption is backed by an executed test asserting the exact sentence,
written before the mechanism was relied on.
…a sixth instance

Running the management interface on plain HTTP is a legitimate deployment behind a
trusted boundary, so it stays possible — but it stops being silent.

The downgrade route is Quarkus-native rather than bespoke: quarkus.management.tls-
configuration-name selects the `plain-management` TLS bucket that application.properties
now declares with no key material, the recorder finds no key/cert, and the management
listener starts plain. It is runtime configuration, so the same native image serves both
postures with no rebuild — which is why the new compose instance needs neither a variant
image nor a config overlay, only an environment variable.

- ManagementPlainHttpAudit observes the real StartupEvent and warns (ApiSheriff-115) on
  the OBSERVED EFFECTIVE STATE — the TLS material the listener actually resolved, the same
  question the recorder asks. An audit keyed on a configuration key would report a
  comfortable fiction as soon as that key was renamed or superseded.
- The observer actively invokes the audit rather than holding a collaborator, and the
  accompanying test fires the event through the container's own bus. A normal-scoped
  observer whose proxy is never touched silently never runs while a directly-invoking test
  stays green (lesson 2026-07-20-18-002).
- api-sheriff-plain-mgmt publishes management on 19005 with its own http:// readiness gate,
  deliberately outside the https-hard-coded loop that gates 19000-19004. The lockstep
  comments on the other five instances now describe the exception instead of quietly
  becoming false.
- ManagementPlainHttpActivationWiringTest asserts the activation is really wired, with the
  five HTTPS instances as the matched negative control, and pins the two constraints that
  make the mechanism work: the selected bucket carries no key material, and no DEFAULT
  quarkus.tls.key-store.* bucket exists — the management interface falls back to the default
  registry bucket and would otherwise inherit HTTPS through a path no config file mentions.

Also repairs the testboot fixture, which declared no token_validation and so could not
complete a container boot at all — TokenValidatorProducer forces the validator eagerly
regardless of the route table's auth requirements.
…g seams

ADR-0025 restates ADR-0011's neutral-name ruling as a general rule and extends it from the JWKS trust surface to the whole server-TLS surface: every knob is policy (neutral in gateway.yaml), deployment-bound (ports and material, supplied by the deployment), or build-time (never in the runtime surface at all). ADR-0011 stays Accepted and is extended, not superseded.

The classification is not filing. It is what makes the single-ordinal projecting ConfigSource safe: because the source projects policy keys only, no deployment-bound knob is in the projected set, so nothing an operator sets in the environment can be outranked by surprise — and the second, lower-ordinal source that would otherwise be needed is unnecessary rather than merely forbidden.

Also recorded: why the two seams are each the only one available on their path (the customizer is enumerated after createSslOptions on the main path; the management interface never enumerates customizers); why the neutral management block declares no port; why management HTTPS stays the default with a runtime-native, audited opt-out rather than a bespoke key or a boot refusal; and the reversal on quarkus.management.tls-configuration-name — both original objections are plausible readings of its name and both fail against the mechanism, so the reversal is recorded to stop a future reader re-deriving them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01TFEs53xjkecC5nX6XRdEMo
…onsumer

The neutral key was bound but never read: NeutralTlsConfigSource.project() returned an empty map for every document, so gateway.yaml's management.tls.enabled reached nothing. A neutral name that binds nothing is the exact defect this plan exists to eliminate — it reads as enforcement while enforcing nothing — so the seam now projects.

management.tls.enabled=false projects exactly one key, quarkus.management.tls-configuration-name=plain-management, selecting the key-less bucket application.properties already declares. Absent and true project NOTHING, deliberately: the secure default needs no key to express it, and the silence is what keeps QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME a working route onto the same Quarkus key. The neutral door and the deployment door are two routes onto one lever, not two mechanisms with a precedence puzzle between them.

Every projection assertion goes through the assembled SmallRyeConfig rather than the source's own accessor, because the question Quarkus asks is what the resolved Config returns after ordinal arbitration — and each no-projection case runs against a competing value at the environment source's ordinal, so 'the deployment value survives' is an observation rather than the absence of one. The projected key set's cardinality is pinned so a second key must arrive as a visible diff.

The top-level tls block is no longer parsed here. It governs the terminated listener, which has a live CDI seam able to observe the SSL options Quarkus actually built; reading it in the config source was a parse with no key to emit for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01TFEs53xjkecC5nX6XRdEMo
…WARN

ApiSheriff-115 observes the effective state and cannot tell which door produced it, so a remedy naming only the deployment environment variable is now incomplete: gateway.yaml's management.tls.enabled=false reaches the same Quarkus key. An operator who took the neutral door would be told to remove an override they never set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01TFEs53xjkecC5nX6XRdEMo
The configuration reference gains cipher_suites, the now-effective min_version/alpn semantics with their terminated-listener scope limit, a management field reference explaining why the block declares no port and why a downgrade takes the whole surface, and a per-key verdict table recording the disposition of every raw quarkus.* server-TLS key with its class and reason — including both named exceptions, since an undocumented exception would be a silent trim.

The architecture document records the two binding seams and, for each, why it is the only one available on its path: the customizer is enumerated after the SSL options are built, and the management recorder never enumerates customizers at all. It also records the ordering constraint that follows — the projecting ConfigSource is constructed before CDI exists, so it cannot read the bound model and re-reads gateway.yaml itself, which is why a malformed document degrades silently there and an unexpected shape reads as 'not explicitly disabled'.

The operator guide gains the TLS policy keys and the management interface: HTTPS by default, the single-port constraint that makes any downgrade total, the two doors onto the opt-out and why they do not compete, why the escape hatch is runtime-native rather than bespoke, and an explicit warning that quarkus.management.enabled=false is the opposite of a fix — build-time fixed, and it moves probes onto the TLS-terminated router.

New operator page environment-variable-overrides.adoc states the three-way classification in operator language and enumerates what stays deployment-bound. LogMessages registers ApiSheriff-115 and the previously-undocumented ApiSheriff-106, and repairs three rows that had drifted from their templates — including a phantom INFO-5 row for an event the code emits as WARN-106.

The primary IT fixture now declares min_version and a cipher allowlist so the integration stack exercises a gateway.yaml-sourced protocol and cipher setting. The floor is 1.2 and the allowlist spans both enabled protocols: under a 1.2 floor a 1.3-only allowlist would leave TLS 1.2 with nothing to negotiate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01TFEs53xjkecC5nX6XRdEMo
The keys this plan deleted were inert, not merely redundant, and nothing structural stops that defect growing back: a contributor reaching for a familiar quarkus.tls.* name would reintroduce a surface that reads as enforcement while reaching no listener, and every existing test would stay green. SingleSourceTlsContractTest turns that reintroduction into a failing build by classifying every default-profile key in the shipped application.properties and refusing any server-TLS POLICY key outside a named allowlist.

The R1 relocation is asserted in BOTH halves — quarkus.ssl.native absent from application.properties AND present, enabled, inside the pom's native profile. The absence assertion alone would pass just as happily if the knob had been dropped outright, and a native image without SSL support fails at runtime rather than at build.

One assertion guards the guard: every allowlist entry must be present in the file AND reachable by the detector. Without it the allowlist could quietly fill with decorative entries — a stale key that no longer exists, or one the namespace matcher never reaches — leaving the bar weaker than it reads. That check is what caught quarkus.http.ssl-port falling outside a dot-terminated quarkus.http.ssl. prefix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01TFEs53xjkecC5nX6XRdEMo
…oute pairs

The management-TLS pair is one instance of a general shape, so the operator page now answers the same four questions for the rest of the surface: default value, security posture of that default, override route, and whether the route is deployment-bound by necessity (a port, material, a build-time knob) or merely by accident (a policy knob that never got a neutral name).

Swept: terminated-listener TLS policy, mTLS, the SNI split and its ports, JWKS trust and egress, token validation and its eager boot assembly, the inbound filter modes, request limits and admission budgets, and response headers/CORS. Two answers are worth naming. The transport framing bounds have NO override route at all, deliberately — an operator hunting for the variable should be told it does not exist rather than left searching. And the inbound filter mode has no environment route on purpose: a validation posture relaxable by an environment variable would be a weakening with no trace in the document under review.

Two areas are recorded as findings rather than fixed, since both sit outside this plan's footprint. The interaction between the gateway's own validator and the extension's parallel sheriff.token.issuers.* readiness surface could not be established by reading, so the page says so explicitly instead of asserting a verdict it did not earn. And QUARKUS_HTTP_SSL_PROTOCOLS on the mtls IT instance works today only because that overlay declares no tls.min_version — adding one would silently shadow the env pin, which is the precedence rule behaving correctly and surprisingly at the same time.

No inert key was found in the shipped properties after this change, and the page says that is a statement about this revision rather than a general guarantee.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01TFEs53xjkecC5nX6XRdEMo
api-sheriff-plain-mgmt could never boot. It is the only gateway instance that
mounts the shared sheriff-config rather than overlaying its own gateway.yaml --
deliberately, so the management plain-HTTP opt-out is the single variable under
test. But that shared gateway.yaml declares a non-empty passthrough_sni, which
starts the SNI front listener on the public port 8443, and the instance was
never given QUARKUS_HTTP_SSL_PORT=8444 to move the terminated HTTPS listener to
the internal port. The two collided and the container crash-looped with "Port
8443 seems to be in use by another process".

The four other gateway instances escape this only because each overlays
gateway.yaml with a variant declaring no passthrough_sni, so the invariant --
documented at the passthrough_sni block in sheriff-config/gateway.yaml -- had
never bitten before.

Add the port move, and delete the "no passthrough front on this instance"
comment, which asserted the opposite of what the mounted config does.

Found by running the containerised suite: a green test-compile showed nothing.
After the fix the suite is 89 completed, 0 failures, 0 errors, 0 skipped, 0
flakes, with ManagementPlainHttpOptOutIT 3/3 and WARN ApiSheriff-115 observed
firing in the native container through the real StartupEvent path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFEs53xjkecC5nX6XRdEMo
…uards

NeutralTlsConfigSource carried an unresolved OpenRewrite marker before a
deliberately broad `catch (Exception e)` guarding the YAML pre-parse of
gateway.yaml. CLAUDE.md forbids committing code with a marker present.

The catch itself is required, not speculative -- it guards a real
external-input parse boundary -- so the fix is to place the suppression
comment in its correct position ahead of `catch`, matching the convention
already used at JwksTrustProfileResolver.java:115, and drop the stray TODO.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFEs53xjkecC5nX6XRdEMo
TlsServerCustomizer forwarded every declared cipher suite straight to
HttpServerOptions with no validation. A probe against a real Vert.x HTTPS
listener showed the bind SUCCEEDS with a bogus suite name -- the gateway boots
green and then resets every TLS handshake with SSLHandshakeException. A
partially mistyped list is worse: the valid entries still negotiate, silently
narrowing the declared cipher policy to an unaudited subset.

Validate each declared suite against the JDK's supported set and abort the boot
with a message naming the offending suite and its closest supported matches,
mirroring the existing applyMinVersion/applyAlpn fail-closed shape. The
supported set is resolved per boot, not in a static initializer, so a native
image cannot bake in the build host's cipher set.

Adds symmetric boot-failure coverage for all three TLS knobs plus a guard test
pinning the integration fixture's four-suite allowlist.

Found by the finalize security-audit sweep (finding 1421ff).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFEs53xjkecC5nX6XRdEMo
Tier-1 re-enrichment scoped to api-sheriff, the only module this plan added
components to. Records TlsServerCustomizer and ManagementPlainHttpAudit under
gateway.tls, NeutralTlsConfigSource under gateway.config, and ManagementConfig
under gateway.config.model, plus the ADR-0025 three-way key classification
(POLICY / DEPLOYMENT-BOUND / BUILD-TIME) as a durable hint.

Tier-0 found no structural change: the new components landed inside existing
modules, so the discovered descriptor is byte-unchanged and is not recommitted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TFEs53xjkecC5nX6XRdEMo
Clears SonarCloud java:S6485 on NeutralTlsConfigSource.policyBlocksOf.
new HashMap<>(n) takes an initial CAPACITY, not a mapping count, so it
under-sizes the table and forces a rehash; HashMap.newHashMap(n) sizes
for the intended number of mappings, which is what the call site means.

Behaviour-preserving one-line change.
…drift guard

The cipher allowlist guard only rejected suite NAMES the JDK does not know.
It could not fire for the operator error with the identical outcome: a list
that no enabled protocol can negotiate. A 1.2 floor enables TLSv1.2 AND
TLSv1.3, so an allowlist of TLS 1.3-only suites passed every name check while
every TLS 1.2 client failed the handshake against a listener that bound
happily — the fail-open shape this plan exists to eliminate.

applyCipherSuites now also asserts that every protocol on the listener's
enabled set retains a negotiable suite, aborting with a message naming the
stranded protocol and both knobs that resolve it. The per-protocol negotiable
sets are derived from the platform's own protocol-named SSLContexts (a
context reports the suites applicable to itself and every protocol below it,
and names those lower protocols, so subtracting isolates each one) rather
than from a hardcoded suite classification. A protocol whose set cannot be
derived is skipped, not treated as unreachable.

Note the JDK does not filter getEnabledCipherSuites() by setEnabledProtocols(),
so that derivation was verified empirically before being relied on.

supportedCipherSuites() now uses Set.copyOf over Set.of: the array comes from
a pluggable JSSE provider that may report a name twice, which Set.of would
turn into a context-free IllegalArgumentException at boot.

The integration fixture's allowlist is no longer mirrored by hand in the
api-sheriff test. That drift guard moves to CipherSuiteFixtureWiringTest in
the module owning the fixture, where it parses gateway.yaml directly and
cannot fall out of step with it.

Also drops clientAuthIsOwnedByTheMtlsCustomizer, a duplicate of
enabledRequiresClientAuthWithTrustAnchor whose only addition asserted the
Vert.x library default rather than the property-file claim it stated;
SingleSourceTlsContractTest already owns that guarantee.
Both guards stated a guarantee their mechanism could not deliver.

The negative control in ManagementPlainHttpActivationWiringTest iterated a
hardcoded five-name list, so the case it exists for — a NEWLY added gateway
instance that sets QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME by accident —
was outside the enumeration and could never fail it. The set is now derived
from the same docker-compose.yml parse the class already performs, keeping
every api-sheriff* instance except the opt-out one. Because a derived set can
silently become empty, the control now asserts its own non-emptiness first;
without that, a parse change would turn it into a vacuous pass.

managementRefusesHttps asserted IOException, which ConnectException,
HttpConnectTimeoutException and DNS failures all satisfy — so it also passed
when nothing was listening on the port at all, reporting success for the
wrong reason. Narrowed to SSLException, which is what a TLS handshake against
a plain listener actually raises. managementServesHealthOverPlainHttp remains
the matched positive control proving the port does answer.
No behaviour changes; each edit removes a promise the implementation does not
keep.

(1) architecture.adoc still asserted that 'no plain-HTTP management surface
remains: every scraper and probe must speak TLS', and described health as
served over the same HTTPS surface — both stale since management.tls.enabled
landed. They now state HTTPS as the default and shipped posture, name the
opt-out and its deployment twin, and keep the single-port consequence: a
downgrade takes health AND metrics together, announced by WARN ApiSheriff-115.

(2) 'These keys are the single effective source' was unconditional, but the
customizer is a no-op for any key gateway.yaml omits — so a deployment-supplied
QUARKUS_HTTP_SSL_PROTOCOLS stays effective until tls.min_version is declared,
which is how the api-sheriff-mtls instance pins TLS 1.2 today. The claim is now
conditional in the four places that overstated it, while keeping the true
statement that no raw quarkus.tls.* policy key ships. Also corrects the
mechanism: for the terminated listener the neutral key wins by being the last
writer, not by ordinal — ordinal governs the management key only.

(3) The gateway.yaml example promised 'neutral names only ... trust material
never appears here' ten lines above tls.mtls.client_ca, a path to trust
material. client_ca is NOT deleted from the examples — it is real,
schema-declared and consumed by MtlsServerCustomizer, so an mTLS example
without it is unusable. The contradiction is fixed instead by naming the
carve-out: gateway.yaml carries a reference to deployment-mounted material,
never the material. ADR-0025's three-way rule does not distinguish a path
reference from material, so this is recorded as an explicitly named exception
rather than a rule the ADR carries; amending the ADR is out of scope and is
raised in decision.log.

Boy Scout, same paragraphs: the cipher-allowlist guidance now states that an
allowlist stranding an enabled protocol is refused at boot, which the sibling
implementation change made true.
… negative control

The negative control asserted no non-opt-out gateway instance activates plain
management via an `=`-anchored prefix match on
QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME. Docker Compose also accepts a BARE
list entry, whose value resolves from the host shell at `compose up` time — such
an entry on an HTTPS-management instance would activate plain-HTTP management
while the control still passed green.

Match the bare variable name as well as the KEY=value form, and add a matched
positive/negative control over the predicate itself so a regression that drops
the bare form cannot pass silently.

Addresses CodeRabbit review_body finding 7642e4 on PR #138.

Co-Authored-By: Claude <noreply@anthropic.com>
@cuioss-oliver
cuioss-oliver force-pushed the feature/plan-31b-neutral-tls-config-surface branch from 21ff759 to be0271a Compare August 1, 2026 13:39
@cuioss-oliver

Copy link
Copy Markdown
Collaborator Author

/review

@cuioss-oliver

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cuioss-oliver

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: cuioss/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 03ef266b-1128-4ac5-bfb3-ddf507bb835b

📥 Commits

Reviewing files that changed from the base of the PR and between 21ff759 and be0271a.

📒 Files selected for processing (38)
  • .plan/project-architecture/api-sheriff/enriched.json
  • api-sheriff/pom.xml
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/ConfigLogMessages.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/NeutralTlsConfigSource.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/load/ConfigLoader.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/model/GatewayConfig.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/model/ManagementConfig.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/model/TlsConfig.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/ManagementPlainHttpAudit.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizer.java
  • api-sheriff/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider
  • api-sheriff/src/main/resources/application.properties
  • api-sheriff/src/main/resources/schema/gateway.schema.json
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/NeutralTlsConfigSourceTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/SingleSourceTlsContractTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/load/ConfigLoaderTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/model/ConfigModelContractTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/quarkus/SheriffMetricsTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/ManagementPlainHttpAuditTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/MtlsServerCustomizerTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizerTest.java
  • api-sheriff/src/test/resources/application.properties
  • api-sheriff/src/test/resources/config/testboot/gateway.yaml
  • api-sheriff/src/test/resources/config/valid/gateway.yaml
  • doc/LogMessages.adoc
  • doc/adr/0025-The_whole_server-TLS_surface_is_neutral_in_gatewayyaml_and_bound_by_exactly_two_seams.adoc
  • doc/architecture.adoc
  • doc/configuration.adoc
  • doc/user/README.adoc
  • doc/user/environment-variable-overrides.adoc
  • doc/user/tls-edge.adoc
  • integration-tests/docker-compose.yml
  • integration-tests/pom.xml
  • integration-tests/scripts/start-integration-container.sh
  • integration-tests/src/main/docker/sheriff-config/gateway.yaml
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/CipherSuiteFixtureWiringTest.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/ManagementPlainHttpActivationWiringTest.java
  • integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/ManagementPlainHttpOptOutIT.java
🚧 Files skipped from review as they are similar to previous changes (32)
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/ManagementPlainHttpAuditTest.java
  • api-sheriff/pom.xml
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/model/GatewayConfig.java
  • integration-tests/src/main/docker/sheriff-config/gateway.yaml
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/ConfigLogMessages.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/MtlsServerCustomizerTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/quarkus/SheriffMetricsTest.java
  • api-sheriff/src/test/resources/application.properties
  • integration-tests/pom.xml
  • api-sheriff/src/test/resources/config/testboot/gateway.yaml
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/load/ConfigLoaderTest.java
  • api-sheriff/src/main/resources/schema/gateway.schema.json
  • doc/architecture.adoc
  • api-sheriff/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider
  • api-sheriff/src/test/resources/config/valid/gateway.yaml
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/model/ManagementConfig.java
  • doc/user/README.adoc
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/ManagementPlainHttpAudit.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/NeutralTlsConfigSourceTest.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/load/ConfigLoader.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizerTest.java
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/SingleSourceTlsContractTest.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/model/TlsConfig.java
  • api-sheriff/src/main/resources/application.properties
  • api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/model/ConfigModelContractTest.java
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/config/NeutralTlsConfigSource.java
  • doc/user/tls-edge.adoc
  • api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizer.java
  • doc/LogMessages.adoc
  • doc/configuration.adoc
  • .plan/project-architecture/api-sheriff/enriched.json
  • doc/user/environment-variable-overrides.adoc

Comment thread integration-tests/docker-compose.yml Outdated
Comment thread integration-tests/scripts/start-integration-container.sh Outdated
@cuioss-oliver

Copy link
Copy Markdown
Collaborator Author

Triage dispositions

In reply to comment_id: PRR_kwDOPatrT88AAAABH77Nmw

Both nitpicks reviewed; one is being fixed and one is declined with reason. (1) MtlsServerCustomizerTest - agreed, and your diagnosis is exact: the pre-assertion reads a bare Vert.x HttpServerOptions and therefore asserts the Vert.x default, not that quarkus.http.ssl.client-auth was deleted. You also identified the right home. Worth noting it is already covered there: SingleSourceTlsContractTest lists 'quarkus.http.ssl' in SERVER_TLS_NAMESPACES deliberately without a trailing dot, and quarkus.http.ssl.client-auth is neither material nor a sanctioned exception - so reintroducing it into the default profile already fails that test today. The remaining method is therefore a pure duplicate of enabledRequiresClientAuthWithTrustAnchor and is deleted in TASK-15; no new assertion is added, since that would duplicate the existing bar. (2) plain-management bucket reference - declining. Unlike the hardcoded lists elsewhere in this PR (which we are fixing), this duplication is not silent: ManagementPlainHttpActivationWiringTest reads the shipped application.properties AND docker-compose.yml at test time and fails the build if the bucket declaration or the per-service selection drifts. The mechanism that would catch the drift already exists, so sourcing the test literal from the Java constant would add a compile-classpath coupling from integration-tests to api-sheriff for no additional guarantee. See the follow-up commit on this branch.

In reply to comment_id: IC_kwDOPatrT88AAAABMnhqQg

Valid. Set.of(E...) does throw IllegalArgumentException on a duplicate element, and getSupportedSSLParameters().getCipherSuites() is a read across a pluggable-provider boundary - a custom or aliasing JSSE provider can return duplicates. The failure mode is the wrong one: a context-free IllegalArgumentException from inside boot instead of either working normally or producing the intended actionable IllegalStateException. Will be addressed by TASK-13 (same file, same method): the construction becomes a duplicate-tolerant copy, with the existing NoSuchAlgorithmException branch and its message left unchanged. See the follow-up commit on this branch.

In reply to comment_id: PRR_kwDOPatrT88AAAABICoIgg

Triaged all three items in this review body.

  1. ManagementPlainHttpActivationWiringTest.java:123-126 (Major) - AGREED, fix incoming. The negative control's per-entry predicate is =-anchored, so a bare compose entry (- QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME, resolved from the host shell) would activate plain-HTTP management on an HTTPS instance while the control still passed green. Will be addressed by TASK-18; see follow-up commit on this branch. Note the sibling assertion in optOutInstanceCarriesNoManagementCertificate is deliberately left alone - it matches the family prefix QUARKUS_MANAGEMENT_SSL_CERTIFICATE_ with no = anchor, so a bare entry already matches there.

  2. doc/user/environment-variable-overrides.adoc:374-379 (Minor) - DECLINED, premise refuted. The claim that reappearance of the four inert keys is 'now a failing build' is already machine-asserted: api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/SingleSourceTlsContractTest#noRawServerTlsPolicyKeyRemainsInTheDefaultProfile scans the shipped application.properties and fails on any default-profile key under quarkus.tls. / quarkus.ssl. / quarkus.http.ssl / quarkus.management.ssl that is neither material nor an explicitly sanctioned exception. All four named keys (quarkus.tls.protocols, quarkus.tls.cipher-suites, quarkus.tls.alpn, quarkus.http.ssl.client-auth) fall inside that bar. It is a surefire test, so it runs under both mvnw verify and mvnw clean install. Sibling tests everySanctionedExceptionIsLoadBearing and theBarAppliesToTheDefaultProfile close the allowlist-decay and profile-prefix escapes. The test landed earlier on this branch, outside the incremental diff this review covered. The doc sentence and the parallel conclusion in doc/configuration.adoc are accurate as written; no change made.

  3. TlsServerCustomizer.java (Trivial nitpick) - DECLINED. The abort message in assertEveryEnabledProtocolNegotiates already names both remediation routes the suggestion asks for, in terms of the two keys an operator actually edits: exclude the protocol from the enabled set via tls.min_version (which IS 'tighten to TLSv1.3 only'), or add a negotiable suite to tls.cipher_suites (which IS 'switch to a default-enabled suite'), plus a concrete sample of up to three suites derived live from this JVM's SSLContext rather than a hardcoded list. Restating the same two routes in suite-level vocabulary would lengthen the message without adding an action.

In reply to comment_id: PRR_kwDOPatrT88AAAABIC7Gcg

Review-summary body — it carries the 'Actionable comments posted: 4' header and the processed/skipped file inventory, with no finding of its own beyond the four inline comments. All four were triaged individually: three accepted and allocated as TASK-19 (ADR-0025 environment-precedence wording), TASK-20 (readiness-probe time limits) and TASK-21 (non-vacuous JDK cipher-support assertion); the fourth (Compose-derived readiness targets) is answered on its own thread. Recorded here for completeness — no separate action.

In reply to comment_id: IC_kwDOPatrT88AAAABMxL6_g

Acknowledgement comment ('Review finished') emitted in response to an explicit @coderabbitai review trigger. Carries no review content, so there is nothing to triage. Recorded for completeness — no action.

In reply to comment_id: IC_kwDOPatrT88AAAABMxURGg

Acknowledgement comment ('Review finished') emitted in response to an explicit @coderabbitai review trigger. Carries no review content, so there is nothing to triage. Recorded for completeness — no action.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- affected files ---'
fd -a -t f \
  'TlsServerCustomizer.java' \
  'TlsServerCustomizerTest.java' \
  'MtlsServerCustomizerTest.java' \
  'SingleSourceTlsContractTest.java' \
  'ManagementPlainHttpActivationWiringTest.java' \
  'application.properties' \
  'docker-compose.yml' \
  | sort

printf '%s\n' '--- outlines ---'
for f in \
  api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizer.java \
  api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/MtlsServerCustomizerTest.java \
  api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/SingleSourceTlsContractTest.java \
  integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/ManagementPlainHttpActivationWiringTest.java
do
  if [ -f "$f" ]; then
    echo "### $f"
    ast-grep outline "$f" --items all || true
  fi
done

printf '%s\n' '--- cipher-suite collection and mTLS test references ---'
rg -n -C 8 \
  -e 'getSupportedSSLParameters\(\)\.getCipherSuites' \
  -e 'Set\.of\(' \
  -e 'enabledRequiresClientAuthWithTrustAnchor' \
  -e 'quarkus\.http\.ssl\.client-auth' \
  api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizer.java \
  api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/MtlsServerCustomizerTest.java \
  api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/SingleSourceTlsContractTest.java || true

printf '%s\n' '--- Compose environment predicate and wiring coverage ---'
rg -n -C 10 \
  -e 'QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME' \
  -e 'plain-management' \
  -e 'environment' \
  -e 'matches\(' \
  integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/ManagementPlainHttpActivationWiringTest.java \
  integration-tests/docker-compose.yml \
  api-sheriff/src/main/resources/application.properties || true

Length of output: 2657


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- affected files ---'
fd -a -t f '(TlsServerCustomizer|MtlsServerCustomizerTest|SingleSourceTlsContractTest|ManagementPlainHttpActivationWiringTest|application\.properties|docker-compose\.yml)$' . | sort

printf '%s\n' '--- outlines ---'
for f in \
  api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizer.java \
  api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/MtlsServerCustomizerTest.java \
  api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/SingleSourceTlsContractTest.java \
  integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/ManagementPlainHttpActivationWiringTest.java
do
  echo "### $f"
  ast-grep outline "$f" --items all || true
done

printf '%s\n' '--- cipher-suite collection and mTLS test references ---'
rg -n -C 8 \
  -e 'getSupportedSSLParameters\(\)\.getCipherSuites' \
  -e 'Set\.of\(' \
  -e 'enabledRequiresClientAuthWithTrustAnchor' \
  -e 'quarkus\.http\.ssl\.client-auth' \
  api-sheriff/src/main/java/de/cuioss/sheriff/gateway/tls/TlsServerCustomizer.java \
  api-sheriff/src/test/java/de/cuioss/sheriff/gateway/tls/MtlsServerCustomizerTest.java \
  api-sheriff/src/test/java/de/cuioss/sheriff/gateway/config/SingleSourceTlsContractTest.java || true

printf '%s\n' '--- Compose environment predicate and wiring coverage ---'
rg -n -C 10 \
  -e 'QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME' \
  -e 'plain-management' \
  -e 'environment' \
  -e 'matches\(' \
  integration-tests/src/test/java/de/cuioss/sheriff/gateway/integration/ManagementPlainHttpActivationWiringTest.java \
  integration-tests/docker-compose.yml \
  api-sheriff/src/main/resources/application.properties || true

Length of output: 72771


@cuioss-oliver Confirmed on the current PR branch.

  • TlsServerCustomizer.supportedCipherSuites() now uses a duplicate-tolerant Set.copyOf(...) collection. The existing NoSuchAlgorithmException failure path remains unchanged.
  • MtlsServerCustomizerTest no longer contains the Vert.x-default pre-assertion. SingleSourceTlsContractTest owns the shipped-property check for quarkus.http.ssl.client-auth.
  • ManagementPlainHttpActivationWiringTest now detects both QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME and QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME=<value>.
  • The wiring test reads both shipped configuration files. It provides the required drift detection without adding an integration-test dependency on the application module.
  • SingleSourceTlsContractTest supports the documentation statement that reintroduced raw TLS policy keys fail the build.
  • The existing TLS negotiation failure text already identifies the relevant tls.min_version and tls.cipher_suites remediation paths.

I accept the declined dispositions. The cited controls make the related review concerns inapplicable.


✏️ Learnings added
Learnt from: cuioss-oliver
URL: https://github.com/cuioss/API-Sheriff/pull/138

Timestamp: 2026-08-01T15:25:32.784Z
Learning: In API-Sheriff, `SingleSourceTlsContractTest` is the authoritative test for the single-source server TLS policy contract. It scans `application.properties` for raw TLS policy keys under `quarkus.tls`, `quarkus.ssl`, `quarkus.http.ssl`, and `quarkus.management.ssl`, while allowing only deployment material and explicitly sanctioned exceptions.

Learnt from: cuioss-oliver
URL: https://github.com/cuioss/API-Sheriff/pull/138

Timestamp: 2026-08-01T15:25:32.784Z
Learning: In API-Sheriff, `ManagementPlainHttpActivationWiringTest` intentionally uses local literals for the `plain-management` TLS bucket. The test parses both `api-sheriff/src/main/resources/application.properties` and `integration-tests/docker-compose.yml` to detect declaration and selection drift. Do not add a compile-classpath dependency from `integration-tests` to `api-sheriff` only to share this literal.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

cuioss-oliver and others added 4 commits August 1, 2026 17:45
…nt-bound keys

The 'projects policy keys only' section claimed nothing an operator supplies
from the environment is in the projected set. That is over-broad: the one
projected key, quarkus.management.tls-configuration-name, is reachable from
the environment as QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME — as the ADR's
own 'deployment door' passage already says.

Narrow the claim to the property the single ordinal actually depends on (no
deployment-bound key, port or trust/key material is ever projected) and state
the deliberate shared-key case in the same place, cross-referenced to the
'two doors onto one lever' subsection. Align the NeutralTlsConfigSource class
Javadoc's matching clause so code and ADR cannot drift.

Prose-only; no behaviour change.
…wn method

everyDeclaredSuiteIsSupportedByThisJdk() filtered a freshly-parsed list and
asserted the result empty, so an empty tls.cipher_suites fixture passed it
without checking anything. The non-emptiness guarantee lived in a sibling test
and evaporated if that sibling were renamed, moved or deleted.

Capture declaredCipherSuites() once into a local, assert non-emptiness inside
the method that depends on it, and filter that same local — which also removes
the method's second parse of the fixture. theFixtureDeclaresACipherAllowlist()
stays as the named statement of the fixture contract; its message no longer
claims to protect the support assertion, since it no longer does.

Test-only; the fixture is still parsed, never mirrored.
…y probe

Two defects in the host-side readiness gate, both from the same PR review round.

Time limits (CodeRabbit e877a8): every readiness and diagnostic curl ran with
no --connect-timeout and no --max-time, so a listener that completed the TCP
handshake but never answered blocked curl indefinitely — the loop hung on
attempt 1, never reached its failure branch, and the CI job died on its outer
timeout with no diagnostics. Every curl in the file now carries
--connect-timeout 2 --max-time 5. The failure messages said 'within N seconds'
for loops that count attempts, not seconds; they now say attempts. The loop
counter comparisons are quoted (SC2086).

Derived targets (CodeRabbit d15d2e): the instance list, management ports and
probe scheme were hand-maintained in two places that had to mirror
docker-compose.yml, under a comment telling the reader to keep them in
lockstep. Each api-sheriff* service now carries a
de.cuioss.sheriff.management-scheme label, and one loop discovers every
instance, its host port published against container port 9000, and its scheme
from the resolved Compose model. The plain-management instance is no longer a
separate block — its http:// probe comes from its label, not from a branch on
its name, and still uses no -k. No management port number and no gateway
service name is left anywhere in the readiness path, so adding, removing or
renumbering an instance needs no edit to the script.

Discovery fails loudly and specifically on zero api-sheriff* services, a
missing or invalid scheme label, a missing 9000 mapping, or a Compose version
without 'config --format json' — a silently empty target list would make the
readiness gate a no-op that passes green, which is worse than the list it
replaces. Verified by running the readiness path against the live stack (all
six instances discovered and probed, plain-mgmt over http) and by a matched
negative control against a gateway-free Compose model, which aborts with
exit 1.
The Python discovery block already exits non-zero on every path that
would yield an empty target set: an empty api-sheriff* service set and
any service missing its scheme label or its 9000 mapping each sys.exit
with a named message. The bash-level re-check of READINESS_TARGETS could
therefore never fire, so it was dead code rather than a boundary guard.

TASK-22's non-vacuity guarantee is unchanged and stays enforced inside
the discovery block, where each failure names the offending service.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEnYPWdaeHMg1GLVWsTC1H
@cuioss-oliver

Copy link
Copy Markdown
Collaborator Author

/review

@cuioss-oliver
cuioss-oliver added this pull request to the merge queue Aug 1, 2026
Merged via the queue into main with commit ffa8cef Aug 1, 2026
24 checks passed
@cuioss-oliver
cuioss-oliver deleted the feature/plan-31b-neutral-tls-config-surface branch August 1, 2026 16:58
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