-
Notifications
You must be signed in to change notification settings - Fork 28
INFOPLAT 2962 rotating beholder headers #1567
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
12a27f3
INFOPLAT-2962 Updates `pkg/chipingress` version
hendoxc b8e815a
INFOPLAT-2962 Adds rotating header impl
hendoxc 95fbfbf
INFOPLAT-2962 Removes log from `rotatingHeaderProvider`
hendoxc 61f26e9
INFOPLAT-2962 Bumps `chipingress` to `0.0.6`
hendoxc 9ef66d9
INFOPLAT-2962 Adds rotating auth header setup
hendoxc 837ecdc
INFOPLAT-2962 Add deprecation warning for NewStaticAuthHeaderProvider
hendoxc d0597bb
INFOPLAT-2962 Clamp lowest possible TTL to 10mins
hendoxc f5380c5
INFOPLAT-2962 Makes signing headers thread safe
hendoxc d55c2b2
Merge branch 'main' into INFOPLAT-2962-rotating-beholder-headers
hendoxc 8ff69a0
Merge branch 'main' into INFOPLAT-2962-rotating-beholder-headers
hendoxc 9c89c45
Merge branch 'main' into INFOPLAT-2962-rotating-beholder-headers
hendoxc a0c6c77
Merge branch 'INFOPLAT-2962-rotating-beholder-headers' of github.com:…
hendoxc 74aeffb
INFOPLAT-2962 Use atomic value for lastupdated
hendoxc 9f60720
Merge branch 'main' into INFOPLAT-2962-rotating-beholder-headers
hendoxc f0b7a60
Removes `defer client.Close`
hendoxc a98382d
Fixes header value using `UnixNano`
hendoxc ea20225
Merge branch 'INFOPLAT-2962-rotating-beholder-headers' of github.com:…
hendoxc ab1dcbd
Uses `atomic` for header map
hendoxc 9f62b5d
Ensure thread safety
hendoxc 0c83267
Merge branch 'main' into INFOPLAT-2962-rotating-beholder-headers
hendoxc 0326c96
Adds benchmarking
hendoxc 388767a
Merge branch 'main' into INFOPLAT-2962-rotating-beholder-headers
hendoxc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,30 +1,157 @@ | ||
| package beholder | ||
|
|
||
| import ( | ||
| "context" | ||
| "crypto" | ||
| "crypto/ed25519" | ||
| "crypto/rand" | ||
| "encoding/binary" | ||
| "fmt" | ||
| "maps" | ||
| "sync" | ||
| "sync/atomic" | ||
| "time" | ||
|
|
||
| "github.com/smartcontractkit/chainlink-common/pkg/chipingress" | ||
| "google.golang.org/grpc" | ||
| "google.golang.org/grpc/credentials" | ||
| ) | ||
|
|
||
| // authHeaderKey is the name of the header that the node authenticator will use to send the auth token | ||
| var authHeaderKey = "X-Beholder-Node-Auth-Token" | ||
|
|
||
| // authHeaderVersion is the version of the auth header format | ||
| var authHeaderVersion = "1" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same, |
||
| var authHeaderV2 = "2" | ||
|
|
||
| type staticAuthHeaderProvider struct { | ||
| headers map[string]string | ||
| type HeaderProvider interface { | ||
| Headers(ctx context.Context) (map[string]string, error) | ||
| } | ||
|
|
||
| func (p *staticAuthHeaderProvider) GetHeaders() map[string]string { | ||
| return p.headers | ||
| type PerRPCCredentialsProvider interface { | ||
| Credentials() credentials.PerRPCCredentials | ||
| } | ||
|
|
||
| type Auth interface { | ||
| PerRPCCredentialsProvider | ||
| HeaderProvider | ||
| } | ||
|
|
||
| type Signer interface { | ||
| Sign(ctx context.Context, keyID []byte, data []byte) ([]byte, error) | ||
| } | ||
|
|
||
| type staticAuth struct { | ||
| headers map[string]string | ||
| requireTransportSecurity bool | ||
| } | ||
|
|
||
| func (p *staticAuth) Headers(_ context.Context) (map[string]string, error) { | ||
| return p.headers, nil | ||
| } | ||
|
|
||
| func (p *staticAuth) Credentials() credentials.PerRPCCredentials { | ||
| return p | ||
| } | ||
|
|
||
| func (p *staticAuth) GetRequestMetadata(ctx context.Context, _ ...string) (map[string]string, error) { | ||
| return p.Headers(ctx) | ||
| } | ||
|
|
||
| func (p *staticAuth) RequireTransportSecurity() bool { | ||
| return p.requireTransportSecurity | ||
| } | ||
|
|
||
| func NewStaticAuth(headers map[string]string, requireTransportSecurity bool) Auth { | ||
| return &staticAuth{headers, requireTransportSecurity} | ||
| } | ||
|
|
||
| // Deprecated: use NewStaticAuth instead | ||
| func NewStaticAuthHeaderProvider(headers map[string]string) chipingress.HeaderProvider { | ||
| return &staticAuthHeaderProvider{headers: headers} | ||
| return &staticAuth{headers: headers} | ||
| } | ||
|
|
||
| type rotatingAuth struct { | ||
| csaPubKey ed25519.PublicKey | ||
| signer Signer | ||
| signerTimeout time.Duration | ||
| headers atomic.Value // stores map[string]string | ||
| ttl time.Duration | ||
| lastUpdatedNanos atomic.Int64 | ||
| requireTransportSecurity bool | ||
| mu sync.Mutex | ||
| } | ||
|
|
||
| func NewRotatingAuth(csaPubKey ed25519.PublicKey, signer Signer, ttl time.Duration, requireTransportSecurity bool) Auth { | ||
| r := &rotatingAuth{ | ||
| csaPubKey: csaPubKey, | ||
| signer: signer, | ||
| signerTimeout: time.Second * 5, | ||
|
hendoxc marked this conversation as resolved.
|
||
| ttl: ttl, | ||
| lastUpdatedNanos: atomic.Int64{}, | ||
| requireTransportSecurity: requireTransportSecurity, | ||
| } | ||
| r.headers.Store(make(map[string]string)) | ||
| return r | ||
| } | ||
|
|
||
| func (r *rotatingAuth) Headers(ctx context.Context) (map[string]string, error) { | ||
|
jmank88 marked this conversation as resolved.
|
||
|
|
||
| // Return a copy of the headers to avoid concurrent read/write to the map by callers | ||
| returnHeader := make(map[string]string) | ||
| lastUpdated := time.Unix(0, r.lastUpdatedNanos.Load()) | ||
|
|
||
| if time.Since(lastUpdated) > r.ttl { | ||
|
|
||
| r.mu.Lock() | ||
| defer r.mu.Unlock() | ||
|
|
||
| // Multiple concurrent calls (after the first) will block waiting for the lock. | ||
| // First will get the lock and update headers + lastUpdated, double check since potentially another goroutine has already | ||
| // updated the headers and lastUpdated while waiting for the lock. | ||
| lastUpdated = time.Unix(0, r.lastUpdatedNanos.Load()) | ||
| if time.Since(lastUpdated) < r.ttl { | ||
| maps.Copy(returnHeader, r.headers.Load().(map[string]string)) | ||
| return returnHeader, nil | ||
| } | ||
|
|
||
| // Append the bytes of the public key with bytes of the timestamp to create the message to sign | ||
| ts := time.Now() | ||
| tsBytes := make([]byte, 8) | ||
| binary.BigEndian.PutUint64(tsBytes, uint64(ts.UnixNano())) | ||
| msgBytes := append(r.csaPubKey, tsBytes...) | ||
|
|
||
| ctxWithTimeout, cancel := context.WithTimeout(ctx, r.signerTimeout) | ||
| defer cancel() | ||
|
|
||
| // Sign(public key bytes + timestamp bytes) | ||
| signature, err := r.signer.Sign(ctxWithTimeout, r.csaPubKey, msgBytes) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("beholder: failed to sign auth header: %w", err) | ||
| } | ||
|
|
||
| newHeaders := make(map[string]string) | ||
| newHeaders[authHeaderKey] = fmt.Sprintf("%s:%x:%d:%x", authHeaderV2, r.csaPubKey, ts.UnixNano(), signature) | ||
|
|
||
| r.headers.Store(newHeaders) | ||
| r.lastUpdatedNanos.Store(ts.UnixNano()) | ||
| } | ||
|
|
||
| maps.Copy(returnHeader, r.headers.Load().(map[string]string)) | ||
|
|
||
| return returnHeader, nil | ||
| } | ||
|
|
||
| func (a *rotatingAuth) Credentials() credentials.PerRPCCredentials { | ||
| return a | ||
| } | ||
|
|
||
| func (a *rotatingAuth) GetRequestMetadata(ctx context.Context, _ ...string) (map[string]string, error) { | ||
| return a.Headers(ctx) | ||
| } | ||
|
|
||
| func (a *rotatingAuth) RequireTransportSecurity() bool { | ||
| return a.requireTransportSecurity | ||
| } | ||
|
|
||
| // BuildAuthHeaders creates the auth header value to be included on requests. | ||
|
|
@@ -58,3 +185,7 @@ func NewAuthHeaders(ed25519Signer crypto.Signer) (map[string]string, error) { | |
|
|
||
| return map[string]string{authHeaderKey: headerValue}, nil | ||
| } | ||
|
|
||
| func authDialOpt(auth PerRPCCredentialsProvider) grpc.DialOption { | ||
| return grpc.WithPerRPCCredentials(auth.Credentials()) | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would go for a
constinstead of avar