Skip to content
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

Relay authentication #911

Merged
merged 38 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
02e9069
Added rate limiting.
cody-littley Nov 15, 2024
dc39c21
Properly handle blob sizes.
cody-littley Nov 15, 2024
d99fd39
Incremental progress.
cody-littley Nov 15, 2024
e39eaf9
Incremental progress.
cody-littley Nov 15, 2024
876ec69
Merge branch 'master' into relay-rate-limits2
cody-littley Nov 18, 2024
9500f4f
unit tests
cody-littley Nov 18, 2024
ffe18c6
Unit tests.
cody-littley Nov 18, 2024
79d9614
Fix tests.
cody-littley Nov 18, 2024
c1d83e6
Cleanup.
cody-littley Nov 18, 2024
ed4daca
Added get chunks request hashing.
cody-littley Nov 18, 2024
3438b92
Start work on authenticator.
cody-littley Nov 18, 2024
b5dc37c
Fix test issue.
cody-littley Nov 18, 2024
b420cca
Cleanup
cody-littley Nov 18, 2024
517b78e
Merge branch 'master' into relay-rate-limits2
cody-littley Nov 19, 2024
7982aec
Convert config to flag pattern.
cody-littley Nov 19, 2024
366fdf7
Simplify rate limiter classes.
cody-littley Nov 19, 2024
f2c10e4
Made suggested changes.
cody-littley Nov 19, 2024
0f95ce4
Merge branch 'relay-rate-limits2' into relay-authentication
cody-littley Nov 19, 2024
c2bb9c3
Shorten package name.
cody-littley Nov 19, 2024
151305b
Started testing
cody-littley Nov 19, 2024
29fd940
Finished unit tests.
cody-littley Nov 19, 2024
3993af4
Nil authenticator test.
cody-littley Nov 19, 2024
d094b80
Test with authentication saving disabled.
cody-littley Nov 19, 2024
d8691e1
Tie together config.
cody-littley Nov 19, 2024
8b9512b
Add method for convenient signing.
cody-littley Nov 19, 2024
fb7ec51
Made requested changes.
cody-littley Nov 19, 2024
6325f0c
Merge branch 'master' into relay-authentication
cody-littley Nov 19, 2024
5f853e0
Revert unintentional changes.
cody-littley Nov 19, 2024
f22544c
Fix bug.
cody-littley Nov 20, 2024
7a26c27
Made requested changes.
cody-littley Nov 20, 2024
d21e0bc
Update proto documentation.
cody-littley Nov 20, 2024
98a8469
Add key caching.
cody-littley Nov 20, 2024
5948e73
lint
cody-littley Nov 20, 2024
cc62eac
Make requested changes.
cody-littley Nov 20, 2024
0372ee9
Made suggested changes.
cody-littley Nov 20, 2024
97cc318
Made requested changes.
cody-littley Nov 20, 2024
fea251a
Add preloading of keys.
cody-littley Nov 20, 2024
fe82924
Made suggested changes.
cody-littley Nov 21, 2024
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
12 changes: 7 additions & 5 deletions api/proto/relay/relay.proto
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,14 @@ message GetChunksRequest {
// 1. the operator id
// 2. for each chunk request:
// a. if the chunk request is a request by index:
// i. the blob key
// ii. the start index
// iii. the end index
// i. a one byte ASCII representation of the character "i" (aka Ox69)
// ii. the blob key
// iii. the start index
// iv. the end index
// b. if the chunk request is a request by range:
// i. the blob key
// ii. each requested chunk index, in order
// i. a one byte ASCII representation of the character "r" (aka Ox72)
// ii. the blob key
// iii. each requested chunk index, in order
bytes operator_signature = 3;
}

Expand Down
61 changes: 44 additions & 17 deletions relay/auth/authenticator.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,28 @@ import (
"fmt"
pb "github.com/Layr-Labs/eigenda/api/grpc/relay"
"github.com/Layr-Labs/eigenda/core"
lru "github.com/hashicorp/golang-lru/v2"
"sync"
"time"
)

// RequestAuthenticator authenticates requests to the relay service. This object is thread safe.
type RequestAuthenticator interface {
// AuthenticateGetChunksRequest authenticates a GetChunksRequest, returning an error if the request is invalid.
// The address is the address of the peer that sent the request. This may be used to cache auth results
// The origin is the address of the peer that sent the request. This may be used to cache auth results
// in order to save server resources.
AuthenticateGetChunksRequest(
address string,
origin string,
request *pb.GetChunksRequest,
now time.Time) error

// PreloadCache preloads the key cache with the public keys of all operators that are currently indexed.
PreloadCache() error
}

// authenticationTimeout is used to track the expiration of an auth.
type authenticationTimeout struct {
clientID string
origin string
expiration time.Time
}

Expand All @@ -47,29 +51,52 @@ type requestAuthenticator struct {
savedAuthLock sync.Mutex

// keyCache is used to cache the public keys of operators. Operator keys are assumed to never change.
keyCache sync.Map
keyCache *lru.Cache[core.OperatorID, *core.G2Point]
}

// NewRequestAuthenticator creates a new RequestAuthenticator.
func NewRequestAuthenticator(
ics core.IndexedChainState,
authenticationTimeoutDuration time.Duration) RequestAuthenticator {
keyCacheSize int,
authenticationTimeoutDuration time.Duration) (RequestAuthenticator, error) {

keyCache, err := lru.New[core.OperatorID, *core.G2Point](keyCacheSize)
if err != nil {
return nil, fmt.Errorf("failed to create key cache: %w", err)
}

return &requestAuthenticator{
ics: ics,
authenticatedClients: make(map[string]struct{}),
authenticationTimeouts: make([]*authenticationTimeout, 0),
authenticationTimeoutDuration: authenticationTimeoutDuration,
keyCache: sync.Map{},
keyCache: keyCache,
}, nil
}

func (a *requestAuthenticator) PreloadCache() error {
Copy link
Contributor

Choose a reason for hiding this comment

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

Make it private and run in the the NewRequestAuthenticator?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

change made

blockNumber, err := a.ics.GetCurrentBlockNumber()
if err != nil {
return fmt.Errorf("failed to get current block number: %w", err)
}
operators, err := a.ics.GetIndexedOperators(context.Background(), blockNumber)
if err != nil {
return fmt.Errorf("failed to get operators: %w", err)
}

for operatorID, operator := range operators {
a.keyCache.Add(operatorID, operator.PubkeyG2)
}

return nil
}

func (a *requestAuthenticator) AuthenticateGetChunksRequest(
address string,
origin string,
request *pb.GetChunksRequest,
now time.Time) error {

if a.isAuthenticationStillValid(now, address) {
if a.isAuthenticationStillValid(now, origin) {
// We've recently authenticated this client. Do not authenticate again for a while.
return nil
}
Expand All @@ -95,15 +122,14 @@ func (a *requestAuthenticator) AuthenticateGetChunksRequest(
return errors.New("signature verification failed")
}

a.saveAuthenticationResult(now, address)
a.saveAuthenticationResult(now, origin)
return nil
}

// getOperatorKey returns the public key of the operator with the given ID, caching the result.
func (a *requestAuthenticator) getOperatorKey(operatorID core.OperatorID) (*core.G2Point, error) {
untypedKey, ok := a.keyCache.Load(operatorID)
key, ok := a.keyCache.Get(operatorID)
if ok {
key := untypedKey.(*core.G2Point)
return key, nil
}

Expand All @@ -120,14 +146,14 @@ func (a *requestAuthenticator) getOperatorKey(operatorID core.OperatorID) (*core
if !ok {
return nil, errors.New("operator not found")
}
key := operator.PubkeyG2
key = operator.PubkeyG2

a.keyCache.Store(operatorID, key)
a.keyCache.Add(operatorID, key)
return key, nil
}

// saveAuthenticationResult saves the result of an auth.
func (a *requestAuthenticator) saveAuthenticationResult(now time.Time, address string) {
func (a *requestAuthenticator) saveAuthenticationResult(now time.Time, origin string) {
if a.authenticationTimeoutDuration == 0 {
// Authentication saving is disabled.
return
Expand All @@ -136,10 +162,10 @@ func (a *requestAuthenticator) saveAuthenticationResult(now time.Time, address s
a.savedAuthLock.Lock()
defer a.savedAuthLock.Unlock()

a.authenticatedClients[address] = struct{}{}
a.authenticatedClients[origin] = struct{}{}
a.authenticationTimeouts = append(a.authenticationTimeouts,
&authenticationTimeout{
clientID: address,
origin: origin,
expiration: now.Add(a.authenticationTimeoutDuration),
})
}
Expand All @@ -160,13 +186,14 @@ func (a *requestAuthenticator) isAuthenticationStillValid(now time.Time, address
}

// removeOldAuthentications removes any authentications that have expired.
ian-shim marked this conversation as resolved.
Show resolved Hide resolved
// This method is not thread safe and should be called with the savedAuthLock held.
func (a *requestAuthenticator) removeOldAuthentications(now time.Time) {
index := 0
for ; index < len(a.authenticationTimeouts); index++ {
if a.authenticationTimeouts[index].expiration.After(now) {
break
}
delete(a.authenticatedClients, a.authenticationTimeouts[index].clientID)
delete(a.authenticatedClients, a.authenticationTimeouts[index].origin)
}
if index > 0 {
a.authenticationTimeouts = a.authenticationTimeouts[index:]
Expand Down
22 changes: 13 additions & 9 deletions relay/auth/authenticator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,13 @@ func TestValidRequest(t *testing.T) {

timeout := 10 * time.Second

authenticator := NewRequestAuthenticator(ics, timeout)
authenticator, err := NewRequestAuthenticator(ics, 1024, timeout)
require.NoError(t, err)

request := randomGetChunksRequest()
request.OperatorId = operatorID[:]
SignGetChunksRequest(ics.KeyPairs[operatorID], request)
signature := request.OperatorSignature
signature := SignGetChunksRequest(ics.KeyPairs[operatorID], request)
request.OperatorSignature = signature

now := time.Now()

Expand Down Expand Up @@ -119,12 +120,13 @@ func TestAuthenticationSavingDisabled(t *testing.T) {
// This disables saving of authentication results.
timeout := time.Duration(0)

authenticator := NewRequestAuthenticator(ics, timeout)
authenticator, err := NewRequestAuthenticator(ics, 1024, timeout)
require.NoError(t, err)

request := randomGetChunksRequest()
request.OperatorId = operatorID[:]
SignGetChunksRequest(ics.KeyPairs[operatorID], request)
signature := request.OperatorSignature
signature := SignGetChunksRequest(ics.KeyPairs[operatorID], request)
request.OperatorSignature = signature

now := time.Now()

Expand Down Expand Up @@ -162,7 +164,8 @@ func TestNonExistingClient(t *testing.T) {

timeout := 10 * time.Second

authenticator := NewRequestAuthenticator(ics, timeout)
authenticator, err := NewRequestAuthenticator(ics, 1024, timeout)
require.NoError(t, err)

invalidOperatorID := tu.RandomBytes(32)

Expand Down Expand Up @@ -191,11 +194,12 @@ func TestBadSignature(t *testing.T) {

timeout := 10 * time.Second

authenticator := NewRequestAuthenticator(ics, timeout)
authenticator, err := NewRequestAuthenticator(ics, 1024, timeout)
require.NoError(t, err)

request := randomGetChunksRequest()
request.OperatorId = operatorID[:]
SignGetChunksRequest(ics.KeyPairs[operatorID], request)
request.OperatorSignature = SignGetChunksRequest(ics.KeyPairs[operatorID], request)

now := time.Now()

Expand Down
14 changes: 11 additions & 3 deletions relay/auth/request_signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import (
"golang.org/x/crypto/sha3"
)

var (
iByte = []byte{0x69}
rByte = []byte{0x72}
)

// HashGetChunksRequest hashes the given GetChunksRequest.
func HashGetChunksRequest(request *pb.GetChunksRequest) []byte {
ian-shim marked this conversation as resolved.
Show resolved Hide resolved

Expand All @@ -19,6 +24,7 @@ func HashGetChunksRequest(request *pb.GetChunksRequest) []byte {
for _, chunkRequest := range request.GetChunkRequests() {
if chunkRequest.GetByIndex() != nil {
getByIndex := chunkRequest.GetByIndex()
hasher.Write(iByte)
hasher.Write(getByIndex.BlobKey)
for _, index := range getByIndex.ChunkIndices {
indexBytes := make([]byte, 4)
Expand All @@ -27,6 +33,7 @@ func HashGetChunksRequest(request *pb.GetChunksRequest) []byte {
}
} else {
getByRange := chunkRequest.GetByRange()
hasher.Write(rByte)
hasher.Write(getByRange.BlobKey)

startBytes := make([]byte, 4)
Expand All @@ -42,9 +49,10 @@ func HashGetChunksRequest(request *pb.GetChunksRequest) []byte {
return hasher.Sum(nil)
}

// SignGetChunksRequest signs the given GetChunksRequest with the given private key.
func SignGetChunksRequest(keys *core.KeyPair, request *pb.GetChunksRequest) {
// SignGetChunksRequest signs the given GetChunksRequest with the given private key. Does not
// write the signature into the request.
func SignGetChunksRequest(keys *core.KeyPair, request *pb.GetChunksRequest) []byte {
hash := HashGetChunksRequest(request)
signature := keys.SignMessage(([32]byte)(hash))
request.OperatorSignature = signature.G1Point.Serialize()
return signature.G1Point.Serialize()
}
5 changes: 3 additions & 2 deletions relay/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ func NewConfig(ctx *cli.Context) (Config, error) {
GetChunkBytesBurstinessClient: ctx.Int(flags.GetChunkBytesBurstinessClientFlag.Name),
MaxConcurrentGetChunkOpsClient: ctx.Int(flags.MaxConcurrentGetChunkOpsClientFlag.Name),
},
AuthenticationTimeout: ctx.Duration(flags.AuthenticationTimeoutFlag.Name),
AuthenticationDisabled: ctx.Bool(flags.AuthenticationDisabledFlag.Name),
AuthenticationKeyCacheSize: ctx.Int(flags.AuthenticationKeyCacheSizeFlag.Name),
AuthenticationTimeout: ctx.Duration(flags.AuthenticationTimeoutFlag.Name),
AuthenticationDisabled: ctx.Bool(flags.AuthenticationDisabledFlag.Name),
},
EthClientConfig: geth.ReadEthClientConfig(ctx),
IndexerPullInterval: ctx.Duration(flags.IndexerPullIntervalFlag.Name),
Expand Down
12 changes: 10 additions & 2 deletions relay/cmd/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,13 @@ var (
EnvVar: common.PrefixEnvVar(envVarPrefix, "INDEXER_PULL_INTERVAL"),
Value: 5 * time.Minute,
}
AuthenticationKeyCacheSizeFlag = cli.IntFlag{
Name: common.PrefixFlag(FlagPrefix, "authentication-key-cache-size"),
Usage: "Max number of items in the authentication key cache",
Required: false,
EnvVar: common.PrefixEnvVar(envVarPrefix, "AUTHENTICATION_KEY_CACHE_SIZE"),
Value: 1024 * 1024,
}
AuthenticationTimeoutFlag = cli.DurationFlag{
Name: common.PrefixFlag(FlagPrefix, "authentication-timeout"),
Usage: "Duration to keep authentication results",
Expand All @@ -232,8 +239,6 @@ var requiredFlags = []cli.Flag{
RelayIDsFlag,
BlsOperatorStateRetrieverAddrFlag,
EigenDAServiceManagerAddrFlag,
AuthenticationTimeoutFlag,
AuthenticationDisabledFlag,
}

var optionalFlags = []cli.Flag{
Expand All @@ -260,6 +265,9 @@ var optionalFlags = []cli.Flag{
GetChunkBytesBurstinessClientFlag,
MaxConcurrentGetChunkOpsClientFlag,
IndexerPullIntervalFlag,
Copy link
Contributor

Choose a reason for hiding this comment

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

We can remove this, right?

AuthenticationKeyCacheSizeFlag,
AuthenticationTimeoutFlag,
AuthenticationDisabledFlag,
}

var Flags []cli.Flag
Expand Down
28 changes: 21 additions & 7 deletions relay/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ type Config struct {
// RateLimits contains configuration for rate limiting.
RateLimits limiter.Config

// AuthenticationKeyCacheSize is the maximum number of operator public keys that can be cached.
AuthenticationKeyCacheSize int

// AuthenticationTimeout is the duration for which an authentication is "cached". A request from the same client
// within this duration will not trigger a new authentication in order to save resources. If zero, then each request
// will be authenticated independently, regardless of timing.
Expand Down Expand Up @@ -145,7 +148,18 @@ func NewServer(

var authenticator auth.RequestAuthenticator
if !config.AuthenticationDisabled {
authenticator = auth.NewRequestAuthenticator(ics, config.AuthenticationTimeout)
authenticator, err = auth.NewRequestAuthenticator(
ics,
config.AuthenticationKeyCacheSize,
config.AuthenticationTimeout)
if err != nil {
return nil, fmt.Errorf("error creating authenticator: %w", err)
}

err = authenticator.PreloadCache()
if err != nil {
return nil, fmt.Errorf("error preloading operator key cache: %w", err)
}
}

return &Server{
Expand Down Expand Up @@ -219,13 +233,13 @@ func (s *Server) GetChunks(ctx context.Context, request *pb.GetChunksRequest) (*
"too many chunk requests provided, max is %d", s.config.MaxKeysPerGetChunksRequest)
}

client, ok := peer.FromContext(ctx)
if !ok {
return nil, errors.New("could not get peer information")
}
clientAddress := client.Addr.String()

if s.authenticator != nil {
client, ok := peer.FromContext(ctx)
if !ok {
return nil, errors.New("could not get peer information")
}
clientAddress := client.Addr.String()

err := s.authenticator.AuthenticateGetChunksRequest(clientAddress, request, time.Now())
if err != nil {
return nil, fmt.Errorf("auth failed: %w", err)
Expand Down
Loading