Skip to content

localjwtauthority: Prep for rotation support - #1313

Merged
Max Thompson (thompsonmax) merged 1 commit into
agent-substrate:mainfrom
ahmedtd:ca-rotation
Sep 3, 2026
Merged

localjwtauthority: Prep for rotation support#1313
Max Thompson (thompsonmax) merged 1 commit into
agent-substrate:mainfrom
ahmedtd:ca-rotation

Conversation

@ahmedtd

Copy link
Copy Markdown
Collaborator
  • Add a RefreshingPool abstraction like localca.RefreshingPool. This provides caching (so we don't read the keys fresh from the filesystem) for each signing operation.
  • Extend ConcretePool so that it tracks which key is currently active for signing, instead of always picking the first key.

A followup PR will add admin commands for actually rotating the JWT pool.

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.

Mostly LGTM, just some nits and potential testing gap

Comment thread cmd/ateapi/main.go Outdated
actorIDCAPool, err := localca.NewRefreshingPool(*actorIDCAPoolFile)
if err != nil {
serverboot.Fatal(ctx, "while loading the Actor ID CA", err)
serverboot.Fatal(ctx, "while loading the Actor ID certificate authority pool: %w", err)

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] %w is printed verbatim because we're calling Fatal rather than Fatalf

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done.

Comment thread cmd/ateapi/main.go Outdated
actorIdentitySrv := actoridentity.New(actorIdentityJWTIssuer, *actorIDJWTPoolFile, actorIDCAPool, persistence, workerCache)
actorIDJWTAuthorityPool, err := localjwtauthority.NewRefreshingPool(*actorIDJWTPoolFile)
if err != nil {
serverboot.Fatal(ctx, "while loading the Actor ID JWT authority pool: %w", err)

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] %w is printed verbatim because we're calling Fatal rather than Fatalf

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done.

return "", fmt.Errorf("selected authority %q not present", p.ActiveForSigning)
}
} else {
// Fall back to first entry.

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.

Should we have some TODO on removing this fall back at some point? (similar to the fallback on the CA pool impl)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done.

return "", fmt.Errorf("while marshaling payload: %w", err)
}

// TODO(ahmedtd): Select authority

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] stale TODO? Seems like you're selecting the authority here :)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Whoops, done.

if err != nil {
t.Fatalf("MarshalECPrivateKey(): %v", err)
pool1 := &ConcretePool{
Authorities: []*Authority{ca1},

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.

There's no tests here that exercise a pool that has multiple authorities set, unless I'm missing something. Should we add unit tests for that?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I've made this test case simulate a full rotation.

// designated as active for signing. The rest are inactive, but are still
// trusted for verifying JWTs.
//
// The active/inactive desngination allows a Pool to be seamlessly rotated.

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.

[typo] 'desngination' -> 'designation'

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done.

* Add a RefreshingPool abstraction like localca.RefreshingPool.  This
  provides caching (so we don't read the keys fresh from the filesystem)
  for each signing operation.
* Extend ConcretePool so that it tracks which key is currently active
  for signing, instead of always picking the first key.

A followup PR will add admin commands for actually rotating the JWT
pool.
@thompsonmax

Copy link
Copy Markdown
Collaborator

Approved, waiting on CI tests to merge.

@thompsonmax
Max Thompson (thompsonmax) merged commit daf5a13 into agent-substrate:main Sep 3, 2026
9 checks passed
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.

2 participants