Skip to content

objectstore-plugin: move the credential exchange's actor checks into actorauthz - #3138

Open
Anish Ramasekar (aramase) wants to merge 2 commits into
agent-substrate:mainfrom
aramase:aramase/c/exchange_authz_extract
Open

Anish Ramasekar (aramase) wants to merge 2 commits into
agent-substrate:mainfrom
aramase:aramase/c/exchange_authz_extract

Conversation

@aramase

@aramase Anish Ramasekar (aramase) commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Step 1 of #3095. No behavior change.

ExchangeActorJwt runs the checks that guard the GCS credential exchange inline. A second provider's exchange would have to copy them and could drift from them.

  • New package internal/objectstoreplugin/actorauthz. Authorizer.Authorize verifies the worker-for-actor JWT, looks the actor up, checks it is still on the token's node, and derives its grants. Its Decision carries only ActorUID and Grants. Codes and messages are unchanged.
  • exchange.Verifier and exchange.Lookup are deleted. exchange.New takes the actorauthz interfaces, and exchange.Server calls Authorize, then the issuer.
  • exchange.ActorJWTAudience becomes actorauthz.Audience, with the same value. atelet, the plugin and the snapshotcredentials e2e suite use it.
  • actorauthz imports no provider package or cloud SDK, and a test enforces that.

Tested with go test -race on ./internal/objectstoreplugin/..., ./cmd/objectstore-plugin/..., ./cmd/atelet/... and ./internal/resources/... on Linux.

  • Tests pass
  • Docs updated

AI use: I used Claude Code to implement and review this change, and reviewed the diff myself.

@aramase

Copy link
Copy Markdown
Collaborator Author

cc ILLUM1N0X

@ILLUM1N0X

Copy link
Copy Markdown
Collaborator

A few things:

  1. The description still says exchange.Verifier and exchange.Lookup are aliases, but the diff deletes them. Could you update it and mention the audience move?
  2. Nothing reads Decision.Atespace, ActorName or Node yet. Could we drop them until a caller needs them, so they don't carry over into the pkg/ wrapper?
  3. New says the verifier must accept only Audience, but nothing enforces that. Fine for this PR, but I'd like the pkg/ wrapper to build the verifier itself from Audience, as your sketch does.

@aramase

Copy link
Copy Markdown
Collaborator Author

ILLUM1N0X updated, PTAL!

@aramase
Anish Ramasekar (aramase) force-pushed the aramase/c/exchange_authz_extract branch from 841b626 to e36a75f Compare October 26, 2026 22:02
@BenTheElder

Benjamin Elder (BenTheElder) commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Julian Gutierrez Oschmann (@juli4n) can you take a look re: exposing APIs to enable external storage plugins?
[trying to loadbalance a bit]

@thockin

Copy link
Copy Markdown
Collaborator

Given the code-slush, can you help me understand if this is fixing a CRITICAL P0 issue, or if this should queue up for post-1.0 (just a couple weeks)

@aramase

Copy link
Copy Markdown
Collaborator Author

Tim Hockin (@thockin) it doesn't fix a bug, it's step 1 of #3095 for the provider-neutral work in #3161: no behavior change, it moves the actor checks out of the GCS exchange into actorauthz so another provider's plugin can reuse them instead of copying them. The plan is this plus one follow-up that exposes those packages under pkg/; the Azure Blob plugin itself lives in its own repo, so nothing Azure-specific lands in substrate. We'd like to get both into 1.0 so Azure Blob support can ship alongside the release. cc ILLUM1N0X

@ILLUM1N0X ILLUM1N0X added this to the M3 milestone Oct 27, 2026
@thockin Tim Hockin (thockin) added prio/P0 Highest priority / required for next milestone milestone-ack labels Oct 27, 2026
@bowei Bowei Du (bowei) added area/security Security related issue/pr area/storage kind/cleanup Small fixes that are not bugs, for example a typo in a code comment labels Oct 27, 2026
@aramase
Anish Ramasekar (aramase) force-pushed the aramase/c/exchange_authz_extract branch from e36a75f to f2fa59c Compare October 28, 2026 00:28
ExchangeActorJwt verified the JWT, looked the actor up, checked it is
still on the token's node and derived its grants inline. Move those
checks into actorauthz.Authorizer so another provider's exchange can
apply the same checks. The exchange now calls Authorize and then the
issuer. Codes and messages are unchanged.

Signed-off-by: Anish Ramasekar <anish.ramasekar@gmail.com>
…uthz

Every exchange serving the objectstore plugin API requires this audience,
so it belongs with the checks every exchange runs. Rename
exchange.ActorJWTAudience to actorauthz.Audience and update atelet, the
plugin and the e2e suite. The value is unchanged.

Signed-off-by: Anish Ramasekar <anish.ramasekar@gmail.com>
@aramase

Copy link
Copy Markdown
Collaborator Author

The run-tests failure here is the TestNon200IsAnError flake in cmd/benchmarking/egress; #3374 fixes it.

}
// Vendor mode is what the build uses, and keeps a GOFLAGS=-mod=mod
// environment from rewriting go.sum.
out, err := exec.Command("go", "list", "-mod=vendor", "-deps", "-f", "{{.ImportPath}}", ".").Output()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already have hack/verify/ateapi-deps.sh doing this kind of check for cmd/ateapi, across every platform in .ko.yaml. Can we extend that (or add a sibling script) instead of shelling out to go list from a unit test? This only checks the host GOOS/GOARCH, and it's the only unit test in the repo that runs the go toolchain.

alternatively for both we can restrict imports via golangci-lint ... we really shouldn't need to homebrew something here

func New(verifier Verifier, lookup Lookup, issuer Issuer) *Server {
return &Server{verifier: verifier, lookup: lookup, issuer: issuer, now: time.Now}
// New returns a Server. The verifier must accept actorauthz.Audience.
func New(verifier actorauthz.Verifier, lookup actorauthz.Lookup, issuer Issuer) *Server {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking: can New take the *actorauthz.Authorizer (or a one-method interface) instead of building it here?

Then exchange doesn't need to know about the verifier or the lookup, the audience precondition is only written once (this says "must accept", actorauthz.New says "must accept only"), and the exchange tests could fake the decision instead of rebuilding a tagged running actor on node-1 just to reach the issuer.

if got != nil {
t.Errorf("decision = %+v, want nil", got)
}
if st := status.Convert(err); st.Code() != tc.want || st.Message() != tc.wantMsg {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the old exchange tests checked codes only, these pin the full message, including grpc's rpc error: code = ... desc = ... formatting, and notValid re-runs the real verifier to build the expected string. That's handy for showing the move didn't change anything, but going forward I think code + lookedUp is enough, and won't break when oidcjwt or grpc error text changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/security Security related issue/pr area/storage kind/cleanup Small fixes that are not bugs, for example a typo in a code comment milestone-ack prio/P0 Highest priority / required for next milestone

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants