Skip to content

Commit 67f9a4b

Browse files
Vault refactor to support jwt auth and allowlist auth based ownership model (#22525)
* Vault refactor * test fix * chore: empty commit Trigger CI. Co-authored-by: Cursor <cursoragent@cursor.com> * orgID plumbing * tenantID verification * bump chainlink-common * fix test * fix test * Code TenantID as a job-spec input * bump chainlink-common to merged commit hash --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent a0608d1 commit 67f9a4b

48 files changed

Lines changed: 1022 additions & 3514 deletions

Some content is hidden

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

core/capabilities/confidentialrelay/handler.go

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
jsonrpc "github.com/smartcontractkit/chainlink-common/pkg/jsonrpc2"
2323
"github.com/smartcontractkit/chainlink-common/pkg/logger"
2424
"github.com/smartcontractkit/chainlink-common/pkg/services"
25-
"github.com/smartcontractkit/chainlink-common/pkg/settings/cresettings"
2625
"github.com/smartcontractkit/chainlink-common/pkg/settings/limits"
2726
"github.com/smartcontractkit/chainlink-common/pkg/types/core"
2827
sdkpb "github.com/smartcontractkit/chainlink-protos/cre/go/sdk"
@@ -108,13 +107,9 @@ type Handler struct {
108107
// validateAttestation validates TEE attestation documents.
109108
// Defaults to the Nitro validator; overridden in tests.
110109
validateAttestation attestationValidatorFunc
111-
112-
// vaultIdentityGate controls whether WorkflowOwner and OrgId are set
113-
// on the vault GetSecretsRequest. Gated behind VaultOrgIdAsSecretOwnerEnabled.
114-
vaultIdentityGate limits.GateLimiter
115110
}
116111

117-
func NewHandler(capRegistry core.CapabilitiesRegistry, conn core.GatewayConnector, responseSigner relayResponseSigner, lggr logger.Logger, limitsFactory limits.Factory) (*Handler, error) {
112+
func NewHandler(capRegistry core.CapabilitiesRegistry, conn core.GatewayConnector, responseSigner relayResponseSigner, lggr logger.Logger, _ limits.Factory) (*Handler, error) {
118113
if responseSigner == nil {
119114
return nil, errors.New("response signer is required")
120115
}
@@ -123,19 +118,13 @@ func NewHandler(capRegistry core.CapabilitiesRegistry, conn core.GatewayConnecto
123118
return nil, fmt.Errorf("failed to create metrics: %w", err)
124119
}
125120

126-
vaultIdentityGate, err := limits.MakeGateLimiter(limitsFactory, cresettings.Default.VaultOrgIdAsSecretOwnerEnabled)
127-
if err != nil {
128-
return nil, fmt.Errorf("failed to create vault identity gate limiter: %w", err)
129-
}
130-
131121
h := &Handler{
132122
capRegistry: capRegistry,
133123
gatewayConnector: conn,
134124
responseSigner: responseSigner,
135125
lggr: logger.Named(lggr, HandlerName),
136126
metrics: m,
137127
validateAttestation: nitro.ValidateAttestation,
138-
vaultIdentityGate: vaultIdentityGate,
139128
}
140129
h.Service, h.eng = services.Config{
141130
Name: HandlerName,
@@ -243,14 +232,6 @@ func (h *Handler) handleSecretsGet(ctx context.Context, gatewayID string, req *j
243232
vaultReq := &vault.GetSecretsRequest{
244233
Requests: make([]*vault.SecretRequest, 0, len(params.Secrets)),
245234
}
246-
gateEnabled, err := h.vaultIdentityGate.Limit(ctx)
247-
if err != nil {
248-
return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, fmt.Errorf("failed to check VaultOrgIdAsSecretOwnerEnabled gate: %w", err))
249-
}
250-
vaultReq.WorkflowOwner = normalizedOwner
251-
if gateEnabled {
252-
vaultReq.OrgId = params.OrgID
253-
}
254235
for _, s := range params.Secrets {
255236
namespace := s.Namespace
256237
if namespace == "" {
@@ -284,10 +265,6 @@ func (h *Handler) handleSecretsGet(ctx context.Context, gatewayID string, req *j
284265
WorkflowDonConfigVersion: localNode.WorkflowDON.ConfigVersion,
285266
ReferenceID: req.ID,
286267
}
287-
if gateEnabled {
288-
metadata.OrgID = params.OrgID
289-
}
290-
291268
capResp, err := vaultCap.Execute(ctx, capabilities.CapabilityRequest{
292269
Payload: anypbReq,
293270
Method: vault.MethodGetSecrets,

core/capabilities/confidentialrelay/handler_test.go

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -427,12 +427,9 @@ func TestHandler_HandleGatewayMessage(t *testing.T) {
427427
},
428428
},
429429
{
430-
name: "secrets get sets WorkflowOwner and OrgId when gate enabled",
430+
name: "secrets get invokes vault execute with stable capability metadata",
431431
registry: secretsGetTestRegistry,
432432
req: secretsGetTestRequest,
433-
modifyHandler: func(_ *testing.T, h *Handler) {
434-
h.vaultIdentityGate = limits.NewGateLimiter(true)
435-
},
436433
checkResp: func(t *testing.T, resp *jsonrpc.Response[json.RawMessage]) {
437434
require.Nil(t, resp.Error)
438435
// signSecretsResponse hashes against the request params (no Attestation),
@@ -452,41 +449,11 @@ func TestHandler_HandleGatewayMessage(t *testing.T) {
452449

453450
var vaultReq vault.GetSecretsRequest
454451
require.NoError(t, exec.lastRequest.Payload.UnmarshalTo(&vaultReq))
455-
456-
// Gate enabled: owner should be EIP-55 checksummed on the vault request.
457-
assert.Equal(t, "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", vaultReq.WorkflowOwner)
458-
assert.Equal(t, "org-123", vaultReq.OrgId)
459-
460-
// Metadata.WorkflowOwner should be the original (non-normalized) value.
452+
require.Len(t, vaultReq.Requests, 1)
453+
assert.Equal(t, "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", vaultReq.Requests[0].Id.Owner)
461454
assert.Equal(t, "0xab5801a7d398351b8be11c439e05c5b3259aec9b", exec.lastRequest.Metadata.WorkflowOwner)
462455
assert.Equal(t, "wf-secrets-1", exec.lastRequest.Metadata.WorkflowID)
463456
assert.Equal(t, uint32(42), exec.lastRequest.Metadata.WorkflowDonID)
464-
// Gate enabled: OrgID should be set on metadata.
465-
assert.Equal(t, "org-123", exec.lastRequest.Metadata.OrgID)
466-
},
467-
},
468-
{
469-
name: "secrets get omits WorkflowOwner and OrgId when gate disabled",
470-
registry: secretsGetTestRegistry,
471-
req: secretsGetTestRequest,
472-
modifyHandler: func(_ *testing.T, h *Handler) {
473-
h.vaultIdentityGate = limits.NewGateLimiter(false)
474-
},
475-
checkResp: func(t *testing.T, resp *jsonrpc.Response[json.RawMessage]) {
476-
require.Nil(t, resp.Error)
477-
},
478-
checkExecutable: func(t *testing.T, reg *mockCapRegistry) {
479-
exec := reg.executables[vault.CapabilityID]
480-
require.NotNil(t, exec.lastRequest, "vault Execute should have been called")
481-
482-
var vaultReq vault.GetSecretsRequest
483-
require.NoError(t, exec.lastRequest.Payload.UnmarshalTo(&vaultReq))
484-
485-
// Gate disabled: WorkflowOwner is always set, OrgId must be empty.
486-
assert.Equal(t, "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", vaultReq.WorkflowOwner)
487-
assert.Empty(t, vaultReq.OrgId)
488-
// Gate disabled: OrgID must be empty on metadata too.
489-
assert.Empty(t, exec.lastRequest.Metadata.OrgID)
490457
},
491458
},
492459
{

core/capabilities/vault/authorizer.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,12 @@ func (a *AuthResult) WorkflowOwner() string {
4545
return a.workflowOwner
4646
}
4747

48-
// AuthorizedOwner returns the canonical owner to use for request scoping.
48+
// AuthorizedOwner returns the canonical workflow-owner address used for Vault request ID prefixing
49+
// and secret ownership (JWT-derived owner or allowlisted workflow owner).
4950
func (a *AuthResult) AuthorizedOwner() string {
5051
if a == nil {
5152
return ""
5253
}
53-
if a.orgID != "" {
54-
return a.orgID
55-
}
5654
return a.workflowOwner
5755
}
5856

core/capabilities/vault/authorizer_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func TestAuthorizer_UsesJWTWhenGateEnabled(t *testing.T) {
5959
require.NoError(t, err)
6060
require.Equal(t, "org-1", authResult.OrgID())
6161
require.Equal(t, "0xworkflow", authResult.WorkflowOwner())
62-
require.Equal(t, "org-1", authResult.AuthorizedOwner())
62+
require.Equal(t, "0xworkflow", authResult.AuthorizedOwner())
6363
}
6464

6565
func TestAuthorizer_DelegatesDigestVerificationToJWTAuth(t *testing.T) {
@@ -82,7 +82,7 @@ func TestAuthorizer_DelegatesDigestVerificationToJWTAuth(t *testing.T) {
8282
require.NoError(t, err)
8383
require.Equal(t, "org-1", authResult.OrgID())
8484
require.Empty(t, authResult.WorkflowOwner())
85-
require.Equal(t, "org-1", authResult.AuthorizedOwner())
85+
require.Empty(t, authResult.AuthorizedOwner())
8686
}
8787

8888
func TestAuthorizer_RejectsJWTReplay(t *testing.T) {
@@ -105,7 +105,7 @@ func TestAuthorizer_RejectsJWTReplay(t *testing.T) {
105105

106106
authResult, err := a.AuthorizeRequest(t.Context(), req)
107107
require.NoError(t, err)
108-
require.Equal(t, "org-1", authResult.AuthorizedOwner())
108+
require.Empty(t, authResult.AuthorizedOwner())
109109

110110
authResult, err = a.AuthorizeRequest(t.Context(), req)
111111
require.Nil(t, authResult)

core/capabilities/vault/capability.go

Lines changed: 26 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
vaultcommon "github.com/smartcontractkit/chainlink-common/pkg/capabilities/actions/vault"
1717
"github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/requests"
1818
"github.com/smartcontractkit/chainlink-common/pkg/logger"
19-
"github.com/smartcontractkit/chainlink-common/pkg/services/orgresolver"
2019
"github.com/smartcontractkit/chainlink-common/pkg/settings/cresettings"
2120
"github.com/smartcontractkit/chainlink-common/pkg/settings/limits"
2221
"github.com/smartcontractkit/chainlink-common/pkg/types/core"
@@ -32,7 +31,6 @@ type Capability struct {
3231
handler *requests.Handler[*vaulttypes.Request, *vaulttypes.Response]
3332
capabilitiesRegistry core.CapabilitiesRegistry
3433
publicKey *LazyPublicKey
35-
linker *OrgIDToWorkflowOwnerLinker
3634
lifecycle *RequestLifecycleTracker
3735
*RequestValidator
3836
}
@@ -89,10 +87,6 @@ func (s *Capability) Close() error {
8987
err = errors.Join(err, fmt.Errorf("error closing identifier namespace length limiter: %w", lerr))
9088
}
9189

92-
if lerr := s.linker.Close(); lerr != nil {
93-
err = errors.Join(err, fmt.Errorf("error closing org_id linker: %w", lerr))
94-
}
95-
9690
return err
9791
}
9892

@@ -155,11 +149,8 @@ func (s *Capability) Execute(ctx context.Context, request capabilities.Capabilit
155149
}
156150
id := fmt.Sprintf("%s::%s::%s", md.WorkflowID, phaseOrExecution, md.ReferenceID)
157151

158-
// When VaultOrgIdAsSecretOwnerEnabled is disabled, request.WorkflowOwner is
159-
// not populated, so it has to be fetched from the first request's secret owner.
160-
if r.WorkflowOwner == "" && len(r.Requests) > 0 && r.Requests[0] != nil && r.Requests[0].Id != nil {
161-
r.WorkflowOwner = r.Requests[0].Id.Owner
162-
}
152+
// Workflow DON reads populate secret identifiers explicitly; OCR paths do not rely on legacy
153+
// top-level protobuf identity fields.
163154

164155
resp, err := s.handleRequest(ctx, id, r)
165156
if err != nil {
@@ -186,17 +177,10 @@ func (s *Capability) Execute(ctx context.Context, request capabilities.Capabilit
186177

187178
func (s *Capability) CreateSecrets(ctx context.Context, request *vaultcommon.CreateSecretsRequest) (*vaulttypes.Response, error) {
188179
s.lggr.Debugw("received create secrets request", "request", request.String())
189-
resolvedIdentity, err := s.resolveRequestIdentity(ctx, request.OrgId, request.WorkflowOwner)
190-
if err != nil {
180+
if err := validateEncryptedSecretsUniformOwners(request.EncryptedSecrets); err != nil {
191181
return nil, err
192182
}
193-
request.OrgId = resolvedIdentity.OrgID
194-
request.WorkflowOwner = resolvedIdentity.WorkflowOwner
195-
if ownerErr := validateEncryptedSecretOwnersMatchResolvedIdentity(request.EncryptedSecrets, resolvedIdentity); ownerErr != nil {
196-
s.lggr.Debugw("failed identity owner checks", "requestID", request.RequestId, "err", ownerErr)
197-
return nil, ownerErr
198-
}
199-
err = s.ValidateCreateSecretsRequest(ctx, s.publicKey.Get(), request, false)
183+
err := s.ValidateCreateSecretsRequest(ctx, s.publicKey.Get(), request, false)
200184
if err != nil {
201185
s.lggr.Debugw("failed validation checks", "requestID", request.RequestId, "err", err)
202186
return nil, err
@@ -206,17 +190,10 @@ func (s *Capability) CreateSecrets(ctx context.Context, request *vaultcommon.Cre
206190

207191
func (s *Capability) UpdateSecrets(ctx context.Context, request *vaultcommon.UpdateSecretsRequest) (*vaulttypes.Response, error) {
208192
s.lggr.Debugw("received update secrets request", "request", request.String())
209-
resolvedIdentity, err := s.resolveRequestIdentity(ctx, request.OrgId, request.WorkflowOwner)
210-
if err != nil {
193+
if err := validateEncryptedSecretsUniformOwners(request.EncryptedSecrets); err != nil {
211194
return nil, err
212195
}
213-
request.OrgId = resolvedIdentity.OrgID
214-
request.WorkflowOwner = resolvedIdentity.WorkflowOwner
215-
if ownerErr := validateEncryptedSecretOwnersMatchResolvedIdentity(request.EncryptedSecrets, resolvedIdentity); ownerErr != nil {
216-
s.lggr.Debugw("failed identity owner checks", "requestID", request.RequestId, "err", ownerErr)
217-
return nil, ownerErr
218-
}
219-
err = s.ValidateUpdateSecretsRequest(ctx, s.publicKey.Get(), request, false)
196+
err := s.ValidateUpdateSecretsRequest(ctx, s.publicKey.Get(), request, false)
220197
if err != nil {
221198
s.lggr.Debugw("failed validation checks", "requestID", request.RequestId, "err", err)
222199
return nil, err
@@ -231,14 +208,8 @@ func (s *Capability) DeleteSecrets(ctx context.Context, request *vaultcommon.Del
231208
s.lggr.Debugw("failed validation checks", "requestID", request.RequestId, "request", request.String(), "err", err)
232209
return nil, err
233210
}
234-
resolvedIdentity, err := s.resolveRequestIdentity(ctx, request.OrgId, request.WorkflowOwner)
235-
if err != nil {
236-
return nil, err
237-
}
238-
request.OrgId = resolvedIdentity.OrgID
239-
request.WorkflowOwner = resolvedIdentity.WorkflowOwner
240-
if err := validateSecretIdentifierOwnersMatchResolvedIdentity(request.Ids, resolvedIdentity); err != nil {
241-
s.lggr.Debugw("failed identity owner checks", "requestID", request.RequestId, "request", request.String(), "err", err)
211+
if err := validateSecretIdentifiersUniformOwners(request.Ids); err != nil {
212+
s.lggr.Debugw("failed uniform owner checks", "requestID", request.RequestId, "request", request.String(), "err", err)
242213
return nil, err
243214
}
244215
return s.handleRequest(ctx, request.RequestId, request)
@@ -262,16 +233,6 @@ func (s *Capability) ListSecretIdentifiers(ctx context.Context, request *vaultco
262233
s.lggr.Debugw("failed validation checks", "requestID", request.RequestId, "request", request.String(), "err", err)
263234
return nil, err
264235
}
265-
resolvedIdentity, err := s.resolveRequestIdentity(ctx, request.OrgId, request.WorkflowOwner)
266-
if err != nil {
267-
return nil, err
268-
}
269-
request.OrgId = resolvedIdentity.OrgID
270-
request.WorkflowOwner = resolvedIdentity.WorkflowOwner
271-
if err := validateOwnerMatchesResolvedIdentity("owner", request.Owner, resolvedIdentity); err != nil {
272-
s.lggr.Debugw("failed identity owner checks", "requestID", request.RequestId, "request", request.String(), "err", err)
273-
return nil, err
274-
}
275236
return s.handleRequest(ctx, request.RequestId, request)
276237
}
277238

@@ -300,47 +261,40 @@ func normalizeOwner(owner string) string {
300261
return strings.ToLower(strings.TrimPrefix(owner, "0x"))
301262
}
302263

303-
func validateEncryptedSecretOwnersMatchResolvedIdentity(encryptedSecrets []*vaultcommon.EncryptedSecret, resolvedIdentity LinkedVaultRequestIdentity) error {
304-
for idx, encryptedSecret := range encryptedSecrets {
305-
if encryptedSecret == nil || encryptedSecret.Id == nil {
264+
func validateEncryptedSecretsUniformOwners(encryptedSecrets []*vaultcommon.EncryptedSecret) error {
265+
var owner string
266+
for idx, enc := range encryptedSecrets {
267+
if enc == nil || enc.Id == nil {
268+
continue
269+
}
270+
if owner == "" {
271+
owner = enc.Id.Owner
306272
continue
307273
}
308-
if err := validateOwnerMatchesResolvedIdentity(fmt.Sprintf("encrypted secret owner at index %d", idx), encryptedSecret.Id.Owner, resolvedIdentity); err != nil {
309-
return err
274+
if normalizeOwner(enc.Id.Owner) != normalizeOwner(owner) {
275+
return fmt.Errorf("encrypted secret owner at index %d %q does not match batch owner %q", idx, enc.Id.Owner, owner)
310276
}
311277
}
312-
313278
return nil
314279
}
315280

316-
func validateSecretIdentifierOwnersMatchResolvedIdentity(ids []*vaultcommon.SecretIdentifier, resolvedIdentity LinkedVaultRequestIdentity) error {
281+
func validateSecretIdentifiersUniformOwners(ids []*vaultcommon.SecretIdentifier) error {
282+
var owner string
317283
for idx, id := range ids {
318284
if id == nil {
319285
continue
320286
}
321-
if err := validateOwnerMatchesResolvedIdentity(fmt.Sprintf("secret identifier owner at index %d", idx), id.Owner, resolvedIdentity); err != nil {
322-
return err
287+
if owner == "" {
288+
owner = id.Owner
289+
continue
290+
}
291+
if normalizeOwner(id.Owner) != normalizeOwner(owner) {
292+
return fmt.Errorf("secret identifier owner at index %d %q does not match batch owner %q", idx, id.Owner, owner)
323293
}
324294
}
325-
326295
return nil
327296
}
328297

329-
func validateOwnerMatchesResolvedIdentity(field string, owner string, resolvedIdentity LinkedVaultRequestIdentity) error {
330-
if resolvedIdentity.WorkflowOwner == "" && resolvedIdentity.OrgID == "" {
331-
return nil
332-
}
333-
334-
if resolvedIdentity.WorkflowOwner != "" && normalizeOwner(owner) == normalizeOwner(resolvedIdentity.WorkflowOwner) {
335-
return nil
336-
}
337-
if resolvedIdentity.OrgID != "" && owner == resolvedIdentity.OrgID {
338-
return nil
339-
}
340-
341-
return fmt.Errorf("%s %q must match resolved workflow owner %q or org_id %q", field, owner, resolvedIdentity.WorkflowOwner, resolvedIdentity.OrgID)
342-
}
343-
344298
func (s *Capability) handleRequest(ctx context.Context, requestID string, request proto.Message) (*vaulttypes.Response, error) {
345299
s.lifecycle.RecordReceived(ctx, requestID, s.clock.Now())
346300
respCh := make(chan *vaulttypes.Response, 1)
@@ -370,27 +324,13 @@ func (s *Capability) handleRequest(ctx context.Context, requestID string, reques
370324
}
371325
}
372326

373-
// resolveRequestIdentity validates and normalizes the org/workflow-owner pair that the vault plugin consumes.
374-
func (s *Capability) resolveRequestIdentity(ctx context.Context, orgID string, workflowOwner string) (LinkedVaultRequestIdentity, error) {
375-
s.lggr.Debugw("resolving request identity", "orgID", orgID, "workflowOwner", workflowOwner)
376-
linked, err := s.linker.Link(ctx, orgID, workflowOwner)
377-
if err != nil {
378-
s.lggr.Errorw("failed to resolve request identity", "orgID", orgID, "workflowOwner", workflowOwner, "err", err)
379-
return LinkedVaultRequestIdentity{}, err
380-
}
381-
s.lggr.Debugw("resolved request identity", "orgID", linked.OrgID, "workflowOwner", linked.WorkflowOwner)
382-
383-
return linked, nil
384-
}
385-
386327
func NewCapability(
387328
lggr logger.Logger,
388329
clock clockwork.Clock,
389330
expiresAfter time.Duration,
390331
handler *requests.Handler[*vaulttypes.Request, *vaulttypes.Response],
391332
capabilitiesRegistry core.CapabilitiesRegistry,
392333
publicKey *LazyPublicKey,
393-
orgResolver orgresolver.OrgResolver,
394334
limitsFactory limits.Factory,
395335
lifecycle *RequestLifecycleTracker,
396336
) (*Capability, error) {
@@ -401,10 +341,6 @@ func NewCapability(
401341
if err != nil {
402342
return nil, fmt.Errorf("could not create request batch size limiter: %w", err)
403343
}
404-
linker, err := NewOrgIDToWorkflowOwnerLinker(orgResolver, limitsFactory)
405-
if err != nil {
406-
return nil, err
407-
}
408344
ciphertextLimiter, err := limits.MakeUpperBoundLimiter(limitsFactory, cresettings.Default.VaultCiphertextSizeLimit)
409345
if err != nil {
410346
return nil, fmt.Errorf("could not create ciphertext size limiter: %w", err)
@@ -428,7 +364,6 @@ func NewCapability(
428364
handler: handler,
429365
capabilitiesRegistry: capabilitiesRegistry,
430366
publicKey: publicKey,
431-
linker: linker,
432367
lifecycle: lifecycle,
433368
RequestValidator: NewRequestValidator(limiter, ciphertextLimiter, idKeyLengthLimiter, idOwnerLengthLimiter, idNamespaceLengthLimiter),
434369
}, nil

0 commit comments

Comments
 (0)