Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
65be103
[codex] Add Vault JWT local CRE test coverage
prashantkumar1982 Apr 17, 2026
dcf8b01
[codex] Fix Vault JWT topology and owner responses
prashantkumar1982 Apr 17, 2026
8cb595f
[codex] Fix Vault config lint
prashantkumar1982 Apr 18, 2026
0428e8c
[codex] Trim non-Vault CRE changes
prashantkumar1982 Apr 18, 2026
6438b85
increase test timeout
prashantkumar1982 Apr 20, 2026
d883c1b
Merge branch 'develop' of https://github.com/smartcontractkit/chainli…
prashantkumar1982 Apr 20, 2026
e3b6ca0
[codex] Add disabled Vault topology coverage
prashantkumar1982 Apr 20, 2026
39bb4e2
[codex] Simplify Vault handler wiring
prashantkumar1982 Apr 20, 2026
b3c2302
[codex] Align Vault JWT behavior with topology flags
prashantkumar1982 Apr 20, 2026
51fe576
[codex] Simplify Vault CRE topologies
prashantkumar1982 Apr 20, 2026
aa34f0f
Merge remote-tracking branch 'origin/develop' into codex/vault-jwt-e2e
prashantkumar1982 Apr 20, 2026
6cba4ea
[codex] Refactor Vault smoke helpers
prashantkumar1982 Apr 20, 2026
5614e15
[codex] Fix Vault smoke CI wiring
prashantkumar1982 Apr 20, 2026
951be59
[codex] Fix Vault smoke copylocks lint
prashantkumar1982 Apr 20, 2026
729ba09
[codex] Add local CRE skill docs
prashantkumar1982 Apr 21, 2026
01f4475
Merge remote-tracking branch 'origin/develop' into codex/vault-jwt-e2e
prashantkumar1982 Apr 21, 2026
a038867
[codex] Address local CRE review feedback
prashantkumar1982 Apr 21, 2026
5dc28bd
[codex] Fix vault linking config in CI
prashantkumar1982 Apr 21, 2026
b656838
[codex] Fix vault workflow linking config
prashantkumar1982 Apr 21, 2026
2556d92
[codex] Speed up vault Bucket B workflow checks
prashantkumar1982 Apr 21, 2026
ff7c8d7
[codex] Reuse vault workflows across Bucket B phases
prashantkumar1982 Apr 21, 2026
d9e3469
Merge remote-tracking branch 'origin/develop' into codex/vault-jwt-e2e
prashantkumar1982 Apr 21, 2026
11fa4c0
[codex] Remove unused vault workflow helpers
prashantkumar1982 Apr 21, 2026
e991a95
[codex] Increase go_core_tests runner memory
prashantkumar1982 Apr 21, 2026
6f8ed93
[codex] Move go_core_tests off tmpfs runner
prashantkumar1982 Apr 21, 2026
867ace2
Merge remote-tracking branch 'origin/develop' into codex/vault-jwt-e2e
prashantkumar1982 Apr 22, 2026
0cd33b8
[codex] Start shared test linking service
prashantkumar1982 Apr 22, 2026
e29f274
[codex] Fix linking service errname lint
prashantkumar1982 Apr 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ jobs:
matrix:
type:
- cmd: go_core_tests
os: runs-on=${{ github.run_id }}-unit/cpu=48/ram=96/family=c6i/spot=false/image=ubuntu24-full-x64/extras=s3-cache+tmpfs
os: runs-on=${{ github.run_id }}-unit/cpu=48/ram=96/family=c6id+c5ad/spot=false/image=ubuntu24-full-x64/extras=s3-cache
should-run: ${{ needs.filter.outputs.should-run-core-tests }}
trunk-auto-quarantine: "true"

Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/cre-system-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ jobs:

# Add list of tests with certain topologies
PER_TEST_TOPOLOGIES_JSON=${PER_TEST_TOPOLOGIES_JSON:-'{
"Test_CRE_V2_Suite_Bucket_B": [
{"topology":"workflow-gateway-capabilities","configs":"configs/workflow-gateway-capabilities-don.toml"},
{"topology":"workflow-gateway-capabilities-vault-jwt_auth-enabled","configs":"configs/workflow-gateway-capabilities-don-vault-jwt_auth-enabled.toml"}
],
"Test_CRE_V2_Aptos_Suite": [
{"topology":"workflow-gateway-aptos","configs":"configs/workflow-gateway-don-aptos.toml"}
],
Expand Down
20 changes: 4 additions & 16 deletions core/capabilities/vault/authorizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,20 @@ import (

vaultcommon "github.com/smartcontractkit/chainlink-common/pkg/capabilities/actions/vault"
jsonrpc "github.com/smartcontractkit/chainlink-common/pkg/jsonrpc2"
"github.com/smartcontractkit/chainlink-common/pkg/settings/cresettings"
"github.com/smartcontractkit/chainlink-common/pkg/settings/limits"
vault "github.com/smartcontractkit/chainlink/v2/core/capabilities/vault"
vaultmocks "github.com/smartcontractkit/chainlink/v2/core/capabilities/vault/mocks"
"github.com/smartcontractkit/chainlink/v2/core/capabilities/vault/vaulttypes"
"github.com/smartcontractkit/chainlink/v2/core/logger"
)

func testLimitsFactory() limits.Factory {
return limits.Factory{Settings: cresettings.DefaultGetter}
}

func TestAuthorizer_RejectsJWTBasedAuthWhenDisabled(t *testing.T) {
func TestAuthorizer_RejectsJWTBasedAuthWhenUnavailable(t *testing.T) {
params, err := json.Marshal(vaultcommon.CreateSecretsRequest{})
require.NoError(t, err)

allowListBasedAuth := vaultmocks.NewAuthorizer(t)
allowListBasedAuth.EXPECT().AuthorizeRequest(mock.Anything, mock.Anything).Maybe()

jwtBasedAuth, err := vault.NewJWTBasedAuth(vault.JWTBasedAuthConfig{}, testLimitsFactory(), logger.TestLogger(t), vault.WithDisabledJWTBasedAuth())
require.NoError(t, err)

a := vault.NewAuthorizer(allowListBasedAuth, jwtBasedAuth, logger.TestLogger(t))
a := vault.NewAuthorizer(allowListBasedAuth, nil, logger.TestLogger(t))

authResult, err := a.AuthorizeRequest(t.Context(), jsonrpc.Request[json.RawMessage]{
ID: "1",
Expand All @@ -42,7 +33,7 @@ func TestAuthorizer_RejectsJWTBasedAuthWhenDisabled(t *testing.T) {
Auth: "jwt-token",
})
require.Nil(t, authResult)
require.ErrorContains(t, err, "JWTBasedAuth is disabled")
require.ErrorContains(t, err, "JWTBasedAuth is nil")
allowListBasedAuth.AssertNotCalled(t, "AuthorizeRequest", mock.Anything, mock.Anything)
}

Expand Down Expand Up @@ -126,10 +117,7 @@ func TestAuthorizer_RejectsAllowListBasedAuthReplay(t *testing.T) {
req := jsonrpc.Request[json.RawMessage]{ID: "1", Method: vaulttypes.MethodSecretsCreate}
allowListBasedAuth.EXPECT().AuthorizeRequest(mock.Anything, req).Return(vault.NewAuthResult("", "0xabc", "digest-1", time.Now().Add(time.Minute).Unix()), nil).Twice()

jwtBasedAuth, err := vault.NewJWTBasedAuth(vault.JWTBasedAuthConfig{}, testLimitsFactory(), logger.TestLogger(t), vault.WithDisabledJWTBasedAuth())
require.NoError(t, err)

a := vault.NewAuthorizer(allowListBasedAuth, jwtBasedAuth, logger.TestLogger(t))
a := vault.NewAuthorizer(allowListBasedAuth, nil, logger.TestLogger(t))

authResult, err := a.AuthorizeRequest(t.Context(), req)
require.NoError(t, err)
Expand Down
57 changes: 30 additions & 27 deletions core/capabilities/vault/gw_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,6 @@ type gatewayConnector interface {
RemoveHandler(ctx context.Context, methods []string) error
}

type gatewayHandlerConfig struct {
authorizer Authorizer
}

// GatewayHandlerOption customizes GatewayHandler construction for tests and future auth extensions.
type GatewayHandlerOption func(*gatewayHandlerConfig)

// WithAuthorizer overrides the default Vault request authorizer.
func WithAuthorizer(authorizer Authorizer) GatewayHandlerOption {
return func(cfg *gatewayHandlerConfig) {
cfg.authorizer = authorizer
}
}

// GatewayHandler serves Vault requests received from the gateway on the node side.
type GatewayHandler struct {
services.Service
Expand All @@ -86,24 +72,36 @@ type GatewayHandler struct {
}

// NewGatewayHandler creates a Vault gateway connector handler with internal auth wiring.
func NewGatewayHandler(secretsService vaulttypes.SecretsService, connector gatewayConnector, workflowRegistrySyncer workflowsyncerv2.WorkflowRegistrySyncer, lggr logger.Logger, limitsFactory limits.Factory, opts ...GatewayHandlerOption) (*GatewayHandler, error) {
cfg := gatewayHandlerConfig{}
for _, opt := range opts {
opt(&cfg)
}
if cfg.authorizer == nil {
allowListBasedAuth := NewAllowListBasedAuth(lggr, workflowRegistrySyncer)
jwtBasedAuth, err := NewJWTBasedAuth(JWTBasedAuthConfig{}, limitsFactory, lggr, WithDisabledJWTBasedAuth())
// Pass a non-nil authorizer only in tests or other cases that need to override the default
// allowlist/JWT authorization chain.
func NewGatewayHandler(
secretsService vaulttypes.SecretsService,
connector gatewayConnector,
workflowRegistrySyncer workflowsyncerv2.WorkflowRegistrySyncer,
lggr logger.Logger,
limitsFactory limits.Factory,
authorizer Authorizer,
auth0 *Auth0Config,
) (*GatewayHandler, error) {
var jwtAuthService services.Service
var jwtBasedAuth Authorizer
if auth0 != nil {
var err error
jwtAuthService, err = NewJWTBasedAuth(JWTBasedAuthConfig{
IssuerURL: auth0.IssuerURL,
Audience: auth0.Audience,
}, limitsFactory, lggr)
if err != nil {
return nil, fmt.Errorf("failed to create JWTBasedAuth: %w", err)
}
cfg.authorizer = NewAuthorizer(allowListBasedAuth, jwtBasedAuth, lggr)
return newGatewayHandlerWithAuthorizer(secretsService, connector, cfg.authorizer, jwtBasedAuth, lggr)
jwtBasedAuth = jwtAuthService.(Authorizer)
}

if authorizer == nil {
allowListBasedAuth := NewAllowListBasedAuth(lggr, workflowRegistrySyncer)
authorizer = NewAuthorizer(allowListBasedAuth, jwtBasedAuth, lggr)
}
return newGatewayHandlerWithAuthorizer(secretsService, connector, cfg.authorizer, nil, lggr)
}

func newGatewayHandlerWithAuthorizer(secretsService vaulttypes.SecretsService, connector gatewayConnector, authorizer Authorizer, jwtAuthService services.Service, lggr logger.Logger) (*GatewayHandler, error) {
metrics, err := newMetrics()
if err != nil {
return nil, fmt.Errorf("failed to create metrics: %w", err)
Expand Down Expand Up @@ -227,6 +225,11 @@ func (h *GatewayHandler) authorizeAndPrefixRequest(ctx context.Context, req *jso
h.lggr.Errorw("failed to normalize gateway request for authorization", "method", req.Method, "requestID", originalRequestID, "error", err)
return nil, err
}
authReq, err := StripRequestIdentity(authReq)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve client identity fields during node-side digest checks

Calling StripRequestIdentity before node-side reauthorization changes the JSON-RPC digest whenever the original client request already included org_id/workflow_owner in params. In that case the gateway authorizes the original digest, but the node recomputes a different digest after zeroing those fields and rejects with a digest mismatch. This is a regression for clients (JWT or allowlist) that legitimately populate these request fields, because their requests now fail only at the node hop.

Useful? React with 👍 / 👎.

if err != nil {
h.lggr.Errorw("failed to strip authorized identity fields before authorization", "method", req.Method, "requestID", originalRequestID, "error", err)
return nil, err
}

h.lggr.Debugw("authorizing gateway request", "method", req.Method, "requestID", originalRequestID)
authResult, err := h.authorizer.AuthorizeRequest(ctx, authReq)
Expand Down
91 changes: 81 additions & 10 deletions core/capabilities/vault/gw_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,56 @@ func TestGatewayHandler_HandleGatewayMessage(t *testing.T) {
},
expectedError: false,
},
{
name: "success - create secrets strips forwarded identity before reauthorization",
setupMocks: func(ss *vaulttypesmocks.SecretsService, gc *connector_mocks.GatewayConnector, ra *vaultcapmocks.Authorizer) {
ra.EXPECT().AuthorizeRequest(mock.Anything, mock.MatchedBy(func(req jsonrpc.Request[json.RawMessage]) bool {
if req.Method != vaulttypes.MethodSecretsCreate || req.ID != "1" || req.Params == nil {
return false
}
parsed := &vaultcommon.CreateSecretsRequest{}
if err := json.Unmarshal(*req.Params, parsed); err != nil {
return false
}
return parsed.OrgId == "" && parsed.WorkflowOwner == ""
})).Return(authResult("org-1", "0xworkflow"), nil)
ss.EXPECT().CreateSecrets(mock.Anything, mock.MatchedBy(func(req *vaultcommon.CreateSecretsRequest) bool {
return len(req.EncryptedSecrets) == 1 &&
req.EncryptedSecrets[0].Id.Key == "test-secret" &&
req.EncryptedSecrets[0].Id.Owner == "org-1" &&
req.RequestId == "org-1"+vaulttypes.RequestIDSeparator+"1" &&
req.OrgId == "org-1" &&
req.WorkflowOwner == "0xworkflow"
})).Return(&vaulttypes.Response{ID: "test-secret"}, nil)

gc.On("SendToGateway", mock.Anything, "gateway-1", mock.MatchedBy(func(resp *jsonrpc.Response[json.RawMessage]) bool {
return resp.Error == nil
})).Return(nil)
},
request: &jsonrpc.Request[json.RawMessage]{
Method: vaulttypes.MethodSecretsCreate,
ID: "org-1" + vaulttypes.RequestIDSeparator + "1",
Params: func() *json.RawMessage {
params, _ := json.Marshal(vaultcommon.CreateSecretsRequest{
RequestId: "org-1" + vaulttypes.RequestIDSeparator + "1",
OrgId: "org-1",
WorkflowOwner: "0xworkflow",
EncryptedSecrets: []*vaultcommon.EncryptedSecret{
{
Id: &vaultcommon.SecretIdentifier{
Key: "test-secret",
Owner: "org-1",
},
EncryptedValue: "encrypted-value",
},
},
})
raw := json.RawMessage(params)
return &raw
}(),
},
expectedError: false,
},
{
name: "failure - service error",
setupMocks: func(ss *vaulttypesmocks.SecretsService, gc *connector_mocks.GatewayConnector, ra *vaultcapmocks.Authorizer) {
Expand Down Expand Up @@ -456,9 +506,6 @@ func TestGatewayHandler_HandleGatewayMessage(t *testing.T) {
secretsService := vaulttypesmocks.NewSecretsService(t)
gwConnector := connector_mocks.NewGatewayConnector(t)
allowListBasedAuth := vaultcapmocks.NewAuthorizer(t)
limitsFactory := limits.Factory{Settings: cresettings.DefaultGetter}
jwtBasedAuth, err := vaultcap.NewJWTBasedAuth(vaultcap.JWTBasedAuthConfig{}, limitsFactory, lggr, vaultcap.WithDisabledJWTBasedAuth())
require.NoError(t, err)

tt.setupMocks(secretsService, gwConnector, allowListBasedAuth)

Expand All @@ -467,8 +514,9 @@ func TestGatewayHandler_HandleGatewayMessage(t *testing.T) {
gwConnector,
nil,
lggr,
limitsFactory,
vaultcap.WithAuthorizer(vaultcap.NewAuthorizer(allowListBasedAuth, jwtBasedAuth, lggr)),
limits.Factory{Settings: cresettings.DefaultGetter},
vaultcap.NewAuthorizer(allowListBasedAuth, nil, lggr),
nil,
)
require.NoError(t, err)

Expand All @@ -490,17 +538,15 @@ func TestGatewayHandler_Lifecycle(t *testing.T) {
secretsService := vaulttypesmocks.NewSecretsService(t)
gwConnector := connector_mocks.NewGatewayConnector(t)
allowListBasedAuth := vaultcapmocks.NewAuthorizer(t)
limitsFactory := limits.Factory{Settings: cresettings.DefaultGetter}
jwtBasedAuth, err := vaultcap.NewJWTBasedAuth(vaultcap.JWTBasedAuthConfig{}, limitsFactory, lggr, vaultcap.WithDisabledJWTBasedAuth())
require.NoError(t, err)

handler, err := vaultcap.NewGatewayHandler(
secretsService,
gwConnector,
nil,
lggr,
limitsFactory,
vaultcap.WithAuthorizer(vaultcap.NewAuthorizer(allowListBasedAuth, jwtBasedAuth, lggr)),
limits.Factory{Settings: cresettings.DefaultGetter},
vaultcap.NewAuthorizer(allowListBasedAuth, nil, lggr),
nil,
)
require.NoError(t, err)

Expand All @@ -522,3 +568,28 @@ func TestGatewayHandler_Lifecycle(t *testing.T) {
assert.Equal(t, vaultcap.HandlerName, id)
})
}

func TestGatewayHandler_Lifecycle_DefaultAuthorizer_NoJWTConfig(t *testing.T) {
lggr := logger.TestLogger(t)
ctx := t.Context()

secretsService := vaulttypesmocks.NewSecretsService(t)
gwConnector := connector_mocks.NewGatewayConnector(t)

handler, err := vaultcap.NewGatewayHandler(
secretsService,
gwConnector,
nil,
lggr,
limits.Factory{Settings: cresettings.DefaultGetter},
nil,
nil,
)
require.NoError(t, err)

gwConnector.On("AddHandler", mock.Anything, vaulttypes.Methods, handler).Return(nil).Once()
require.NoError(t, handler.Start(ctx))

gwConnector.On("RemoveHandler", mock.Anything, vaulttypes.Methods).Return(nil).Once()
require.NoError(t, handler.Close())
}
Loading
Loading