fix: set application_id when provisioning resources - #27
Merged
Conversation
bin/provision created resources with credential_provider_id and linked the application as a dependency, never as the resource owner. svc-sts gates re-exchange on that owner: it compares resource.application_id against the authenticated client before accepting a zone-issued access token as a subject_token. With the field unset the comparison runs against undefined and every exchange fails with invalid_grant, while impersonation keeps working, so it presents as a token problem rather than a provisioning one. Dependencies do not stand in for it: they let the application request tokens, not own the resource. Sets it at resource-creation time and backfills existing zones with an idempotent PATCH. This closes the last live coverage gap, and not the way the checklist expected. The production grant path is now a live row: an inbound caller token exchanged for one token per downstream resource, with an impersonated token standing in for the verified inbound token, so no browser login is involved. Two live rows change with it. The re-exchange row asserted a refusal and called it an impersonation invariant; it was recording the missing owner, so it becomes onward delegation succeeding with the subject preserved across the hop. The malformed-subject-token row claimed invalid_grant in its label while accepting any OAuth error in its body; it now asserts invalid_request, which is what a zone actually emits and a real divergence from the spec. 13 of 13 live rows pass.
The a2a example registers its own Agent B resource and had the same omission, so agent A's exchange leg would fail against a zone it provisioned itself. Sets it at creation and backfills with a PATCH. Also corrects the README: bin/live takes the impersonation fallback because that harness sends no inbound bearer token, not because the zone refuses to exchange one. The old text attributed the fallback to a refusal that was really the missing resource owner. bin/live re-run against the live zone: 6 checks pass, act still nil.
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.
Fixes the provisioning bug under the retracted finding, and it closes ECO-297 as a side effect.
The bug
bin/provisioncreated resources withcredential_provider_idand linked the application as a dependency, never as the resource owner.svc-stsgates re-exchange on that owner: it comparesresource.application_idagainst the authenticated client before accepting a zone-issued access token as asubject_token. With the field unset the comparison runs againstundefinedand every exchange fails withinvalid_grant, while impersonation keeps working — so it presents as a token problem rather than a provisioning one. Dependencies don't stand in for it: they let the application request tokens, not own the resource.Now set at creation time, with an idempotent PATCH to backfill zones provisioned before this.
Verified by flipping it
I patched the two resources in the E2E zone and re-ran the same probe:
invalid_grantinvalid_grantaud= downstreaminvalid_targetinvalid_targetSo the "impersonated tokens are not re-exchangeable" claim was entirely an artifact of our own provisioning. With an owner set, an impersonated token re-exchanges normally.
Worth stating plainly since the old text called it anti-laundering: re-exchange confers nothing the caller couldn't already get. The same client can impersonate that user directly for the downstream resource, which the table above shows succeeding both before and after. Nothing is escalated.
ECO-297 is closed, without a browser login
The production
grantpath is a live row now. An inbound caller token is exchanged for one token per downstream resource, with an impersonated token standing in for the verified inbound token:Subject is preserved across the hop, verified separately (
subidentical on both issued tokens). The checklist assumed this needed one interactive browser login; it needed the owner field.Two live rows corrected
The re-exchange row asserted a refusal and labelled it an impersonation invariant. It becomes onward delegation succeeding, asserting the subject survives the hop.
The malformed-subject-token row claimed
invalid_grantin its label while its body accepted anyOAuthError. It now assertsinvalid_request, which is what a zone actually emits. That makes it a real divergence from the spec and restores the half of the earlier error-code finding that was correct: the spec listsinvalid_grantfor a rejected subject token, and a malformed one yieldsinvalid_request. The half I retracted stays retracted, sinceinvalid_targetis emitted exactly as specified.The a2a example had the same omission
examples/a2a-delegation/bin/liveregisters its own Agent B resource and also left the owner unset, so agent A's exchange leg would fail against a zone that harness provisioned itself. Fixed the same way.Its README also blamed the impersonation fallback on the zone refusing to exchange a token. The real reason is narrower:
bin/livesends no inbound bearer token, so agent A has nothing to exchange. Corrected.Verified
bin/live-e2e: 13 of 13 pass, 0 fail, 0 skip.examples/a2a-delegation/bin/live: 6 checks pass (actstill nil, unchanged finding).bundle exec rake: 166 examples, 0 failures, RuboCop clean.Live-zone state changed: the two resources in zone
rhq9vepbjm8pfld7n2pz2lgys5now carryapplication_id. That was the point, and re-runningbin/provisionis idempotent.