Skip to content

Commit 8748ffd

Browse files
vault: retry allowlist auth on sync lag
1 parent 260cce3 commit 8748ffd

2 files changed

Lines changed: 102 additions & 7 deletions

File tree

core/capabilities/vault/allow_list_based_auth.go

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import (
1414
workflowsyncerv2 "github.com/smartcontractkit/chainlink/v2/core/services/workflows/syncer/v2"
1515
)
1616

17+
const (
18+
allowListBasedAuthRetryCount = 3
19+
allowListBasedAuthRetryInterval = 3 * time.Second
20+
)
21+
1722
// AllowListBasedAuth validates Vault requests against the workflow registry's onchain allowlist.
1823
type AllowListBasedAuth interface {
1924
AuthorizeRequest(ctx context.Context, req jsonrpc.Request[json.RawMessage]) (*AuthResult, error)
@@ -22,6 +27,8 @@ type AllowListBasedAuth interface {
2227
type allowListBasedAuth struct {
2328
workflowRegistrySyncer workflowsyncerv2.WorkflowRegistrySyncer
2429
lggr logger.Logger
30+
retryCount int
31+
retryInterval time.Duration
2532
}
2633

2734
// AuthorizeRequest authorizes a request using AllowListBasedAuth.
@@ -43,14 +50,10 @@ func (r *allowListBasedAuth) AuthorizeRequest(ctx context.Context, req jsonrpc.R
4350
r.lggr.Errorw("AllowListBasedAuth workflowRegistrySyncer is nil", "method", req.Method, "requestID", req.ID)
4451
return nil, errors.New("internal error: workflowRegistrySyncer is nil")
4552
}
46-
allowedRequests := r.workflowRegistrySyncer.GetAllowlistedRequests(ctx)
47-
allowedRequestsStrs := make([]string, 0, len(allowedRequests))
48-
for _, rr := range allowedRequests {
49-
allowedReqStr := fmt.Sprintf("AuthorizedOwner: %s, RequestDigest: %s, ExpiryTimestamp: %d", rr.Owner.Hex(), hex.EncodeToString(rr.RequestDigest[:]), rr.ExpiryTimestamp)
50-
allowedRequestsStrs = append(allowedRequestsStrs, allowedReqStr)
53+
allowlistedRequest, allowedRequestsStrs, err := r.findAllowlistedItemWithRetry(ctx, req, requestDigest, requestDigestBytes32)
54+
if err != nil {
55+
return nil, err
5156
}
52-
r.lggr.Debugw("AllowListBasedAuth loaded allowlisted requests", "method", req.Method, "requestID", req.ID, "allowedRequests", allowedRequestsStrs)
53-
allowlistedRequest := r.fetchAllowlistedItem(allowedRequests, requestDigestBytes32)
5457
if allowlistedRequest == nil {
5558
r.lggr.Debugw("AllowListBasedAuth request digest not allowlisted",
5659
"method", req.Method,
@@ -75,6 +78,40 @@ func (r *allowListBasedAuth) AuthorizeRequest(ctx context.Context, req jsonrpc.R
7578
}, nil
7679
}
7780

81+
func (r *allowListBasedAuth) findAllowlistedItemWithRetry(ctx context.Context, req jsonrpc.Request[json.RawMessage], requestDigest string, requestDigestBytes32 [32]byte) (*workflow_registry_wrapper_v2.WorkflowRegistryOwnerAllowlistedRequest, []string, error) {
82+
for attempt := 0; attempt <= r.retryCount; attempt++ {
83+
allowedRequests := r.workflowRegistrySyncer.GetAllowlistedRequests(ctx)
84+
allowedRequestsStrs := make([]string, 0, len(allowedRequests))
85+
for _, rr := range allowedRequests {
86+
allowedReqStr := fmt.Sprintf("AuthorizedOwner: %s, RequestDigest: %s, ExpiryTimestamp: %d", rr.Owner.Hex(), hex.EncodeToString(rr.RequestDigest[:]), rr.ExpiryTimestamp)
87+
allowedRequestsStrs = append(allowedRequestsStrs, allowedReqStr)
88+
}
89+
r.lggr.Debugw("AllowListBasedAuth loaded allowlisted requests", "method", req.Method, "requestID", req.ID, "attempt", attempt+1, "allowedRequests", allowedRequestsStrs)
90+
91+
allowlistedRequest := r.fetchAllowlistedItem(allowedRequests, requestDigestBytes32)
92+
if allowlistedRequest != nil {
93+
return allowlistedRequest, allowedRequestsStrs, nil
94+
}
95+
if attempt == r.retryCount {
96+
return nil, allowedRequestsStrs, nil
97+
}
98+
99+
r.lggr.Debugw("AllowListBasedAuth request digest not yet allowlisted, retrying",
100+
"method", req.Method,
101+
"requestID", req.ID,
102+
"digestHexStr", requestDigest,
103+
"attempt", attempt+1,
104+
"maxAttempts", r.retryCount+1,
105+
"retryInterval", r.retryInterval)
106+
if err := sleepWithContext(ctx, r.retryInterval); err != nil {
107+
r.lggr.Debugw("AllowListBasedAuth retry canceled", "method", req.Method, "requestID", req.ID, "error", err)
108+
return nil, nil, err
109+
}
110+
}
111+
112+
return nil, nil, nil
113+
}
114+
78115
func (r *allowListBasedAuth) fetchAllowlistedItem(allowListedRequests []workflow_registry_wrapper_v2.WorkflowRegistryOwnerAllowlistedRequest, digest [32]byte) *workflow_registry_wrapper_v2.WorkflowRegistryOwnerAllowlistedRequest {
79116
for _, item := range allowListedRequests {
80117
if item.RequestDigest == digest {
@@ -89,5 +126,19 @@ func NewAllowListBasedAuth(lggr logger.Logger, workflowRegistrySyncer workflowsy
89126
return &allowListBasedAuth{
90127
workflowRegistrySyncer: workflowRegistrySyncer,
91128
lggr: logger.Named(lggr, "VaultAllowListBasedAuth"),
129+
retryCount: allowListBasedAuthRetryCount,
130+
retryInterval: allowListBasedAuthRetryInterval,
131+
}
132+
}
133+
134+
func sleepWithContext(ctx context.Context, d time.Duration) error {
135+
timer := time.NewTimer(d)
136+
defer timer.Stop()
137+
138+
select {
139+
case <-ctx.Done():
140+
return ctx.Err()
141+
case <-timer.C:
142+
return nil
92143
}
93144
}

core/capabilities/vault/allow_list_based_auth_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ func testAuthForRequests(t *testing.T, allowlistedRequest, notAllowlistedRequest
159159

160160
mockSyncer := syncerv2mocks.NewWorkflowRegistrySyncer(t)
161161
auth := NewAllowListBasedAuth(lggr, mockSyncer)
162+
auth.retryCount = 0
163+
auth.retryInterval = time.Millisecond
162164

163165
// Happy path
164166
digest, err := allowlistedRequest.Digest()
@@ -203,3 +205,45 @@ func testAuthForRequests(t *testing.T, allowlistedRequest, notAllowlistedRequest
203205
require.Nil(t, authResult)
204206
require.ErrorContains(t, err, "not allowlisted")
205207
}
208+
209+
func TestAllowListBasedAuth_RetriesUntilRequestIsAllowlisted(t *testing.T) {
210+
lggr := logger.TestLogger(t)
211+
owner := common.Address{1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
212+
213+
params, err := json.Marshal(vaultcommon.ListSecretIdentifiersRequest{
214+
Namespace: "b",
215+
})
216+
require.NoError(t, err)
217+
218+
req := jsonrpc.Request[json.RawMessage]{
219+
ID: "123",
220+
Method: vaulttypes.MethodSecretsList,
221+
Params: (*json.RawMessage)(&params),
222+
}
223+
224+
digest, err := req.Digest()
225+
require.NoError(t, err)
226+
digestBytes, err := hex.DecodeString(digest)
227+
require.NoError(t, err)
228+
expiry := time.Now().UTC().Unix() + 100
229+
allowlisted := []workflow_registry_wrapper_v2.WorkflowRegistryOwnerAllowlistedRequest{
230+
{
231+
RequestDigest: [32]byte(digestBytes),
232+
Owner: owner,
233+
ExpiryTimestamp: uint32(expiry), //nolint:gosec // it is a safe conversion
234+
},
235+
}
236+
237+
mockSyncer := syncerv2mocks.NewWorkflowRegistrySyncer(t)
238+
auth := NewAllowListBasedAuth(lggr, mockSyncer)
239+
auth.retryCount = 1
240+
auth.retryInterval = time.Millisecond
241+
242+
mockSyncer.On("GetAllowlistedRequests", mock.Anything).Return([]workflow_registry_wrapper_v2.WorkflowRegistryOwnerAllowlistedRequest{}).Once()
243+
mockSyncer.On("GetAllowlistedRequests", mock.Anything).Return(allowlisted).Once()
244+
245+
authResult, err := auth.AuthorizeRequest(t.Context(), req)
246+
require.NoError(t, err)
247+
require.Equal(t, owner.Hex(), authResult.AuthorizedOwner())
248+
require.Equal(t, expiry, authResult.ExpiresAt)
249+
}

0 commit comments

Comments
 (0)