Skip to content

Commit

Permalink
feat: add api key for authz (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
shrimalmadhur authored Jan 17, 2025
1 parent 6d0c13b commit 4cdf1cf
Show file tree
Hide file tree
Showing 20 changed files with 745 additions and 128 deletions.
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ build:
.PHONY: start
start:
make build
./bin/$(APP_NAME) --log-level=debug
./bin/$(APP_NAME) --log-level=debug --enable-admin

.PHONY: fmt
fmt: ## formats all go files
Expand Down Expand Up @@ -41,3 +41,8 @@ docker: ## runs docker build
migrate: ## runs database migrations
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
migrate -path internal/database/migrations/ -database "postgres://user:password@localhost:5432/cerberus?sslmode=disable" --verbose up

.PHONY: create-migration
create-migration: ## creates a new database migration
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
migrate create -dir internal/database/migrations/ -ext sql $(name)
32 changes: 26 additions & 6 deletions cmd/cerberus/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,34 @@ var (
EnvVars: []string{"KEYSTORE_DIR"},
}

grpcPortFlag = &cli.StringFlag{
grpcPortFlag = &cli.IntFlag{
Name: "grpc-port",
Usage: "Port for the gRPC server",
Value: "50051",
Value: 50051,
EnvVars: []string{"GRPC_PORT"},
}

metricsPortFlag = &cli.StringFlag{
adminPortFlag = &cli.IntFlag{
Name: "admin-port",
Usage: "Port for the admin server",
Value: 50052,
EnvVars: []string{"ADMIN_PORT"},
}

metricsPortFlag = &cli.IntFlag{
Name: "metrics-port",
Usage: "Port for the metrics server",
Value: "9091",
Value: 9091,
EnvVars: []string{"METRICS_PORT"},
}

enableAdminFlag = &cli.BoolFlag{
Name: "enable-admin",
Usage: "Enable the admin server",
Value: false,
EnvVars: []string{"ENABLE_ADMIN"},
}

logLevelFlag = &cli.StringFlag{
Name: "log-level",
Usage: "Log level - supported levels: debug, info, warn, error",
Expand Down Expand Up @@ -142,6 +156,7 @@ func main() {
logFormatFlag,
logLevelFlag,
metricsPortFlag,
enableAdminFlag,
tlsCaCertFlag,
tlsServerKeyFlag,
storageTypeFlag,
Expand All @@ -152,6 +167,7 @@ func main() {
awsSecretAccessKeyFlag,
gcpProjectIDFlag,
postgresDatabaseURLFlag,
adminPortFlag,
}
sort.Sort(cli.FlagsByName(app.Flags))

Expand All @@ -168,8 +184,9 @@ func main() {

func start(c *cli.Context) error {
keystoreDir := c.String(keystoreDirFlag.Name)
grpcPort := c.String(grpcPortFlag.Name)
metricsPort := c.String(metricsPortFlag.Name)
grpcPort := c.Int(grpcPortFlag.Name)
adminPort := c.Int(adminPortFlag.Name)
metricsPort := c.Int(metricsPortFlag.Name)
logLevel := c.String(logLevelFlag.Name)
logFormat := c.String(logFormatFlag.Name)
tlsCaCert := c.String(tlsCaCertFlag.Name)
Expand All @@ -182,9 +199,11 @@ func start(c *cli.Context) error {
awsSecretAccessKey := c.String(awsSecretAccessKeyFlag.Name)
gcpProjectID := c.String(gcpProjectIDFlag.Name)
postgresDatabaseURL := c.String(postgresDatabaseURLFlag.Name)
enableAdmin := c.Bool(enableAdminFlag.Name)
cfg := &configuration.Configuration{
KeystoreDir: keystoreDir,
GrpcPort: grpcPort,
AdminPort: adminPort,
MetricsPort: metricsPort,
TLSCACert: tlsCaCert,
TLSServerKey: tlsServerKey,
Expand All @@ -196,6 +215,7 @@ func start(c *cli.Context) error {
AWSSecretAccessKey: awsSecretAccessKey,
GCPProjectID: gcpProjectID,
PostgresDatabaseURL: postgresDatabaseURL,
EnableAdmin: enableAdmin,
}

if err := cfg.Validate(); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ toolchain go1.22.3
require (
cloud.google.com/go/secretmanager v1.14.2
github.com/Layr-Labs/bn254-keystore-go v0.0.0-20250107020618-26bd412fae87
github.com/Layr-Labs/cerberus-api v0.0.2-0.20250108174619-d5e1eb03fbd5
github.com/Layr-Labs/cerberus-api v0.0.2-0.20250117193600-e69c5e8b08fd
github.com/aws/aws-sdk-go-v2 v1.32.5
github.com/aws/aws-sdk-go-v2/config v1.28.5
github.com/aws/aws-sdk-go-v2/credentials v1.17.46
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.6
github.com/consensys/gnark-crypto v0.12.1
github.com/golang-migrate/migrate/v4 v4.18.1
github.com/google/uuid v1.6.0
github.com/prometheus/client_golang v1.20.3
github.com/stretchr/testify v1.10.0
github.com/testcontainers/testcontainers-go v0.34.0
Expand All @@ -37,7 +38,6 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg6
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Layr-Labs/bn254-keystore-go v0.0.0-20250107020618-26bd412fae87 h1:EkaBNT0o8RTgtFeYSKaoNHNbnCVxrcsAyRpUeN29hiQ=
github.com/Layr-Labs/bn254-keystore-go v0.0.0-20250107020618-26bd412fae87/go.mod h1:7J8hptSX8cFq7KmVb+rEO5aEifj7E44c3i0afIyr4WA=
github.com/Layr-Labs/cerberus-api v0.0.2-0.20250108174619-d5e1eb03fbd5 h1:s24M6HYObEuV9OSY36jUM09kp5fOhuz/g1ev2qWDPzU=
github.com/Layr-Labs/cerberus-api v0.0.2-0.20250108174619-d5e1eb03fbd5/go.mod h1:Lm4fhzy0S3P7GjerzuseGaBFVczsIKmEhIjcT52Hluo=
github.com/Layr-Labs/cerberus-api v0.0.2-0.20250117193600-e69c5e8b08fd h1:prMzW4BY6KZtWEanf5EIsyHzIZKCNV2mVIXrE6glRRM=
github.com/Layr-Labs/cerberus-api v0.0.2-0.20250117193600-e69c5e8b08fd/go.mod h1:Lm4fhzy0S3P7GjerzuseGaBFVczsIKmEhIjcT52Hluo=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/aws/aws-sdk-go-v2 v1.32.5 h1:U8vdWJuY7ruAkzaOdD7guwJjD06YSKmnKCJs7s3IkIo=
Expand Down
23 changes: 23 additions & 0 deletions internal/common/common.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package common

import (
"crypto/sha256"
"encoding/hex"

"github.com/google/uuid"
)

func Trim0x(s string) string {
if len(s) >= 2 && s[0:2] == "0x" {
return s[2:]
Expand All @@ -13,3 +20,19 @@ func RemovePrefix(s string, prefix string) string {
}
return s
}

func CreateSHA256Hash(s string) string {
hash := sha256.New()
hash.Write([]byte(s))
return hex.EncodeToString(hash.Sum(nil))
}

func GenerateNewAPIKeyAndHash() (string, string, error) {
newUUID, err := uuid.NewV7()
if err != nil {
return "", "", err
}
apiKey := newUUID.String()
apiKeyHash := CreateSHA256Hash(apiKey)
return apiKey, apiKeyHash, nil
}
11 changes: 7 additions & 4 deletions internal/configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ type Configuration struct {
// Google Secrets Manager storage parameters
GCPProjectID string

GrpcPort string
MetricsPort string
GrpcPort int
MetricsPort int
AdminPort int

EnableAdmin bool

TLSCACert string
TLSServerKey string
Expand Down Expand Up @@ -72,11 +75,11 @@ func (s *Configuration) Validate() error {
return fmt.Errorf("unsupported storage type: %s", s.StorageType)
}

if s.GrpcPort == "" {
if s.GrpcPort == 0 {
return fmt.Errorf("gRPC port is required")
}

if s.MetricsPort == "" {
if s.MetricsPort == 0 {
return fmt.Errorf("metrics port is required")
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE public.keys_metadata ADD COLUMN api_key_hash text;
ALTER TABLE public.keys_metadata ADD COLUMN locked boolean DEFAULT false;
2 changes: 2 additions & 0 deletions internal/database/model/key_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ type KeyMetadata struct {
PublicKeyG2 string `db:"public_key_g2"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
ApiKeyHash string `db:"api_key_hash"`
Locked bool `db:"locked"`
}
7 changes: 7 additions & 0 deletions internal/database/repository/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package repository

import "errors"

var (
ErrKeyNotFound = errors.New("key not found")
)
2 changes: 2 additions & 0 deletions internal/database/repository/key_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ type KeyMetadataRepository interface {
Create(ctx context.Context, metadata *model.KeyMetadata) error
Get(ctx context.Context, publicKeyG1 string) (*model.KeyMetadata, error)
Update(ctx context.Context, metadata *model.KeyMetadata) error
UpdateAPIKeyHash(ctx context.Context, publicKeyG1 string, apiKeyHash string) error
UpdateLockStatus(ctx context.Context, publicKeyG1 string, locked bool) error
Delete(ctx context.Context, publicKeyG1 string) error
List(ctx context.Context) ([]*model.KeyMetadata, error)
}
68 changes: 61 additions & 7 deletions internal/database/repository/postgres/key_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ func NewKeyMetadataRepository(db *sql.DB) repository.KeyMetadataRepository {
const (
createKeyMetadataQuery = `
INSERT INTO public.keys_metadata (
public_key_g1, public_key_g2, created_at, updated_at
) VALUES ($1, $2, $3, $4)
public_key_g1, public_key_g2, created_at, updated_at, api_key_hash
) VALUES ($1, $2, $3, $4, $5)
`

getKeyMetadataQuery = `
SELECT public_key_g1, public_key_g2, created_at, updated_at
SELECT public_key_g1, public_key_g2, created_at, updated_at, api_key_hash, locked
FROM public.keys_metadata
WHERE public_key_g1 = $1
`
Expand All @@ -39,13 +39,25 @@ const (
WHERE public_key_g1 = $2
`

updateAPIKeyHashQuery = `
UPDATE public.keys_metadata
SET api_key_hash = $1, updated_at = $2
WHERE public_key_g1 = $3
`

updateLockStatusQuery = `
UPDATE public.keys_metadata
SET locked = $1, updated_at = $2
WHERE public_key_g1 = $3
`

deleteKeyMetadataQuery = `
DELETE FROM public.keys_metadata
WHERE public_key_g1 = $1
`

listKeyMetadataQuery = `
SELECT public_key_g1, public_key_g2, created_at, updated_at
listAllKeysQuery = `
SELECT public_key_g1, public_key_g2, created_at, updated_at, locked
FROM public.keys_metadata
ORDER BY created_at DESC
`
Expand All @@ -68,6 +80,7 @@ func (r *keyMetadataRepo) Create(ctx context.Context, metadata *model.KeyMetadat
metadata.PublicKeyG2,
metadata.CreatedAt,
metadata.UpdatedAt,
metadata.ApiKeyHash,
)
return err
}
Expand All @@ -79,9 +92,11 @@ func (r *keyMetadataRepo) Get(ctx context.Context, publicKeyG1 string) (*model.K
&metadata.PublicKeyG2,
&metadata.CreatedAt,
&metadata.UpdatedAt,
&metadata.ApiKeyHash,
&metadata.Locked,
)
if err == sql.ErrNoRows {
return nil, errors.New("key metadata not found")
return nil, repository.ErrKeyNotFound
}
if err != nil {
return nil, err
Expand Down Expand Up @@ -113,6 +128,29 @@ func (r *keyMetadataRepo) Update(ctx context.Context, metadata *model.KeyMetadat
return nil
}

func (r *keyMetadataRepo) UpdateAPIKeyHash(
ctx context.Context,
publicKeyG1 string,
apiKeyHash string,
) error {
if publicKeyG1 == "" {
return errors.New("public key g1 is required")
}

if apiKeyHash == "" {
return errors.New("api key hash is required")
}

updatedAt := time.Now().UTC()

_, err := r.db.ExecContext(ctx, updateAPIKeyHashQuery,
apiKeyHash,
updatedAt,
publicKeyG1,
)
return err
}

func (r *keyMetadataRepo) Delete(ctx context.Context, publicKeyG1 string) error {
if publicKeyG1 == "" {
return errors.New("public key g1 is required")
Expand All @@ -134,7 +172,7 @@ func (r *keyMetadataRepo) Delete(ctx context.Context, publicKeyG1 string) error
}

func (r *keyMetadataRepo) List(ctx context.Context) ([]*model.KeyMetadata, error) {
rows, err := r.db.QueryContext(ctx, listKeyMetadataQuery)
rows, err := r.db.QueryContext(ctx, listAllKeysQuery)
if err != nil {
return nil, err
}
Expand All @@ -148,6 +186,7 @@ func (r *keyMetadataRepo) List(ctx context.Context) ([]*model.KeyMetadata, error
&m.PublicKeyG2,
&m.CreatedAt,
&m.UpdatedAt,
&m.Locked,
)
if err != nil {
return nil, err
Expand All @@ -160,3 +199,18 @@ func (r *keyMetadataRepo) List(ctx context.Context) ([]*model.KeyMetadata, error
}
return metadata, nil
}

func (r *keyMetadataRepo) UpdateLockStatus(
ctx context.Context,
publicKeyG1 string,
locked bool,
) error {
updatedAt := time.Now().UTC()

_, err := r.db.ExecContext(ctx, updateLockStatusQuery,
locked,
updatedAt,
publicKeyG1,
)
return err
}
25 changes: 25 additions & 0 deletions internal/database/repository/postgres/key_metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,28 @@ func TestKeyMetadataRepository_List(t *testing.T) {
assert.Equal(t, "test_key_1", results[1].PublicKeyG1)
})
}

func TestKeyMetadataRepository_UpdateAPIKeyHash(t *testing.T) {
testDB := SetupTestDB(t)
// No need to defer db.Close() as it's handled by t.Cleanup

// Create initial test data
initialKey := &model.KeyMetadata{
PublicKeyG1: "test_key_1",
PublicKeyG2: "test_key_2",
ApiKeyHash: "test_api_key_hash",
}
err := testDB.Repo.Create(context.Background(), initialKey)
require.NoError(t, err)

apiKeyHash := "test_api_key_hash_2"

err = testDB.Repo.UpdateAPIKeyHash(context.Background(), initialKey.PublicKeyG1, apiKeyHash)
require.NoError(t, err)

// Verify the update
result, err := testDB.Repo.Get(context.Background(), initialKey.PublicKeyG1)
assert.NoError(t, err)
assert.Equal(t, apiKeyHash, result.ApiKeyHash)
assert.WithinDuration(t, time.Now(), result.UpdatedAt, 2*time.Second)
}
4 changes: 3 additions & 1 deletion internal/database/repository/postgres/test_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ func CreateTestContainer(t *testing.T) (*TestContainer, error) {
public_key_g1 VARCHAR(255) PRIMARY KEY,
public_key_g2 VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
api_key_hash text,
locked boolean DEFAULT false
);
`)
if err != nil {
Expand Down
Loading

0 comments on commit 4cdf1cf

Please sign in to comment.