Skip to content

vault: add generic authorizer with jwt auth support#21714

Open
prashantkumar1982 wants to merge 15 commits intodevelopfrom
codex/vault-gwt-auth-authorizer
Open

vault: add generic authorizer with jwt auth support#21714
prashantkumar1982 wants to merge 15 commits intodevelopfrom
codex/vault-gwt-auth-authorizer

Conversation

@prashantkumar1982
Copy link
Contributor

@prashantkumar1982 prashantkumar1982 commented Mar 26, 2026

Summary

This adds the Vault auth abstraction needed for gateway-side JWT support while keeping current behavior unchanged because JWT auth remains disabled.

What changed

  • add a generic Authorizer used by both the Vault gateway handler and the Vault capability gateway handler
  • model two first-class auth mechanisms: AllowListBasedAuth and JWTBasedAuth
  • add a shared AuthResult contract and a shared RequestReplayGuard
  • implement JWT auth validation plumbing behind JWTBasedAuth, gated internally and failing closed when disabled
  • rename the old request-authorizer/replay-guard types and files to reflect the new mechanism names

Behavior

  • runtime behavior is unchanged today because JWT auth is still disabled
  • allowlist-based auth remains the active mechanism for existing traffic

…th-authorizer

# Conflicts:
#	core/capabilities/vault/gw_handler.go
#	core/capabilities/vault/gw_handler_test.go
#	core/services/ocr2/delegate.go
@github-actions
Copy link
Contributor

github-actions bot commented Mar 26, 2026

✅ No conflicts with other open PRs targeting develop

@github-actions
Copy link
Contributor

I see you updated files related to core. Please run make gocs in the root directory to add a changeset as well as in the text include at least one of the following tags:

  • #added For any new functionality added.
  • #breaking_change For any functionality that requires manual action for the node to boot.
  • #bugfix For bug fixes.
  • #changed For any change to the existing functionality.
  • #db_update For any feature that introduces updates to database schema.
  • #deprecation_notice For any upcoming deprecation functionality.
  • #internal For changesets that need to be excluded from the final changelog.
  • #nops For any feature that is NOP facing and needs to be in the official Release Notes for the release.
  • #removed For any functionality/config that is removed.
  • #updated For any functionality that is updated.
  • #wip For any change that is not ready yet and external communication about it should be held off till it is feature complete.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This file is pretty much just a rename of previous request_authorizer.go file

@trunk-io
Copy link

trunk-io bot commented Mar 26, 2026

Static BadgeStatic BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

- Cap JWKS response body to 1 MB to prevent resource exhaustion
- Make all AuthResult fields unexported with accessor methods
- Add iat claim validation to JWT parser
- Annotate unreachable return in allowlist retry loop
- Fix misleading test name for digest verification delegation

Made-with: Cursor
@prashantkumar1982 prashantkumar1982 force-pushed the codex/vault-gwt-auth-authorizer branch from 77cda12 to 68f66e2 Compare March 26, 2026 06:43
…th-authorizer

# Conflicts:
#	core/capabilities/vault/allow_list_based_auth_test.go
#	core/capabilities/vault/request_authorizer.go
@github-actions
Copy link
Contributor

CORA - Pending Reviewers

Codeowners Entry Overall Num Files Owners
* 💬 4 @smartcontractkit/foundations, @smartcontractkit/core
/core/capabilities/ 💬 13 @smartcontractkit/keystone, @smartcontractkit/capabilities-team
/core/services/ocr* 💬 1 @smartcontractkit/foundations, @smartcontractkit/core
go.mod 💬 6 @smartcontractkit/core, @smartcontractkit/foundations
go.sum 💬 6 @smartcontractkit/core, @smartcontractkit/foundations
integration-tests/go.mod 💬 1 @smartcontractkit/core, @smartcontractkit/devex-tooling, @smartcontractkit/foundations
integration-tests/go.sum 💬 1 @smartcontractkit/core, @smartcontractkit/devex-tooling, @smartcontractkit/foundations

Legend: ✅ Approved | ❌ Changes Requested | 💬 Commented | 🚫 Dismissed | ⏳ Pending | ❓ Unknown

For more details, see the full review summary.

@cl-sonarqube-production
Copy link

@@ -0,0 +1,99 @@
// Code generated by mockery v2.53.0. DO NOT EDIT.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not stub it with mockery? Would save quite a bit of code / maintenance

expiresAt int64
}

func NewAllowListBasedAuthResult(workflowOwner, digest string, expiresAt int64) *AuthResult {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: having a function seems a bit overkill to construct a struct (especially if we support more auth types), we could just inline this.

if a.orgID != "" {
return a.orgID
}
return a.workflowOwner
Copy link
Contributor

Choose a reason for hiding this comment

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

Aren't we tying all secrets to the org ID? Or is this just for backwards compat and we're planning to remove it?

}

// JWTBasedAuth validates Vault requests authenticated with JWTs.
type JWTBasedAuth interface {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need a separate interface, if it's the same as type Authorizer interface? The JWTBasedAuth could just implement the Authorizer interface?

}

func (a *authorizer) authorizeRequest(ctx context.Context, req jsonrpc.Request[json.RawMessage]) (*AuthResult, error) {
if req.Auth == "" {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add a comment on why we pick allowlist based auth here? IIIUC it's for backwards compat since we currently don't populate this field?

}, nil
}

// NewDisabledJWTBasedAuth returns a JWTBasedAuth implementation that always fails closed.
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: similar to the other comment on []grpc.Option, it's generally cleaner to expose a list of options []JwtAuthOption, allowing you to extend multiple flexible configs in the future, rather than exposing multiple constructors. The possible combinations can become excessive quickly.

}
if !enabled {
v.lggr.Debugw("JWTBasedAuth rejected request because it is disabled", "method", req.Method, "requestID", req.ID)
return nil, errors.New("JWTBasedAuth is disabled")
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this actually an on / off toggle, or a rate limit? The Limit call is confusing

return nil, fmt.Errorf("invalid JWT auth token: %w", err)
}

requestDigest, err := req.Digest()
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: it's a bit more efficient to call & validate this before validating the token, could prevent early JWKS calls

return "", "", ErrMissingRequestDigest
}

if detail, ok := rawDetails.(map[string]interface{}); ok {
Copy link
Contributor

Choose a reason for hiding this comment

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

authorization_details can only be of array type, so this code path is redundant

for _, d := range details {
if detail, ok := d.(map[string]interface{}); ok {
if wo, rd, err := parseAuthDetail(detail); err == nil {
return wo, rd, nil
Copy link
Contributor

Choose a reason for hiding this comment

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

The parsing is incorrect & we are returning too early. Here's a sample of how the details will look like:

    "authorization_details": [
      {
        "type": "request_digest",
        "value": "testdigestabc"
      },
      {
        "type": "workflow_owner",
        "value": "0xabcd"
      }
    ]

You will need to look at both array elements, and drive the logic based on type.

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