From 6ea39ee37030d324ba2fcae1f6d2b19d91d9cd8a Mon Sep 17 00:00:00 2001 From: hendoxc Date: Wed, 4 Dec 2024 10:55:49 -0700 Subject: [PATCH 01/10] INFOPLAT-1559 Adds auth header token expiry functionality - Adds function for signing publickey + timestamp. - Adjusts `BuildAuthHeaders` to take variadic args allowing backwards compatibiilty --- pkg/beholder/auth.go | 71 ++++++++++++++++++++++++++++++++++++--- pkg/beholder/auth_test.go | 55 +++++++++++++++++++++++++++++- 2 files changed, 121 insertions(+), 5 deletions(-) diff --git a/pkg/beholder/auth.go b/pkg/beholder/auth.go index ae944ab0f..db3b48799 100644 --- a/pkg/beholder/auth.go +++ b/pkg/beholder/auth.go @@ -2,7 +2,9 @@ package beholder import ( "crypto/ed25519" + "encoding/binary" "fmt" + "time" ) // authHeaderKey is the name of the header that the node authenticator will use to send the auth token @@ -11,17 +13,78 @@ var authHeaderKey = "X-Beholder-Node-Auth-Token" // authHeaderVersion is the version of the auth header format var authHeaderVersion = "1" +// BuildAuthHeadersOpt are used to modify the behavior of the BuildAuthHeaders function +type BuildAuthHeadersOpt func(*authHeaderConfig) + +type authHeaderConfig struct { + privKey ed25519.PrivateKey + timestamp int64 + version string +} + // BuildAuthHeaders creates the auth header value to be included on requests. -// The current format for the header is: +// There are two formats for the header. Version `1` is: // // :: // // where the byte value of is what's being signed -func BuildAuthHeaders(privKey ed25519.PrivateKey) map[string]string { +// and is the signature of the public key. +// The version `2` is: +// +// ::: +// +// where the byte value of and are what's being signed +func BuildAuthHeaders(privKey ed25519.PrivateKey, opts ...BuildAuthHeadersOpt) map[string]string { + // Defaults + cfg := &authHeaderConfig{ + privKey: privKey, + timestamp: time.Now().UnixMilli(), + version: authHeaderVersion, + } + // Apply config options + for _, opt := range opts { + opt(cfg) + } + // Use the new version of the auth header with timestamps + if cfg.version == "2" { + return buildAuthHeadersV2(cfg.privKey, cfg.timestamp, cfg.version) + } + // Use the old version of the auth header as default + return buildAuthHeaderV1(cfg.privKey) +} + +func buildAuthHeaderV1(privKey ed25519.PrivateKey) map[string]string { + pubKey := privKey.Public().(ed25519.PublicKey) messageBytes := pubKey signature := ed25519.Sign(privKey, messageBytes) - headerValue := fmt.Sprintf("%s:%x:%x", authHeaderVersion, messageBytes, signature) - return map[string]string{authHeaderKey: headerValue} + return map[string]string{authHeaderKey: fmt.Sprintf("%s:%x:%x", authHeaderVersion, messageBytes, signature)} +} + +func buildAuthHeadersV2(privKey ed25519.PrivateKey, timestamp int64, version string) map[string]string { + + pubKey := privKey.Public().(ed25519.PublicKey) + + timestampUnixMsBytes := make([]byte, 8) + binary.BigEndian.PutUint64(timestampUnixMsBytes, uint64(timestamp)) + + messageBytes := append(pubKey, timestampUnixMsBytes...) + signature := ed25519.Sign(privKey, messageBytes) + + return map[string]string{authHeaderKey: fmt.Sprintf("%s:%x:%d:%x", version, pubKey, timestamp, signature)} +} + +// WithAuthHeaderTimestamp is an option to set the timestamp to be used in auth headers ~ default is time.Now().UnixMilli() +func WithAuthHeaderTimestamp(timestamp int64) BuildAuthHeadersOpt { + return func(cfg *authHeaderConfig) { + cfg.timestamp = timestamp + } +} + +// WithAuthHeaderV2 is an option to use version 2 of auth headers that uses timestamps +func WithAuthHeaderV2() BuildAuthHeadersOpt { + return func(cfg *authHeaderConfig) { + cfg.version = "2" + } } diff --git a/pkg/beholder/auth_test.go b/pkg/beholder/auth_test.go index fd0e2c86c..8eb371fbb 100644 --- a/pkg/beholder/auth_test.go +++ b/pkg/beholder/auth_test.go @@ -2,13 +2,20 @@ package beholder import ( "crypto/ed25519" + "encoding/binary" "encoding/hex" + "fmt" + "strconv" + "strings" "testing" + "time" + "github.com/stretchr/testify/assert" ) -func TestBuildAuthHeaders(t *testing.T) { +func TestBuildAuthHeadersV1(t *testing.T) { + csaPrivKeyHex := "1ac84741fa51c633845fa65c06f37a700303619135630a01f2d22fb98eb1c54ecab39509e63cfaa81c70e2c907391f96803aacb00db5619a5ace5588b4b08159" csaPrivKeyBytes, err := hex.DecodeString(csaPrivKeyHex) assert.NoError(t, err) @@ -20,3 +27,49 @@ func TestBuildAuthHeaders(t *testing.T) { assert.Equal(t, expectedHeaders, BuildAuthHeaders(csaPrivKey)) } + +func TestBuildAuthHeadersV2(t *testing.T) { + + csaPrivKeyHex := "1ac84741fa51c633845fa65c06f37a700303619135630a01f2d22fb98eb1c54ecab39509e63cfaa81c70e2c907391f96803aacb00db5619a5ace5588b4b08159" + csaPrivKeyBytes, err := hex.DecodeString(csaPrivKeyHex) + assert.NoError(t, err) + csaPrivKey := ed25519.PrivateKey(csaPrivKeyBytes) + timestamp := time.Now().UnixMilli() + + authHeaderMap := BuildAuthHeaders(csaPrivKey, + WithAuthHeaderV2(), + WithAuthHeaderTimestamp(timestamp), + ) + + authHeaderValue, ok := authHeaderMap[authHeaderKey] + assert.True(t, ok, "auth header should be present") + + parts := strings.Split(authHeaderValue, ":") + assert.Len(t, parts, 4, "auth header v2 should have 4 parts") + // Check the parts + version, pubKeyHex, timestampStr, signatureHex := parts[0], parts[1], parts[2], parts[3] + assert.Equal(t, "2", version, "using WithAuthHeaderV2 should should have version 2") + assert.Equal(t, hex.EncodeToString(csaPrivKey.Public().(ed25519.PublicKey)), pubKeyHex) + assert.Equal(t, fmt.Sprintf("%d", timestamp), timestampStr) + + // Decode the public key and signature + pubKeyBytes, err := hex.DecodeString(pubKeyHex) + assert.NoError(t, err) + assert.Equal(t, csaPrivKey.Public().(ed25519.PublicKey), ed25519.PublicKey(pubKeyBytes)) + + // Parse the timestamp + timestampParsed, err := strconv.ParseInt(timestampStr, 10, 64) + assert.NoError(t, err) + assert.Equal(t, timestamp, timestampParsed) + timestampBytes := make([]byte, 8) + binary.BigEndian.PutUint64(timestampBytes, uint64(timestampParsed)) + + // Reconstruct the message bytes + messageBytes := append(pubKeyBytes, timestampBytes...) + + // Verify the signature + signatureBytes, err := hex.DecodeString(signatureHex) + assert.NoError(t, err) + assert.True(t, ed25519.Verify(pubKeyBytes, messageBytes, signatureBytes)) + +} From fcba0daa6cd9722157c4d9acae9e9addd2effc66 Mon Sep 17 00:00:00 2001 From: hendoxc Date: Wed, 4 Dec 2024 11:13:54 -0700 Subject: [PATCH 02/10] INFOPLAT-1559 Fixes some lint issues --- pkg/beholder/auth.go | 2 -- pkg/beholder/auth_test.go | 19 ++++++++----------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/pkg/beholder/auth.go b/pkg/beholder/auth.go index db3b48799..34dd725fa 100644 --- a/pkg/beholder/auth.go +++ b/pkg/beholder/auth.go @@ -54,7 +54,6 @@ func BuildAuthHeaders(privKey ed25519.PrivateKey, opts ...BuildAuthHeadersOpt) m } func buildAuthHeaderV1(privKey ed25519.PrivateKey) map[string]string { - pubKey := privKey.Public().(ed25519.PublicKey) messageBytes := pubKey signature := ed25519.Sign(privKey, messageBytes) @@ -63,7 +62,6 @@ func buildAuthHeaderV1(privKey ed25519.PrivateKey) map[string]string { } func buildAuthHeadersV2(privKey ed25519.PrivateKey, timestamp int64, version string) map[string]string { - pubKey := privKey.Public().(ed25519.PublicKey) timestampUnixMsBytes := make([]byte, 8) diff --git a/pkg/beholder/auth_test.go b/pkg/beholder/auth_test.go index 8eb371fbb..507bb26bb 100644 --- a/pkg/beholder/auth_test.go +++ b/pkg/beholder/auth_test.go @@ -4,7 +4,6 @@ import ( "crypto/ed25519" "encoding/binary" "encoding/hex" - "fmt" "strconv" "strings" "testing" @@ -12,13 +11,13 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestBuildAuthHeadersV1(t *testing.T) { - csaPrivKeyHex := "1ac84741fa51c633845fa65c06f37a700303619135630a01f2d22fb98eb1c54ecab39509e63cfaa81c70e2c907391f96803aacb00db5619a5ace5588b4b08159" csaPrivKeyBytes, err := hex.DecodeString(csaPrivKeyHex) - assert.NoError(t, err) + require.NoError(t, err) csaPrivKey := ed25519.PrivateKey(csaPrivKeyBytes) expectedHeaders := map[string]string{ @@ -29,10 +28,9 @@ func TestBuildAuthHeadersV1(t *testing.T) { } func TestBuildAuthHeadersV2(t *testing.T) { - csaPrivKeyHex := "1ac84741fa51c633845fa65c06f37a700303619135630a01f2d22fb98eb1c54ecab39509e63cfaa81c70e2c907391f96803aacb00db5619a5ace5588b4b08159" csaPrivKeyBytes, err := hex.DecodeString(csaPrivKeyHex) - assert.NoError(t, err) + require.NoError(t, err) csaPrivKey := ed25519.PrivateKey(csaPrivKeyBytes) timestamp := time.Now().UnixMilli() @@ -42,7 +40,7 @@ func TestBuildAuthHeadersV2(t *testing.T) { ) authHeaderValue, ok := authHeaderMap[authHeaderKey] - assert.True(t, ok, "auth header should be present") + require.True(t, ok, "auth header should be present") parts := strings.Split(authHeaderValue, ":") assert.Len(t, parts, 4, "auth header v2 should have 4 parts") @@ -50,16 +48,16 @@ func TestBuildAuthHeadersV2(t *testing.T) { version, pubKeyHex, timestampStr, signatureHex := parts[0], parts[1], parts[2], parts[3] assert.Equal(t, "2", version, "using WithAuthHeaderV2 should should have version 2") assert.Equal(t, hex.EncodeToString(csaPrivKey.Public().(ed25519.PublicKey)), pubKeyHex) - assert.Equal(t, fmt.Sprintf("%d", timestamp), timestampStr) + assert.Equal(t, strconv.FormatInt(timestamp, 10), timestampStr) // Decode the public key and signature pubKeyBytes, err := hex.DecodeString(pubKeyHex) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, csaPrivKey.Public().(ed25519.PublicKey), ed25519.PublicKey(pubKeyBytes)) // Parse the timestamp timestampParsed, err := strconv.ParseInt(timestampStr, 10, 64) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, timestamp, timestampParsed) timestampBytes := make([]byte, 8) binary.BigEndian.PutUint64(timestampBytes, uint64(timestampParsed)) @@ -69,7 +67,6 @@ func TestBuildAuthHeadersV2(t *testing.T) { // Verify the signature signatureBytes, err := hex.DecodeString(signatureHex) - assert.NoError(t, err) + require.NoError(t, err) assert.True(t, ed25519.Verify(pubKeyBytes, messageBytes, signatureBytes)) - } From 87d88c32188d9be19ce792843d44e6cd62dbe784 Mon Sep 17 00:00:00 2001 From: hendoxc Date: Wed, 4 Dec 2024 11:26:14 -0700 Subject: [PATCH 03/10] INFOPLAT-1559 Handles edge case of negative timestamps --- pkg/beholder/auth.go | 5 +++++ pkg/beholder/auth_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/pkg/beholder/auth.go b/pkg/beholder/auth.go index 34dd725fa..a1feac6f4 100644 --- a/pkg/beholder/auth.go +++ b/pkg/beholder/auth.go @@ -76,6 +76,11 @@ func buildAuthHeadersV2(privKey ed25519.PrivateKey, timestamp int64, version str // WithAuthHeaderTimestamp is an option to set the timestamp to be used in auth headers ~ default is time.Now().UnixMilli() func WithAuthHeaderTimestamp(timestamp int64) BuildAuthHeadersOpt { return func(cfg *authHeaderConfig) { + // negative timestamps can cause overflow when casting to unint64 + // this will send a 0 timestamp, which will be rejected by auth server as being too old + if timestamp < 0 { + timestamp = 0 + } cfg.timestamp = timestamp } } diff --git a/pkg/beholder/auth_test.go b/pkg/beholder/auth_test.go index 507bb26bb..ffa256bcb 100644 --- a/pkg/beholder/auth_test.go +++ b/pkg/beholder/auth_test.go @@ -70,3 +70,27 @@ func TestBuildAuthHeadersV2(t *testing.T) { require.NoError(t, err) assert.True(t, ed25519.Verify(pubKeyBytes, messageBytes, signatureBytes)) } + +func TestBuildAuthHeadersV2WithNegativeTimestamp(t *testing.T) { + csaPrivKeyHex := "1ac84741fa51c633845fa65c06f37a700303619135630a01f2d22fb98eb1c54ecab39509e63cfaa81c70e2c907391f96803aacb00db5619a5ace5588b4b08159" + csaPrivKeyBytes, err := hex.DecodeString(csaPrivKeyHex) + require.NoError(t, err) + csaPrivKey := ed25519.PrivateKey(csaPrivKeyBytes) + timestamp := int64(-2244466688) + + authHeaderMap := BuildAuthHeaders(csaPrivKey, + WithAuthHeaderV2(), + WithAuthHeaderTimestamp(timestamp), + ) + + authHeaderValue, ok := authHeaderMap[authHeaderKey] + require.True(t, ok, "auth header should be present") + + parts := strings.Split(authHeaderValue, ":") + assert.Len(t, parts, 4, "auth header v2 should have 4 parts") + // Check the the returned timestamp is 0 + _, _, timestampStr, _ := parts[0], parts[1], parts[2], parts[3] + timestampParsed, err := strconv.ParseInt(timestampStr, 10, 64) + require.NoError(t, err) + assert.Zero(t, timestampParsed) +} From 1c520aac574e6a36440eb33af6041834876bb853 Mon Sep 17 00:00:00 2001 From: hendoxc Date: Wed, 4 Dec 2024 14:31:04 -0700 Subject: [PATCH 04/10] INFOPLAT-1559 Switches to exposing new function - go recommended way to add extend a function - added Config as 2nd arg as opposed to optional args --- pkg/beholder/auth.go | 71 ++++++++++++++----------------------- pkg/beholder/auth_test.go | 74 +++++++++++++++++++++++++++++++-------- 2 files changed, 86 insertions(+), 59 deletions(-) diff --git a/pkg/beholder/auth.go b/pkg/beholder/auth.go index a1feac6f4..8ab168d4c 100644 --- a/pkg/beholder/auth.go +++ b/pkg/beholder/auth.go @@ -12,12 +12,10 @@ var authHeaderKey = "X-Beholder-Node-Auth-Token" // authHeaderVersion is the version of the auth header format var authHeaderVersion = "1" +var authHeaderVersionV2 = "2" -// BuildAuthHeadersOpt are used to modify the behavior of the BuildAuthHeaders function -type BuildAuthHeadersOpt func(*authHeaderConfig) - -type authHeaderConfig struct { - privKey ed25519.PrivateKey +// AuthHeaderConfig is a configuration struct for the BuildAuthHeadersV2 function +type AuthHeaderConfig struct { timestamp int64 version string } @@ -34,26 +32,7 @@ type authHeaderConfig struct { // ::: // // where the byte value of and are what's being signed -func BuildAuthHeaders(privKey ed25519.PrivateKey, opts ...BuildAuthHeadersOpt) map[string]string { - // Defaults - cfg := &authHeaderConfig{ - privKey: privKey, - timestamp: time.Now().UnixMilli(), - version: authHeaderVersion, - } - // Apply config options - for _, opt := range opts { - opt(cfg) - } - // Use the new version of the auth header with timestamps - if cfg.version == "2" { - return buildAuthHeadersV2(cfg.privKey, cfg.timestamp, cfg.version) - } - // Use the old version of the auth header as default - return buildAuthHeaderV1(cfg.privKey) -} - -func buildAuthHeaderV1(privKey ed25519.PrivateKey) map[string]string { +func BuildAuthHeaders(privKey ed25519.PrivateKey) map[string]string { pubKey := privKey.Public().(ed25519.PublicKey) messageBytes := pubKey signature := ed25519.Sign(privKey, messageBytes) @@ -61,33 +40,37 @@ func buildAuthHeaderV1(privKey ed25519.PrivateKey) map[string]string { return map[string]string{authHeaderKey: fmt.Sprintf("%s:%x:%x", authHeaderVersion, messageBytes, signature)} } -func buildAuthHeadersV2(privKey ed25519.PrivateKey, timestamp int64, version string) map[string]string { +func BuildAuthHeadersV2(privKey ed25519.PrivateKey, config *AuthHeaderConfig) map[string]string { + if config == nil { + config = defaultAuthHeaderConfig() + } + if config.version == "" { + config.version = authHeaderVersionV2 + } + // If timestamp is not set, use the current time + if config.timestamp == 0 { + config.timestamp = time.Now().UnixMilli() + } + // If timestamp is negative, set it to 0. negative values cause overflow on convertsion to uint64 + // 0 timestamps will be rejected by the server as being too old + if config.timestamp < 0 { + config.timestamp = 0 + } + pubKey := privKey.Public().(ed25519.PublicKey) timestampUnixMsBytes := make([]byte, 8) - binary.BigEndian.PutUint64(timestampUnixMsBytes, uint64(timestamp)) + binary.BigEndian.PutUint64(timestampUnixMsBytes, uint64(config.timestamp)) messageBytes := append(pubKey, timestampUnixMsBytes...) signature := ed25519.Sign(privKey, messageBytes) - return map[string]string{authHeaderKey: fmt.Sprintf("%s:%x:%d:%x", version, pubKey, timestamp, signature)} + return map[string]string{authHeaderKey: fmt.Sprintf("%s:%x:%d:%x", config.version, pubKey, config.timestamp, signature)} } -// WithAuthHeaderTimestamp is an option to set the timestamp to be used in auth headers ~ default is time.Now().UnixMilli() -func WithAuthHeaderTimestamp(timestamp int64) BuildAuthHeadersOpt { - return func(cfg *authHeaderConfig) { - // negative timestamps can cause overflow when casting to unint64 - // this will send a 0 timestamp, which will be rejected by auth server as being too old - if timestamp < 0 { - timestamp = 0 - } - cfg.timestamp = timestamp - } -} - -// WithAuthHeaderV2 is an option to use version 2 of auth headers that uses timestamps -func WithAuthHeaderV2() BuildAuthHeadersOpt { - return func(cfg *authHeaderConfig) { - cfg.version = "2" +func defaultAuthHeaderConfig() *AuthHeaderConfig { + return &AuthHeaderConfig{ + version: authHeaderVersionV2, + timestamp: time.Now().UnixMilli(), } } diff --git a/pkg/beholder/auth_test.go b/pkg/beholder/auth_test.go index ffa256bcb..a271ab2a6 100644 --- a/pkg/beholder/auth_test.go +++ b/pkg/beholder/auth_test.go @@ -28,16 +28,13 @@ func TestBuildAuthHeadersV1(t *testing.T) { } func TestBuildAuthHeadersV2(t *testing.T) { - csaPrivKeyHex := "1ac84741fa51c633845fa65c06f37a700303619135630a01f2d22fb98eb1c54ecab39509e63cfaa81c70e2c907391f96803aacb00db5619a5ace5588b4b08159" - csaPrivKeyBytes, err := hex.DecodeString(csaPrivKeyHex) + csaPrivKey, err := generateTestCSAPrivateKey() require.NoError(t, err) - csaPrivKey := ed25519.PrivateKey(csaPrivKeyBytes) timestamp := time.Now().UnixMilli() - authHeaderMap := BuildAuthHeaders(csaPrivKey, - WithAuthHeaderV2(), - WithAuthHeaderTimestamp(timestamp), - ) + authHeaderMap := BuildAuthHeadersV2(csaPrivKey, &AuthHeaderConfig{ + timestamp: timestamp, + }) authHeaderValue, ok := authHeaderMap[authHeaderKey] require.True(t, ok, "auth header should be present") @@ -71,17 +68,55 @@ func TestBuildAuthHeadersV2(t *testing.T) { assert.True(t, ed25519.Verify(pubKeyBytes, messageBytes, signatureBytes)) } +func TestBuildAuthHeadersV2WithDefaults(t *testing.T) { + csaPrivKey, err := generateTestCSAPrivateKey() + require.NoError(t, err) + + authHeaderMap := BuildAuthHeadersV2(csaPrivKey, nil) + authHeaderValue, ok := authHeaderMap[authHeaderKey] + require.True(t, ok, "auth header should be present") + + parts := strings.Split(authHeaderValue, ":") + assert.Len(t, parts, 4, "auth header v2 should have 4 parts") + // Check the parts + version, pubKeyHex, timestampStr, signatureHex := parts[0], parts[1], parts[2], parts[3] + assert.Equal(t, "2", version, "using WithAuthHeaderV2 should should have version 2") + assert.Equal(t, hex.EncodeToString(csaPrivKey.Public().(ed25519.PublicKey)), pubKeyHex) + + // Decode the public key and signature + pubKeyBytes, err := hex.DecodeString(pubKeyHex) + require.NoError(t, err) + assert.Equal(t, csaPrivKey.Public().(ed25519.PublicKey), ed25519.PublicKey(pubKeyBytes)) + + // Parse the timestamp + timestampParsed, err := strconv.ParseInt(timestampStr, 10, 64) + require.NoError(t, err) + + // Verify the timestamp is within the last 5 seconds + // This verifies that default configuration is to use the current time + now := time.Now().UnixMilli() + assert.InDelta(t, now, timestampParsed, 5000, "timestamp should be within the last 5 seconds") + + timestampBytes := make([]byte, 8) + binary.BigEndian.PutUint64(timestampBytes, uint64(timestampParsed)) + + // Reconstruct the message bytes + messageBytes := append(pubKeyBytes, timestampBytes...) + + // Verify the signature + signatureBytes, err := hex.DecodeString(signatureHex) + require.NoError(t, err) + assert.True(t, ed25519.Verify(pubKeyBytes, messageBytes, signatureBytes)) +} + func TestBuildAuthHeadersV2WithNegativeTimestamp(t *testing.T) { - csaPrivKeyHex := "1ac84741fa51c633845fa65c06f37a700303619135630a01f2d22fb98eb1c54ecab39509e63cfaa81c70e2c907391f96803aacb00db5619a5ace5588b4b08159" - csaPrivKeyBytes, err := hex.DecodeString(csaPrivKeyHex) + csaPrivKey, err := generateTestCSAPrivateKey() require.NoError(t, err) - csaPrivKey := ed25519.PrivateKey(csaPrivKeyBytes) - timestamp := int64(-2244466688) + timestamp := int64(-111) - authHeaderMap := BuildAuthHeaders(csaPrivKey, - WithAuthHeaderV2(), - WithAuthHeaderTimestamp(timestamp), - ) + authHeaderMap := BuildAuthHeadersV2(csaPrivKey, &AuthHeaderConfig{ + timestamp: timestamp, + }) authHeaderValue, ok := authHeaderMap[authHeaderKey] require.True(t, ok, "auth header should be present") @@ -94,3 +129,12 @@ func TestBuildAuthHeadersV2WithNegativeTimestamp(t *testing.T) { require.NoError(t, err) assert.Zero(t, timestampParsed) } + +func generateTestCSAPrivateKey() (ed25519.PrivateKey, error) { + csaPrivKeyHex := "1ac84741fa51c633845fa65c06f37a700303619135630a01f2d22fb98eb1c54ecab39509e63cfaa81c70e2c907391f96803aacb00db5619a5ace5588b4b08159" + csaPrivKeyBytes, err := hex.DecodeString(csaPrivKeyHex) + if err != nil { + return nil, err + } + return ed25519.PrivateKey(csaPrivKeyBytes), nil +} From bf6dc3ed3f34b00ca3821f1f8cf978f38b9360a6 Mon Sep 17 00:00:00 2001 From: hendoxc Date: Wed, 4 Dec 2024 15:12:09 -0700 Subject: [PATCH 05/10] INFOPLAT-1559 Adjusts `authHeaderVersion2` - minor refactor of test --- pkg/beholder/auth.go | 10 +++++----- pkg/beholder/auth_test.go | 6 ++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/pkg/beholder/auth.go b/pkg/beholder/auth.go index 8ab168d4c..8eaf40de4 100644 --- a/pkg/beholder/auth.go +++ b/pkg/beholder/auth.go @@ -11,8 +11,8 @@ import ( var authHeaderKey = "X-Beholder-Node-Auth-Token" // authHeaderVersion is the version of the auth header format -var authHeaderVersion = "1" -var authHeaderVersionV2 = "2" +var authHeaderVersion1 = "1" +var authHeaderVersion2 = "2" // AuthHeaderConfig is a configuration struct for the BuildAuthHeadersV2 function type AuthHeaderConfig struct { @@ -37,7 +37,7 @@ func BuildAuthHeaders(privKey ed25519.PrivateKey) map[string]string { messageBytes := pubKey signature := ed25519.Sign(privKey, messageBytes) - return map[string]string{authHeaderKey: fmt.Sprintf("%s:%x:%x", authHeaderVersion, messageBytes, signature)} + return map[string]string{authHeaderKey: fmt.Sprintf("%s:%x:%x", authHeaderVersion1, messageBytes, signature)} } func BuildAuthHeadersV2(privKey ed25519.PrivateKey, config *AuthHeaderConfig) map[string]string { @@ -45,7 +45,7 @@ func BuildAuthHeadersV2(privKey ed25519.PrivateKey, config *AuthHeaderConfig) ma config = defaultAuthHeaderConfig() } if config.version == "" { - config.version = authHeaderVersionV2 + config.version = authHeaderVersion2 } // If timestamp is not set, use the current time if config.timestamp == 0 { @@ -70,7 +70,7 @@ func BuildAuthHeadersV2(privKey ed25519.PrivateKey, config *AuthHeaderConfig) ma func defaultAuthHeaderConfig() *AuthHeaderConfig { return &AuthHeaderConfig{ - version: authHeaderVersionV2, + version: authHeaderVersion2, timestamp: time.Now().UnixMilli(), } } diff --git a/pkg/beholder/auth_test.go b/pkg/beholder/auth_test.go index a271ab2a6..9220deb02 100644 --- a/pkg/beholder/auth_test.go +++ b/pkg/beholder/auth_test.go @@ -15,10 +15,8 @@ import ( ) func TestBuildAuthHeadersV1(t *testing.T) { - csaPrivKeyHex := "1ac84741fa51c633845fa65c06f37a700303619135630a01f2d22fb98eb1c54ecab39509e63cfaa81c70e2c907391f96803aacb00db5619a5ace5588b4b08159" - csaPrivKeyBytes, err := hex.DecodeString(csaPrivKeyHex) + csaPrivKey, err := generateTestCSAPrivateKey() require.NoError(t, err) - csaPrivKey := ed25519.PrivateKey(csaPrivKeyBytes) expectedHeaders := map[string]string{ "X-Beholder-Node-Auth-Token": "1:cab39509e63cfaa81c70e2c907391f96803aacb00db5619a5ace5588b4b08159:4403178e299e9acc5b48ae97de617d3975c5d431b794cfab1d23eda01c194119b2360f5f74cfb3e4f706237ab57a0ba88ffd3f8addbc1e5197b3d3e13a1fc409", @@ -43,7 +41,7 @@ func TestBuildAuthHeadersV2(t *testing.T) { assert.Len(t, parts, 4, "auth header v2 should have 4 parts") // Check the parts version, pubKeyHex, timestampStr, signatureHex := parts[0], parts[1], parts[2], parts[3] - assert.Equal(t, "2", version, "using WithAuthHeaderV2 should should have version 2") + assert.Equal(t, authHeaderVersion2, version, "BuildAuthHeadersV2 should should have version 2") assert.Equal(t, hex.EncodeToString(csaPrivKey.Public().(ed25519.PublicKey)), pubKeyHex) assert.Equal(t, strconv.FormatInt(timestamp, 10), timestampStr) From 436c56a3bbdbab119852e5db88f01eef81c8ffe9 Mon Sep 17 00:00:00 2001 From: Hagen H <42331373+hendoxc@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:23:05 -0700 Subject: [PATCH 06/10] Update pkg/beholder/auth.go Co-authored-by: 4of9 <177086174+4of9@users.noreply.github.com> --- pkg/beholder/auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/beholder/auth.go b/pkg/beholder/auth.go index 8eaf40de4..f0c549768 100644 --- a/pkg/beholder/auth.go +++ b/pkg/beholder/auth.go @@ -51,7 +51,7 @@ func BuildAuthHeadersV2(privKey ed25519.PrivateKey, config *AuthHeaderConfig) ma if config.timestamp == 0 { config.timestamp = time.Now().UnixMilli() } - // If timestamp is negative, set it to 0. negative values cause overflow on convertsion to uint64 + // If timestamp is negative, set it to 0. negative values cause overflow on conversion to uint64 // 0 timestamps will be rejected by the server as being too old if config.timestamp < 0 { config.timestamp = 0 From 7446b1391bb0dd844170107dd68946ebf23582c0 Mon Sep 17 00:00:00 2001 From: hendoxc Date: Fri, 6 Dec 2024 12:28:03 -0700 Subject: [PATCH 07/10] INFOPLAT-1559 Tightens time range for test --- pkg/beholder/auth_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/beholder/auth_test.go b/pkg/beholder/auth_test.go index 9220deb02..d6ffb4943 100644 --- a/pkg/beholder/auth_test.go +++ b/pkg/beholder/auth_test.go @@ -70,6 +70,9 @@ func TestBuildAuthHeadersV2WithDefaults(t *testing.T) { csaPrivKey, err := generateTestCSAPrivateKey() require.NoError(t, err) + now := time.Now().UnixMilli() + time.Sleep(30 * time.Millisecond) + authHeaderMap := BuildAuthHeadersV2(csaPrivKey, nil) authHeaderValue, ok := authHeaderMap[authHeaderKey] require.True(t, ok, "auth header should be present") @@ -90,10 +93,9 @@ func TestBuildAuthHeadersV2WithDefaults(t *testing.T) { timestampParsed, err := strconv.ParseInt(timestampStr, 10, 64) require.NoError(t, err) - // Verify the timestamp is within the last 5 seconds + // Verify the timestamp is within the last 100ms // This verifies that default configuration is to use the current time - now := time.Now().UnixMilli() - assert.InDelta(t, now, timestampParsed, 5000, "timestamp should be within the last 5 seconds") + assert.InDelta(t, now, timestampParsed, 100, "timestamp should be within the last 100ms") timestampBytes := make([]byte, 8) binary.BigEndian.PutUint64(timestampBytes, uint64(timestampParsed)) From 8a9310a5a5738cb14d9ca4d683effda29a45ae5e Mon Sep 17 00:00:00 2001 From: hendoxc Date: Fri, 6 Dec 2024 12:36:08 -0700 Subject: [PATCH 08/10] INFOPLAT-1559 Removes sleep --- pkg/beholder/auth_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/beholder/auth_test.go b/pkg/beholder/auth_test.go index d6ffb4943..5dda59b83 100644 --- a/pkg/beholder/auth_test.go +++ b/pkg/beholder/auth_test.go @@ -71,7 +71,6 @@ func TestBuildAuthHeadersV2WithDefaults(t *testing.T) { require.NoError(t, err) now := time.Now().UnixMilli() - time.Sleep(30 * time.Millisecond) authHeaderMap := BuildAuthHeadersV2(csaPrivKey, nil) authHeaderValue, ok := authHeaderMap[authHeaderKey] @@ -95,7 +94,7 @@ func TestBuildAuthHeadersV2WithDefaults(t *testing.T) { // Verify the timestamp is within the last 100ms // This verifies that default configuration is to use the current time - assert.InDelta(t, now, timestampParsed, 100, "timestamp should be within the last 100ms") + assert.InDelta(t, now, timestampParsed, 50, "timestamp should be within the last 100ms") timestampBytes := make([]byte, 8) binary.BigEndian.PutUint64(timestampBytes, uint64(timestampParsed)) From cc7e772e594ad75160c76eb397eb1f5fd8b330a6 Mon Sep 17 00:00:00 2001 From: hendoxc Date: Fri, 6 Dec 2024 12:37:00 -0700 Subject: [PATCH 09/10] INFOPLAT-1559 Updates test comments --- pkg/beholder/auth_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/beholder/auth_test.go b/pkg/beholder/auth_test.go index 5dda59b83..12bb5a138 100644 --- a/pkg/beholder/auth_test.go +++ b/pkg/beholder/auth_test.go @@ -92,9 +92,9 @@ func TestBuildAuthHeadersV2WithDefaults(t *testing.T) { timestampParsed, err := strconv.ParseInt(timestampStr, 10, 64) require.NoError(t, err) - // Verify the timestamp is within the last 100ms + // Verify the timestamp is within the last 50ms // This verifies that default configuration is to use the current time - assert.InDelta(t, now, timestampParsed, 50, "timestamp should be within the last 100ms") + assert.InDelta(t, now, timestampParsed, 50, "timestamp should be within the last 50ms") timestampBytes := make([]byte, 8) binary.BigEndian.PutUint64(timestampBytes, uint64(timestampParsed)) From 9ca97ffae6189245b36b17869f785a9937bae420 Mon Sep 17 00:00:00 2001 From: Hagen H <42331373+hendoxc@users.noreply.github.com> Date: Thu, 12 Dec 2024 07:57:38 -0700 Subject: [PATCH 10/10] Update pkg/beholder/auth.go Co-authored-by: Geert G <117188496+cll-gg@users.noreply.github.com> --- pkg/beholder/auth.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/beholder/auth.go b/pkg/beholder/auth.go index f0c549768..ef3ebc6ba 100644 --- a/pkg/beholder/auth.go +++ b/pkg/beholder/auth.go @@ -40,6 +40,8 @@ func BuildAuthHeaders(privKey ed25519.PrivateKey) map[string]string { return map[string]string{authHeaderKey: fmt.Sprintf("%s:%x:%x", authHeaderVersion1, messageBytes, signature)} } +// BuildAuthHeadersV2 creates the auth header value to be included on requests. +// See documentation on BuildAuthHeaders for more info. func BuildAuthHeadersV2(privKey ed25519.PrivateKey, config *AuthHeaderConfig) map[string]string { if config == nil { config = defaultAuthHeaderConfig()