Repository navigation
Conversation
gtema
commented
Oct 21, 2026
Collaborator
- feat(oauth2): Validate delegation role ceiling
- fix(service_grant): Key redemption limit per SVID
- test(service_grant): Add Raft integration tests
- test(api): Add live service grant tests
- test(api): Redeem service grants with a real SVID
- docs(adr): Add on-behalf-of phase 2 design
- feat(auth): Add on-behalf-of auth context
- feat(oauth2): Add on-behalf-of exchange logic
- feat(oauth2): Serve on-behalf-of token exchange
- test(oauth2): Cover on-behalf-of with raft tests
- fix(oauth2): Harden on-behalf-of exchange
- fix(oauth2): Key chain revocation on first audit id
Cover the service grant API against a live server: the grant lifecycle, the delegatable service listing hiding SPIFFE IDs, and rejected or foreign callers. Another user's grant answers as an id that never existed, so the response is no oracle for grant ids. Prove a grant can not be consumed through the public interface: a redemption with the grantee client id, a forged SPIFFE identity, or the grantor's own token is refused with invalid_client and issues no token, and a live and an unknown grant answer alike. The internal interface refuses a client without an SVID at the TLS handshake. Add an [interface_internal] section to the API test server so client registration accepts SPIFFE IDs of the example.org trust domain. Assisted-By: Claude Sonnet 5.5 <noreply@anthropic.com> Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
The live harness already runs SPIRE, so redeem a grant over the internal mTLS listener with an X.509-SVID fetched from the agent. start-spire.sh registers two more workload entries for the test uid. The svid_client helper picks one of the SVIDs the agent hands out by its SPIFFE ID and posts a form over mTLS. A client's spiffe_ids may not overlap another's, so the redeeming test owns one entry. Cover a successful redemption (token claims, act.sub, repeated redemption, last_redeemed_at, refusal after revocation) and the refusal of a valid SVID that is not one of the client's spiffe_ids. Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
Record how phase 2 implements the on-behalf-of exchange: accepted subjects, SVID client authentication, the OnBehalfOf context shape, root revocation and the operation lifetime bound. Assisted-By: Claude Sonnet 5.5 <noreply@anthropic.com> Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
Add AuthenticationContext::OnBehalfOf and the matching delegation claim. The chain keeps the subject's project, role ceiling, methods, auth time and root token id, so a hop token can be exchanged again without the original context. A delegated subject reaches only its own project and stays bounded by its roles on every hop. A plain subject may use project or domain scope. Nothing else is reachable and no Fernet token is ever minted from the context. Assisted-By: Claude Sonnet 5.5 <noreply@anthropic.com> Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
Exchange a user's Keystone token, or the hop token of an earlier exchange by the same client, for an OnBehalfOf security context bound to the authenticated client. The client must authenticate by its SVID and hold the on_behalf_of policy. Trust, service grant and other actors' tokens are refused. The chain ends with its root token's revocation, the user's or domain's state, the live roles and the client's operation lifetime. Assisted-By: Claude Sonnet 5.5 <noreply@anthropic.com> Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
A service that authenticates with its SVID on the internal interface and presents no client secret now exchanges a user's token through the existing token-exchange grant. subject_token_type selects a Keystone token or the hop token of an earlier exchange. The token names the user as sub and the service as act, never outlives the client's operation lifetime and carries no refresh token. Each exchange emits a CADF event with an on_behalf_of attachment that records the root token, the user, the actor and the delegated project. Assisted-By: Claude Sonnet 5.5 <noreply@anthropic.com> Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
Drive the exchange end to end against real providers: a user's real token is exchanged, the result is signed with the domain key and exchanged again. The chain keeps its root and auth time, and ends when the root token is revoked, the role is removed or the client's operation lifetime passes. Another client cannot continue the chain. Document the exchange in the admin guide and record the ceiling decision in the ADR. Assisted-By: Claude Sonnet 5.5 <noreply@anthropic.com> Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
Close the gaps found reviewing the on-behalf-of exchange. - Reject restricted application credentials as subject, as their restriction cannot be carried along. - Treat every on-behalf-of token as delegated, so trust, credential and EC2 policies bound a token a service holds. - Carry the application credential id and recheck that it exists and has not expired on every hop. - Derive auth_time from the token expiry and use the last audit id as root_jti, so rescoping neither extends nor hides the chain. - Keep a renewed token within the audience of the hop token. - Audit refused exchanges and read client_id from Basic auth too. - Look up max_role_ids concurrently, share the rate limit key helper and drop an unreachable branch. Assisted-by: Claude <noreply@anthropic.com> Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
Revocation events are recorded under the first audit id of the revoked token, so the chain must be keyed on it to end when the token the user handed over is revoked. The last id matched no event. Assisted-by: Claude <noreply@anthropic.com> Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
Set the new cnf claim and dpop_bound_access_tokens field that landed on main. On-behalf-of tokens stay bearer tokens, like the other service grants. Drop a stray conflict marker left in the live service grant test. Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
|
| Project | keystone |
| Branch | service_grant |
| Testbed | ubuntu-latest |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result nanoseconds (ns) (Result Δ%) | Upper Boundary nanoseconds (ns) (Limit %) |
|---|---|---|---|
| Command_Serde/apply/remove | 📈 view plot 🚷 view threshold | 84,301.00 ns(-38.49%)Baseline: 137,054.11 ns | 262,278.58 ns (32.14%) |
| Command_Serde/apply/set | 📈 view plot 🚷 view threshold | 91,495.00 ns(-35.54%)Baseline: 141,939.64 ns | 247,142.65 ns (37.02%) |
| Command_Serde/pack/delete | 📈 view plot 🚷 view threshold | 102.02 ns(-11.12%)Baseline: 114.79 ns | 164.59 ns (61.98%) |
| Command_Serde/pack/delete_index | 📈 view plot 🚷 view threshold | 92.31 ns(-10.57%)Baseline: 103.22 ns | 144.53 ns (63.87%) |
| Command_Serde/pack/set | 📈 view plot 🚷 view threshold | 163.35 ns(-12.19%)Baseline: 186.04 ns | 270.77 ns (60.33%) |
| Command_Serde/pack/set_index | 📈 view plot 🚷 view threshold | 92.19 ns(-11.01%)Baseline: 103.59 ns | 146.59 ns (62.89%) |
| Command_Serde/unpack/delete | 📈 view plot 🚷 view threshold | 141.18 ns(-2.27%)Baseline: 144.45 ns | 217.38 ns (64.95%) |
| Command_Serde/unpack/delete_index | 📈 view plot 🚷 view threshold | 124.47 ns(+1.76%)Baseline: 122.32 ns | 183.06 ns (67.99%) |
| Command_Serde/unpack/set | 📈 view plot 🚷 view threshold | 197.09 ns(-11.83%)Baseline: 223.54 ns | 345.25 ns (57.09%) |
| Command_Serde/unpack/set_index | 📈 view plot 🚷 view threshold | 119.38 ns(-1.98%)Baseline: 121.79 ns | 180.73 ns (66.05%) |
| Payload_encryption/pack/remove_cmd | 📈 view plot 🚷 view threshold | 97.16 ns(-8.86%)Baseline: 106.60 ns | 159.50 ns (60.92%) |
| Payload_encryption/pack/set_cmd | 📈 view plot 🚷 view threshold | 157.45 ns(-17.98%)Baseline: 191.96 ns | 292.67 ns (53.80%) |
| Payload_encryption/unpack/remove_cmd | 📈 view plot 🚷 view threshold | 148.93 ns(+1.61%)Baseline: 146.57 ns | 218.78 ns (68.07%) |
| Payload_encryption/unpack/set_cmd | 📈 view plot 🚷 view threshold | 201.50 ns(-11.40%)Baseline: 227.43 ns | 349.83 ns (57.60%) |
| Raft_1Node_Latency/prefix/1node | 📈 view plot 🚷 view threshold | 3,860,100.00 ns(+6.39%)Baseline: 3,628,337.66 ns | 8,736,647.71 ns (44.18%) |
| Raft_1Node_Latency/read/1node | 📈 view plot 🚷 view threshold | 24,138.00 ns(-29.48%)Baseline: 34,230.17 ns | 52,373.44 ns (46.09%) |
| Raft_1Node_Latency/remove/1node | 📈 view plot 🚷 view threshold | 229,640.00 ns(-35.18%)Baseline: 354,294.69 ns | 597,366.53 ns (38.44%) |
| Raft_1Node_Latency/write/1node | 📈 view plot 🚷 view threshold | 242,580.00 ns(-37.80%)Baseline: 389,994.84 ns | 770,143.20 ns (31.50%) |
| build_snapshot/default | 📈 view plot 🚷 view threshold | 1,743.00 ns(-15.83%)Baseline: 2,070.79 ns | 2,784.15 ns (62.60%) |
| fernet token/project | 📈 view plot 🚷 view threshold | 1,165.10 ns(-7.88%)Baseline: 1,264.76 ns | 1,753.62 ns (66.44%) |
| get_data_keyspace | 📈 view plot 🚷 view threshold | 0.27 ns(-6.80%)Baseline: 0.29 ns | 0.38 ns (72.18%) |
| get_db | 📈 view plot 🚷 view threshold | 0.27 ns(-7.04%)Baseline: 0.29 ns | 0.38 ns (72.18%) |
| get_fernet_token_timestamp/project | 📈 view plot 🚷 view threshold | 114.98 ns(-12.19%)Baseline: 130.94 ns | 183.07 ns (62.81%) |
| get_keyspace | 📈 view plot 🚷 view threshold | 3.53 ns(-46.75%)Baseline: 6.62 ns | 15.45 ns (22.84%) |
|
🦢 Load Test Results Goose Attack ReportPlan Overview
Request Metrics
Response Time Metrics
Status Code Metrics
Transaction Metrics
Scenario Metrics
Error Metrics
|
|
🧪 Tempest Identity Compatibility Results (advisory, non-blocking) rust Failed test IDs |
Gate J rejects wildcard arms in validate_scope_boundaries. List the Some and None cases of the on-behalf-of project check explicitly. Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
This branch has not been deployed
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.