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.
| Property | Value |
|---|---|
| Artifact | `desktop-browser-binding.jar` |
| SHA-256 | `7cb09c2261b399809b66537b2f12ea2ff1a272478a4060b4cb2037a480fb5672` |
| Independent builds compared | 2, identical |
| Network during compile | none |
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.
| Case | Outcome |
|---|---|
| Exchange with the configured Bridge certificate | 200; certificate-bound, `aud=gateway`, `azp=bridge`, session present, browser audience present |
| Gateway introspection of that token | Active, certificate-bound, browser audience preserved |
| Exchange with another trusted certificate | 400 `invalid_request` |
| Exchange with no client certificate | 400 `invalid_request` |
| Binding absent, empty, malformed or oversized | 400 `invalid_request` |
| Duplicate binding or duplicate audience | 400 `invalid_request` |
| Re-exchanging an existing delegation to rebind | 400 `invalid_request` |
| Wrong requested audience | 400 `invalid_request` |
| Refresh token requested | 400 `invalid_request` |
| Unconfigured certificate with own subject | 400 `invalid_request` |
| Unconfigured client | 400 `invalid_request` |
| Two browsers on one user session | Distinct bindings retained |
| Introspection | Preserves the original token binding |
| Login token at introspection | Cannot gain a binding |
| Tampered binding signature | Rejected |
| Logout with the wrong certificate | 401; token remains active |
| Logout with the matching certificate | 204; token becomes inactive while still unexpired |
| Application listener | HTTPS 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.
| Module | Lines | Regions | 80% 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.