[release] feat(lib-pairing): record the token issuer on the pairing record — v1.2.0 [COMP-2357] - #125
Closed
munishchouhan wants to merge 1 commit into
Closed
[release] feat(lib-pairing): record the token issuer on the pairing record — v1.2.0 [COMP-2357]#125munishchouhan wants to merge 1 commit into
munishchouhan wants to merge 1 commit into
Conversation
The pairing URL carried only `endpoint` — where a paired service receives API calls. That is not the `iss` the service stamps on the tokens it signs, and on Seqera Cloud the two genuinely differ: the API is served at https://api.cloud.seqera.io while the issuer is https://cloud.seqera.io/api. A verifier holding one cannot derive the other. That gap blocks seqeralabs/platform#12431, which already sends `issuer=` on the Sched pairing URL: the route did not bind the param, so Micronaut never passed it to anything and it was silently dropped. It also blocks the Scheduler work it exists for — verifying Platform-issued tokens against a published JWKS (seqeralabs/sched#813) needs a per-installation issuer to resolve a key set from, and taking it from a caller-supplied header would put the tenant binding outside the signed token. Add `issuer` alongside the existing `token`, following that field exactly: - `PairingWebSocket` route becomes `{?endpoint,issuer}`, with the value bound in `@OnOpen` and passed to the service. - `PairingRecord` gains a `String issuer`. - `PairingService` gains a 4-arg `acquirePairingKey(service, endpoint, token, issuer)`; the 2- and 3-arg forms delegate to it. - `PairingServiceImpl` refreshes `issuer` on re-pair in the same `store.put` as `token`, so a record can never persist with only half the update applied. Backward compatible in both directions. The bound param is `@Nullable` because a client predating it omits the query string entirely — without that, binding would fail for every existing caller. The shorter overloads are retained, so nothing that compiles today breaks. And a record written before the field decodes with a null issuer rather than erroring. A re-pair that omits the issuer deliberately does NOT erase a recorded one: a rolled-back client would otherwise silently drop the trust anchor for a service that had already announced it. The issuer is only recorded here, not yet trusted. Turning it into a JWKS lookup — gated so an issuer must belong to a service that actually paired — is Scheduler-side work. Tests: lib-pairing 37 pass. Five new cases cover recording, the null case, the refresh-on-re-pair, the omit-does-not-erase rule, and token+issuer refreshed together; two more cover serialization and decoding a pre-issuer record. `PairingServiceIssuerTest` uses a real `PairingStore` over the in-memory `LocalStateProvider` rather than a Spock mock: `PairingStore` is a concrete class and cglib cannot proxy classes on Java 25 ("Unsupported class file major version 69"). The real store also exercises the Moshi round-trip on every put/get, so a field the encoder cannot handle surfaces in the test. Signed-off-by: munishchouhan <hrma017@gmail.com>
Contributor
|
Think we had already discussed this in the past with Jon and discard it |
Member
Author
|
i will close it |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The pairing URL carries only
endpoint— where a paired service receives API calls. That is not theissthe service stamps on the tokens it signs, and on Seqera Cloud the two genuinely differ:A verifier holding one cannot derive the other.
That gap blocks two things already in flight:
issuer=on the Sched pairing URL. The route did not bind the param, so Micronaut never passed it to anything and it was silently dropped — the PR is inert until this lands.TOWER_JWT_SECRET. For a customer-hosted Platform, Sched needs a per-installation issuer to resolve a key set from. Taking it from a caller-supplied header instead would put the tenant binding outside the signed token, so it has to arrive at pairing time.What
issueradded alongside the existingtoken, following that field exactly:PairingWebSocket{?endpoint,issuer}; value bound in@OnOpenand passed to the servicePairingRecordString issuerPairingServiceacquirePairingKey(service, endpoint, token, issuer); 2- and 3-arg forms delegate to itPairingServiceImplissueron re-pair in the samestore.putastoken, so a record cannot persist with half the update appliedThe issuer is only recorded here, not yet trusted. Turning it into a JWKS lookup — gated so an issuer must belong to a service that actually paired — is Scheduler-side work.
Compatibility — both directions
@Nullablebecause a client predating it omits the query string entirely. Without that, binding would fail for every existing caller.byte[]encoding Moshi uses.One deliberate behaviour: a re-pair that omits the issuer does not erase a recorded one. A rolled-back client would otherwise silently drop the trust anchor for a service that had already announced it.
Testing
:lib-pairing:test— 37 pass, 0 failures.Five new cases in
PairingServiceIssuerTest: recording, the null case, refresh-on-re-pair (asserting the key pair is not regenerated), the omit-does-not-erase rule, and token+issuer refreshed together. Two more inPairingRecordSerializationTestfor the round-trip and the pre-issuer record.PairingServiceIssuerTestuses a realPairingStoreover the in-memoryLocalStateProviderrather than a Spock mock —PairingStoreis a concrete class and cglib cannot proxy classes on Java 25 (Unsupported class file major version 69). Using the real store also exercises the Moshi round-trip on every put/get, so a field the encoder cannot handle surfaces here rather than in production.Release
lib-pairing/VERSION→ 1.2.0, README dependency snippet updated. Minor rather than patch: new public API surface, additive only.Downstream, after this releases
acquirePairingKeygained a third arg in 1.1.0 and sched is still on 1.0.0.iss, gated on an existingPairingRecord, which is what makes Enterprise multi-tenancy work.