Skip to content

fix: set application_id when provisioning resources - #27

Merged
Larry-Osakwe merged 2 commits into
mainfrom
larry/fix-resource-ownership-provisioning
Aug 20, 2026
Merged

fix: set application_id when provisioning resources#27
Larry-Osakwe merged 2 commits into
mainfrom
larry/fix-resource-ownership-provisioning

Conversation

@Larry-Osakwe

@Larry-Osakwe Larry-Osakwe commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Fixes the provisioning bug under the retracted finding, and it closes ECO-297 as a side effect.

The bug

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 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:

Probe Before After
re-exchange for the same resource invalid_grant succeeds
re-exchange for a different resource invalid_grant succeeds, aud = downstream
impersonate directly for that resource succeeds succeeds
unregistered target invalid_target invalid_target

So 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 grant path 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:

grant-decorator  1: the grant path exchanges an inbound token for every resource  PASS  status=success, both resources granted

Subject is preserved across the hop, verified separately (sub identical 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_grant in its label while its body accepted any OAuthError. It now asserts invalid_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 lists invalid_grant for a rejected subject token, and a malformed one yields invalid_request. The half I retracted stays retracted, since invalid_target is emitted exactly as specified.

The a2a example had the same omission

examples/a2a-delegation/bin/live registers 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/live sends 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 (act still nil, unchanged finding). bundle exec rake: 166 examples, 0 failures, RuboCop clean.

Live-zone state changed: the two resources in zone rhq9vepbjm8pfld7n2pz2lgys5 now carry application_id. That was the point, and re-running bin/provision is idempotent.

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.
@Larry-Osakwe
Larry-Osakwe merged commit c8ad4c9 into main Aug 20, 2026
12 checks passed
@Larry-Osakwe
Larry-Osakwe deleted the larry/fix-resource-ownership-provisioning branch August 20, 2026 02:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant