Skip to content

Commit 444ae28

Browse files
Add Vault JWT local CRE coverage and topology docs (#22048)
* [codex] Add Vault JWT local CRE test coverage * [codex] Fix Vault JWT topology and owner responses * [codex] Fix Vault config lint * [codex] Trim non-Vault CRE changes * increase test timeout * [codex] Add disabled Vault topology coverage * [codex] Simplify Vault handler wiring * [codex] Align Vault JWT behavior with topology flags * [codex] Simplify Vault CRE topologies * [codex] Refactor Vault smoke helpers * [codex] Fix Vault smoke CI wiring * [codex] Fix Vault smoke copylocks lint * [codex] Add local CRE skill docs * [codex] Address local CRE review feedback * [codex] Fix vault linking config in CI * [codex] Fix vault workflow linking config * [codex] Speed up vault Bucket B workflow checks * [codex] Reuse vault workflows across Bucket B phases * [codex] Remove unused vault workflow helpers * [codex] Increase go_core_tests runner memory * [codex] Move go_core_tests off tmpfs runner * [codex] Start shared test linking service * [codex] Fix linking service errname lint
1 parent 1eca13c commit 444ae28

49 files changed

Lines changed: 3219 additions & 769 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci-core.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ jobs:
206206
matrix:
207207
type:
208208
- cmd: go_core_tests
209-
os: runs-on=${{ github.run_id }}-unit/cpu=48/ram=96/family=c6i/spot=false/image=ubuntu24-full-x64/extras=s3-cache+tmpfs
209+
os: runs-on=${{ github.run_id }}-unit/cpu=48/ram=96/family=c6id+c5ad/spot=false/image=ubuntu24-full-x64/extras=s3-cache
210210
should-run: ${{ needs.filter.outputs.should-run-core-tests }}
211211
trunk-auto-quarantine: "true"
212212

.github/workflows/cre-system-tests.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ jobs:
8686
8787
# Add list of tests with certain topologies
8888
PER_TEST_TOPOLOGIES_JSON=${PER_TEST_TOPOLOGIES_JSON:-'{
89+
"Test_CRE_V2_Suite_Bucket_B": [
90+
{"topology":"workflow-gateway-capabilities","configs":"configs/workflow-gateway-capabilities-don.toml"},
91+
{"topology":"workflow-gateway-capabilities-vault-jwt_auth-enabled","configs":"configs/workflow-gateway-capabilities-don-vault-jwt_auth-enabled.toml"}
92+
],
8993
"Test_CRE_V2_Aptos_Suite": [
9094
{"topology":"workflow-gateway-aptos","configs":"configs/workflow-gateway-don-aptos.toml"}
9195
],

core/capabilities/vault/authorizer_test.go

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,20 @@ import (
1111

1212
vaultcommon "github.com/smartcontractkit/chainlink-common/pkg/capabilities/actions/vault"
1313
jsonrpc "github.com/smartcontractkit/chainlink-common/pkg/jsonrpc2"
14-
"github.com/smartcontractkit/chainlink-common/pkg/settings/cresettings"
15-
"github.com/smartcontractkit/chainlink-common/pkg/settings/limits"
1614
vault "github.com/smartcontractkit/chainlink/v2/core/capabilities/vault"
1715
vaultmocks "github.com/smartcontractkit/chainlink/v2/core/capabilities/vault/mocks"
1816
"github.com/smartcontractkit/chainlink/v2/core/capabilities/vault/vaulttypes"
1917
"github.com/smartcontractkit/chainlink/v2/core/logger"
2018
)
2119

22-
func testLimitsFactory() limits.Factory {
23-
return limits.Factory{Settings: cresettings.DefaultGetter}
24-
}
25-
26-
func TestAuthorizer_RejectsJWTBasedAuthWhenDisabled(t *testing.T) {
20+
func TestAuthorizer_RejectsJWTBasedAuthWhenUnavailable(t *testing.T) {
2721
params, err := json.Marshal(vaultcommon.CreateSecretsRequest{})
2822
require.NoError(t, err)
2923

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

33-
jwtBasedAuth, err := vault.NewJWTBasedAuth(vault.JWTBasedAuthConfig{}, testLimitsFactory(), logger.TestLogger(t), vault.WithDisabledJWTBasedAuth())
34-
require.NoError(t, err)
35-
36-
a := vault.NewAuthorizer(allowListBasedAuth, jwtBasedAuth, logger.TestLogger(t))
27+
a := vault.NewAuthorizer(allowListBasedAuth, nil, logger.TestLogger(t))
3728

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

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

129-
jwtBasedAuth, err := vault.NewJWTBasedAuth(vault.JWTBasedAuthConfig{}, testLimitsFactory(), logger.TestLogger(t), vault.WithDisabledJWTBasedAuth())
130-
require.NoError(t, err)
131-
132-
a := vault.NewAuthorizer(allowListBasedAuth, jwtBasedAuth, logger.TestLogger(t))
120+
a := vault.NewAuthorizer(allowListBasedAuth, nil, logger.TestLogger(t))
133121

134122
authResult, err := a.AuthorizeRequest(t.Context(), req)
135123
require.NoError(t, err)

core/capabilities/vault/gw_handler.go

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,6 @@ type gatewayConnector interface {
5858
RemoveHandler(ctx context.Context, methods []string) error
5959
}
6060

61-
type gatewayHandlerConfig struct {
62-
authorizer Authorizer
63-
}
64-
65-
// GatewayHandlerOption customizes GatewayHandler construction for tests and future auth extensions.
66-
type GatewayHandlerOption func(*gatewayHandlerConfig)
67-
68-
// WithAuthorizer overrides the default Vault request authorizer.
69-
func WithAuthorizer(authorizer Authorizer) GatewayHandlerOption {
70-
return func(cfg *gatewayHandlerConfig) {
71-
cfg.authorizer = authorizer
72-
}
73-
}
74-
7561
// GatewayHandler serves Vault requests received from the gateway on the node side.
7662
type GatewayHandler struct {
7763
services.Service
@@ -86,24 +72,36 @@ type GatewayHandler struct {
8672
}
8773

8874
// NewGatewayHandler creates a Vault gateway connector handler with internal auth wiring.
89-
func NewGatewayHandler(secretsService vaulttypes.SecretsService, connector gatewayConnector, workflowRegistrySyncer workflowsyncerv2.WorkflowRegistrySyncer, lggr logger.Logger, limitsFactory limits.Factory, opts ...GatewayHandlerOption) (*GatewayHandler, error) {
90-
cfg := gatewayHandlerConfig{}
91-
for _, opt := range opts {
92-
opt(&cfg)
93-
}
94-
if cfg.authorizer == nil {
95-
allowListBasedAuth := NewAllowListBasedAuth(lggr, workflowRegistrySyncer)
96-
jwtBasedAuth, err := NewJWTBasedAuth(JWTBasedAuthConfig{}, limitsFactory, lggr, WithDisabledJWTBasedAuth())
75+
// Pass a non-nil authorizer only in tests or other cases that need to override the default
76+
// allowlist/JWT authorization chain.
77+
func NewGatewayHandler(
78+
secretsService vaulttypes.SecretsService,
79+
connector gatewayConnector,
80+
workflowRegistrySyncer workflowsyncerv2.WorkflowRegistrySyncer,
81+
lggr logger.Logger,
82+
limitsFactory limits.Factory,
83+
authorizer Authorizer,
84+
auth0 *Auth0Config,
85+
) (*GatewayHandler, error) {
86+
var jwtAuthService services.Service
87+
var jwtBasedAuth Authorizer
88+
if auth0 != nil {
89+
var err error
90+
jwtAuthService, err = NewJWTBasedAuth(JWTBasedAuthConfig{
91+
IssuerURL: auth0.IssuerURL,
92+
Audience: auth0.Audience,
93+
}, limitsFactory, lggr)
9794
if err != nil {
9895
return nil, fmt.Errorf("failed to create JWTBasedAuth: %w", err)
9996
}
100-
cfg.authorizer = NewAuthorizer(allowListBasedAuth, jwtBasedAuth, lggr)
101-
return newGatewayHandlerWithAuthorizer(secretsService, connector, cfg.authorizer, jwtBasedAuth, lggr)
97+
jwtBasedAuth = jwtAuthService.(Authorizer)
98+
}
99+
100+
if authorizer == nil {
101+
allowListBasedAuth := NewAllowListBasedAuth(lggr, workflowRegistrySyncer)
102+
authorizer = NewAuthorizer(allowListBasedAuth, jwtBasedAuth, lggr)
102103
}
103-
return newGatewayHandlerWithAuthorizer(secretsService, connector, cfg.authorizer, nil, lggr)
104-
}
105104

106-
func newGatewayHandlerWithAuthorizer(secretsService vaulttypes.SecretsService, connector gatewayConnector, authorizer Authorizer, jwtAuthService services.Service, lggr logger.Logger) (*GatewayHandler, error) {
107105
metrics, err := newMetrics()
108106
if err != nil {
109107
return nil, fmt.Errorf("failed to create metrics: %w", err)
@@ -227,6 +225,11 @@ func (h *GatewayHandler) authorizeAndPrefixRequest(ctx context.Context, req *jso
227225
h.lggr.Errorw("failed to normalize gateway request for authorization", "method", req.Method, "requestID", originalRequestID, "error", err)
228226
return nil, err
229227
}
228+
authReq, err := StripRequestIdentity(authReq)
229+
if err != nil {
230+
h.lggr.Errorw("failed to strip authorized identity fields before authorization", "method", req.Method, "requestID", originalRequestID, "error", err)
231+
return nil, err
232+
}
230233

231234
h.lggr.Debugw("authorizing gateway request", "method", req.Method, "requestID", originalRequestID)
232235
authResult, err := h.authorizer.AuthorizeRequest(ctx, authReq)

core/capabilities/vault/gw_handler_test.go

Lines changed: 81 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,56 @@ func TestGatewayHandler_HandleGatewayMessage(t *testing.T) {
121121
},
122122
expectedError: false,
123123
},
124+
{
125+
name: "success - create secrets strips forwarded identity before reauthorization",
126+
setupMocks: func(ss *vaulttypesmocks.SecretsService, gc *connector_mocks.GatewayConnector, ra *vaultcapmocks.Authorizer) {
127+
ra.EXPECT().AuthorizeRequest(mock.Anything, mock.MatchedBy(func(req jsonrpc.Request[json.RawMessage]) bool {
128+
if req.Method != vaulttypes.MethodSecretsCreate || req.ID != "1" || req.Params == nil {
129+
return false
130+
}
131+
parsed := &vaultcommon.CreateSecretsRequest{}
132+
if err := json.Unmarshal(*req.Params, parsed); err != nil {
133+
return false
134+
}
135+
return parsed.OrgId == "" && parsed.WorkflowOwner == ""
136+
})).Return(authResult("org-1", "0xworkflow"), nil)
137+
ss.EXPECT().CreateSecrets(mock.Anything, mock.MatchedBy(func(req *vaultcommon.CreateSecretsRequest) bool {
138+
return len(req.EncryptedSecrets) == 1 &&
139+
req.EncryptedSecrets[0].Id.Key == "test-secret" &&
140+
req.EncryptedSecrets[0].Id.Owner == "org-1" &&
141+
req.RequestId == "org-1"+vaulttypes.RequestIDSeparator+"1" &&
142+
req.OrgId == "org-1" &&
143+
req.WorkflowOwner == "0xworkflow"
144+
})).Return(&vaulttypes.Response{ID: "test-secret"}, nil)
145+
146+
gc.On("SendToGateway", mock.Anything, "gateway-1", mock.MatchedBy(func(resp *jsonrpc.Response[json.RawMessage]) bool {
147+
return resp.Error == nil
148+
})).Return(nil)
149+
},
150+
request: &jsonrpc.Request[json.RawMessage]{
151+
Method: vaulttypes.MethodSecretsCreate,
152+
ID: "org-1" + vaulttypes.RequestIDSeparator + "1",
153+
Params: func() *json.RawMessage {
154+
params, _ := json.Marshal(vaultcommon.CreateSecretsRequest{
155+
RequestId: "org-1" + vaulttypes.RequestIDSeparator + "1",
156+
OrgId: "org-1",
157+
WorkflowOwner: "0xworkflow",
158+
EncryptedSecrets: []*vaultcommon.EncryptedSecret{
159+
{
160+
Id: &vaultcommon.SecretIdentifier{
161+
Key: "test-secret",
162+
Owner: "org-1",
163+
},
164+
EncryptedValue: "encrypted-value",
165+
},
166+
},
167+
})
168+
raw := json.RawMessage(params)
169+
return &raw
170+
}(),
171+
},
172+
expectedError: false,
173+
},
124174
{
125175
name: "failure - service error",
126176
setupMocks: func(ss *vaulttypesmocks.SecretsService, gc *connector_mocks.GatewayConnector, ra *vaultcapmocks.Authorizer) {
@@ -456,9 +506,6 @@ func TestGatewayHandler_HandleGatewayMessage(t *testing.T) {
456506
secretsService := vaulttypesmocks.NewSecretsService(t)
457507
gwConnector := connector_mocks.NewGatewayConnector(t)
458508
allowListBasedAuth := vaultcapmocks.NewAuthorizer(t)
459-
limitsFactory := limits.Factory{Settings: cresettings.DefaultGetter}
460-
jwtBasedAuth, err := vaultcap.NewJWTBasedAuth(vaultcap.JWTBasedAuthConfig{}, limitsFactory, lggr, vaultcap.WithDisabledJWTBasedAuth())
461-
require.NoError(t, err)
462509

463510
tt.setupMocks(secretsService, gwConnector, allowListBasedAuth)
464511

@@ -467,8 +514,9 @@ func TestGatewayHandler_HandleGatewayMessage(t *testing.T) {
467514
gwConnector,
468515
nil,
469516
lggr,
470-
limitsFactory,
471-
vaultcap.WithAuthorizer(vaultcap.NewAuthorizer(allowListBasedAuth, jwtBasedAuth, lggr)),
517+
limits.Factory{Settings: cresettings.DefaultGetter},
518+
vaultcap.NewAuthorizer(allowListBasedAuth, nil, lggr),
519+
nil,
472520
)
473521
require.NoError(t, err)
474522

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

497542
handler, err := vaultcap.NewGatewayHandler(
498543
secretsService,
499544
gwConnector,
500545
nil,
501546
lggr,
502-
limitsFactory,
503-
vaultcap.WithAuthorizer(vaultcap.NewAuthorizer(allowListBasedAuth, jwtBasedAuth, lggr)),
547+
limits.Factory{Settings: cresettings.DefaultGetter},
548+
vaultcap.NewAuthorizer(allowListBasedAuth, nil, lggr),
549+
nil,
504550
)
505551
require.NoError(t, err)
506552

@@ -522,3 +568,28 @@ func TestGatewayHandler_Lifecycle(t *testing.T) {
522568
assert.Equal(t, vaultcap.HandlerName, id)
523569
})
524570
}
571+
572+
func TestGatewayHandler_Lifecycle_DefaultAuthorizer_NoJWTConfig(t *testing.T) {
573+
lggr := logger.TestLogger(t)
574+
ctx := t.Context()
575+
576+
secretsService := vaulttypesmocks.NewSecretsService(t)
577+
gwConnector := connector_mocks.NewGatewayConnector(t)
578+
579+
handler, err := vaultcap.NewGatewayHandler(
580+
secretsService,
581+
gwConnector,
582+
nil,
583+
lggr,
584+
limits.Factory{Settings: cresettings.DefaultGetter},
585+
nil,
586+
nil,
587+
)
588+
require.NoError(t, err)
589+
590+
gwConnector.On("AddHandler", mock.Anything, vaulttypes.Methods, handler).Return(nil).Once()
591+
require.NoError(t, handler.Start(ctx))
592+
593+
gwConnector.On("RemoveHandler", mock.Anything, vaulttypes.Methods).Return(nil).Once()
594+
require.NoError(t, handler.Close())
595+
}

0 commit comments

Comments
 (0)