Desktop browser-binding mapper build and issuer qualification (#853)

Desktop browser-binding mapper build and issuer qualification (#853)

This record closes the outstanding "runtime image build was not run locally" gap carried in the #862 handoff. It qualifies the opt-in Keycloak protocol mapper only. It does not qualify the production Bridge, a production realm, authenticated RDP, or managed desktop readiness.

Build

The provider JAR is built from the Dockerfile with no network access during compilation, against libraries copied from the exact digest-pinned runtime image:

docker buildx build --network none --output type=local,dest=/tmp/desktop-mapper-build integrations/keycloak-desktop

Two independent builds produced a byte-identical artifact, confirming the fixed archive timestamp and pinned base images yield a reproducible JAR. Details in build.json.

PropertyValue
Artifact`desktop-browser-binding.jar`
SHA-256`7cb09c2261b399809b66537b2f12ea2ff1a272478a4060b4cb2037a480fb5672`
Independent builds compared2, identical
Network during compilenone

Issuer qualification

The disposable HTTPS fixture was started with the freshly built JAR, allowed to become ready, probed, and stopped. The fixture independently recorded the same mapper SHA-256 it loaded, tying the qualification below to the artifact above. Sanitized outcomes are in result.json.

CaseOutcome
Exchange with the configured Bridge certificate200; certificate-bound, `aud=gateway`, `azp=bridge`, session present, browser audience present
Gateway introspection of that tokenActive, certificate-bound, browser audience preserved
Exchange with another trusted certificate400 `invalid_request`
Exchange with no client certificate400 `invalid_request`
Binding absent, empty, malformed or oversized400 `invalid_request`
Duplicate binding or duplicate audience400 `invalid_request`
Re-exchanging an existing delegation to rebind400 `invalid_request`
Wrong requested audience400 `invalid_request`
Refresh token requested400 `invalid_request`
Unconfigured certificate with own subject400 `invalid_request`
Unconfigured client400 `invalid_request`
Two browsers on one user sessionDistinct bindings retained
IntrospectionPreserves the original token binding
Login token at introspectionCannot gain a binding
Tampered binding signatureRejected
Logout with the wrong certificate401; token remains active
Logout with the matching certificate204; token becomes inactive while still unexpired
Application listenerHTTPS only

Scope and limits

The fixture uses a disposable private CA, synthetic credentials and host loopback only. A password grant creates the synthetic session; it is not a proposed production login flow. No credentials, tokens or private keys are published here. The owned issuer container was removed after the run; private material stayed local.

This is an internal Keycloak SPI and must be requalified before an issuer upgrade.

Changed-module coverage

Measured at `3172b61` with `cargo-llvm-cov` on the stable toolchain, default library suite. Per-module figures are in coverage.json. Branch-outcome coverage needs a nightly toolchain and is not reported here, so the 75% branch half of the gate remains unmeasured in this run.

ModuleLinesRegions80% line gate
`desktop_grants.rs`99.40%96.43%pass
`desktop_enrollment.rs`98.21%86.96%pass
`http/desktop.rs`97.56%96.86%pass
`desktop_keycloak.rs`96.62%93.48%pass
`desktop_enrollment/resources.rs`95.92%84.97%pass
`desktop_admission.rs`94.88%91.17%pass
`desktop_enrollment/controller.rs`93.86%83.96%pass
`desktop_enrollment/reconciliation.rs`92.09%81.27%pass
`http/desktop/resources.rs`90.80%89.36%pass
`desktop_startup.rs`90.62%83.27%pass
`desktop_worker.rs`87.82%82.35%pass
`desktop_admission/worker.rs`84.78%61.17%pass
`desktop_admission/resources.rs`74.55%69.70%unmet
`desktop_guest.rs`0.00%0.00%unmet

Two modules miss the line gate. `desktop_admission/resources.rs` sits just under it. `desktop_guest.rs` reports zero because its only test is the opt-in live root fixture, which the default suite ignores; the adapter it drives is now covered separately by test-desktop-reconcile.py, but the Rust wrapper itself still has no offline test.

Report generation needed a direct `llvm-cov` invocation against the single test binary. Routing it through `cargo llvm-cov report` segfaulted inside the LLVM tool while merging the extra build-script objects it passes; the instrumented test run itself passed 1047 tests with no failures.