diff --git a/.gitignore b/.gitignore index e306362..47292e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -.env +**/.env data/ bin/ diff --git a/Makefile b/Makefile index 8f01273..4b25303 100644 --- a/Makefile +++ b/Makefile @@ -36,3 +36,8 @@ tests: ## runs all tests .PHONY: docker docker: ## runs docker build docker build -t $(APP_NAME):latest . + +.PHONY: migrate +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 diff --git a/README.md b/README.md index f1ca4ed..a6a7226 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # Remote Signer Implementation of cerberus-api This is a remote signer which supports BLS signatures on the BN254 curve. +## Disclaimer +🚧 Cerberus is under active development and has not been audited. Cerberus is rapidly being upgraded, features may be added, removed or otherwise improved or modified and interfaces will have breaking changes. Cerberus should be used only for testing purposes and not in production. Cerberus is provided "as is" and Eigen Labs, Inc. does not guarantee its functionality or provide support for its use in production. 🚧 + * [Remote Signer Implementation of cerberus-api](#remote-signer-implementation-of-cerberus-api) * [Installation](#installation) diff --git a/cmd/cerberus/main.go b/cmd/cerberus/main.go index 7ceca9f..255b2ae 100644 --- a/cmd/cerberus/main.go +++ b/cmd/cerberus/main.go @@ -108,6 +108,13 @@ var ( Usage: "Project ID for Google Cloud Platform", EnvVars: []string{"GCP_PROJECT_ID"}, } + + postgresDatabaseURLFlag = &cli.StringFlag{ + Name: "postgres-database-url", + Usage: "Postgres database URL", + Value: "postgres://user:password@localhost:5432/cerberus?sslmode=disable", + EnvVars: []string{"POSTGRES_DATABASE_URL"}, + } ) func main() { @@ -143,6 +150,7 @@ func main() { awsAccessKeyIDFlag, awsSecretAccessKeyFlag, gcpProjectIDFlag, + postgresDatabaseURLFlag, } sort.Sort(cli.FlagsByName(app.Flags)) @@ -172,7 +180,7 @@ func start(c *cli.Context) error { awsAccessKeyID := c.String(awsAccessKeyIDFlag.Name) awsSecretAccessKey := c.String(awsSecretAccessKeyFlag.Name) gcpProjectID := c.String(gcpProjectIDFlag.Name) - + postgresDatabaseURL := c.String(postgresDatabaseURLFlag.Name) cfg := &configuration.Configuration{ KeystoreDir: keystoreDir, GrpcPort: grpcPort, @@ -186,6 +194,7 @@ func start(c *cli.Context) error { AWSAccessKeyID: awsAccessKeyID, AWSSecretAccessKey: awsSecretAccessKey, GCPProjectID: gcpProjectID, + PostgresDatabaseURL: postgresDatabaseURL, } if err := cfg.Validate(); err != nil { diff --git a/go.mod b/go.mod index 245bc9b..3e53f82 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,14 @@ module github.com/Layr-Labs/cerberus -go 1.21 +go 1.22.0 -toolchain go1.21.11 +toolchain go1.22.3 + +replace github.com/Layr-Labs/cerberus-api => ../cerberus-api require ( cloud.google.com/go/secretmanager v1.14.2 - github.com/Layr-Labs/bn254-keystore-go v0.0.0-20241118175331-3ceaf682f032 + github.com/Layr-Labs/bn254-keystore-go v0.0.0-20250107020618-26bd412fae87 github.com/Layr-Labs/cerberus-api v0.0.1 github.com/aws/aws-sdk-go-v2 v1.32.5 github.com/aws/aws-sdk-go-v2/config v1.28.5 @@ -20,6 +22,47 @@ require ( google.golang.org/grpc v1.67.1 ) +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/containerd/containerd v1.7.18 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.2.0+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + 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/golang-migrate/migrate/v4 v4.18.1 // 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 + github.com/magiconair/properties v1.8.7 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/user v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/shirou/gopsutil/v3 v3.23.12 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/testcontainers/testcontainers-go v0.34.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.uber.org/atomic v1.7.0 // indirect +) + require ( cloud.google.com/go/auth v0.9.9 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect @@ -50,6 +93,7 @@ require ( github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/klauspost/compress v1.17.9 // indirect + github.com/lib/pq v1.10.9 github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 825523a..c591645 100644 --- a/go.sum +++ b/go.sum @@ -11,11 +11,17 @@ cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= cloud.google.com/go/secretmanager v1.14.2 h1:2XscWCfy//l/qF96YE18/oUaNJynAx749Jg3u0CjQr8= cloud.google.com/go/secretmanager v1.14.2/go.mod h1:Q18wAPMM6RXLC/zVpWTlqq2IBSbbm7pKBlM3lCKsmjw= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Layr-Labs/bn254-keystore-go v0.0.0-20241118175331-3ceaf682f032 h1:nKNJmoEB1+S1H09EsJuo8A2IXeFIg5tsGVK3UB6Qefo= -github.com/Layr-Labs/bn254-keystore-go v0.0.0-20241118175331-3ceaf682f032/go.mod h1:7J8hptSX8cFq7KmVb+rEO5aEifj7E44c3i0afIyr4WA= -github.com/Layr-Labs/cerberus-api v0.0.1 h1:MSLVdxtRS1qnwLks3COnnUw/M3tjLGtbSGaLde86HRg= -github.com/Layr-Labs/cerberus-api v0.0.1/go.mod h1:Lm4fhzy0S3P7GjerzuseGaBFVczsIKmEhIjcT52Hluo= +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/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= github.com/aws/aws-sdk-go-v2 v1.32.5/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0= @@ -48,6 +54,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.14.2 h1:YXVoyPndbdvcEVcseEovVfp0qjJp7S+i5+xgp/Nfbdc= github.com/bits-and-blooms/bitset v1.14.2/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -57,11 +65,29 @@ github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/Yj github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= +github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= +github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -73,6 +99,12 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y= +github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -95,6 +127,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= @@ -109,6 +143,13 @@ github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDP github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -119,13 +160,39 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -139,18 +206,38 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= +github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/testcontainers/testcontainers-go v0.34.0 h1:5fbgF0vIN5u+nD3IWabQwRybuB4GY8G2HHgCkbMzMHo= +github.com/testcontainers/testcontainers-go v0.34.0/go.mod h1:6P/kMkQe8yqPHfPWNulFGdFHTD8HB2vLq/231xY2iPQ= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= @@ -165,7 +252,10 @@ go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHy go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= @@ -173,11 +263,16 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= @@ -187,12 +282,21 @@ golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -206,7 +310,13 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU= google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= diff --git a/internal/configuration/configuration.go b/internal/configuration/configuration.go index 2fd28b2..b779ff9 100644 --- a/internal/configuration/configuration.go +++ b/internal/configuration/configuration.go @@ -36,6 +36,9 @@ type Configuration struct { TLSCACert string TLSServerKey string + + // Postgres database parameters + PostgresDatabaseURL string } func (s *Configuration) Validate() error { @@ -85,5 +88,9 @@ func (s *Configuration) Validate() error { return fmt.Errorf("TLS CA certificate is required when TLS server key is provided") } + if s.PostgresDatabaseURL == "" { + return fmt.Errorf("postgres database URL is required") + } + return nil } diff --git a/internal/database/.env.example b/internal/database/.env.example new file mode 100644 index 0000000..e2271fb --- /dev/null +++ b/internal/database/.env.example @@ -0,0 +1,4 @@ +DB_PASSWORD=password +DB_USER=user +DB_NAME=db +DB_PORT=5432 diff --git a/internal/database/docker-compose.yml b/internal/database/docker-compose.yml new file mode 100644 index 0000000..1fe5c50 --- /dev/null +++ b/internal/database/docker-compose.yml @@ -0,0 +1,18 @@ +services: + db: + image: postgres:15 + container_name: db + ports: + - "${DB_PORT}:${DB_PORT}" + environment: + - "POSTGRES_PASSWORD=${DB_PASSWORD}" + - "POSTGRES_USER=${DB_USER}" + - "POSTGRES_DB=${DB_NAME}" + volumes: + - postgres_data:/var/lib/postgresql/data + env_file: + - .env + restart: unless-stopped + +volumes: + postgres_data: \ No newline at end of file diff --git a/internal/database/migration.go b/internal/database/migration.go new file mode 100644 index 0000000..a13b649 --- /dev/null +++ b/internal/database/migration.go @@ -0,0 +1,40 @@ +package database + +import ( + "embed" + "errors" + "fmt" + "log/slog" + + "github.com/golang-migrate/migrate/v4" + _ "github.com/golang-migrate/migrate/v4/database/postgres" + "github.com/golang-migrate/migrate/v4/source/iofs" +) + +//go:embed migrations/*.sql +var migrationsFS embed.FS + +// MigrateDB handles database migrations using embedded files +func MigrateDB(dbURL string, logger *slog.Logger) error { + logger.Info("Running database migrations") + // Create iofs driver with embedded files + driver, err := iofs.New(migrationsFS, "migrations") + if err != nil { + return fmt.Errorf("failed to create iofs driver: %w", err) + } + + // Create migrate instance + m, err := migrate.NewWithSourceInstance("iofs", driver, dbURL) + if err != nil { + return fmt.Errorf("failed to create migrate instance: %w", err) + } + defer m.Close() + + // Run migrations + if err := m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) { + return fmt.Errorf("failed to run migrations: %w", err) + } + + logger.Info("Database migration completed successfully") + return nil +} diff --git a/internal/database/migrations/20250103213805_create_keys_metadata.down.sql b/internal/database/migrations/20250103213805_create_keys_metadata.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/internal/database/migrations/20250103213805_create_keys_metadata.up.sql b/internal/database/migrations/20250103213805_create_keys_metadata.up.sql new file mode 100644 index 0000000..bdcf0ed --- /dev/null +++ b/internal/database/migrations/20250103213805_create_keys_metadata.up.sql @@ -0,0 +1,8 @@ +CREATE SCHEMA IF NOT EXISTS public; + +CREATE TABLE IF NOT EXISTS public.keys_metadata ( + 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() +); diff --git a/internal/database/model/key_metadata.go b/internal/database/model/key_metadata.go new file mode 100644 index 0000000..e5ab661 --- /dev/null +++ b/internal/database/model/key_metadata.go @@ -0,0 +1,10 @@ +package model + +import "time" + +type KeyMetadata struct { + PublicKeyG1 string `db:"public_key_g1"` + PublicKeyG2 string `db:"public_key_g2"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} diff --git a/internal/database/repository/key_metadata.go b/internal/database/repository/key_metadata.go new file mode 100644 index 0000000..6c8626e --- /dev/null +++ b/internal/database/repository/key_metadata.go @@ -0,0 +1,15 @@ +package repository + +import ( + "context" + + "github.com/Layr-Labs/cerberus/internal/database/model" +) + +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 + Delete(ctx context.Context, publicKeyG1 string) error + List(ctx context.Context) ([]*model.KeyMetadata, error) +} diff --git a/internal/database/repository/postgres/key_metadata.go b/internal/database/repository/postgres/key_metadata.go new file mode 100644 index 0000000..42fc9e9 --- /dev/null +++ b/internal/database/repository/postgres/key_metadata.go @@ -0,0 +1,147 @@ +package postgres + +import ( + "context" + "database/sql" + "errors" + "time" + + "github.com/Layr-Labs/cerberus/internal/database/model" + "github.com/Layr-Labs/cerberus/internal/database/repository" +) + +type keyMetadataRepo struct { + db *sql.DB +} + +func NewKeyMetadataRepository(db *sql.DB) repository.KeyMetadataRepository { + return &keyMetadataRepo{ + db: db, + } +} + +const ( + createKeyMetadataQuery = ` + INSERT INTO public.keys_metadata ( + public_key_g1, public_key_g2, created_at, updated_at + ) VALUES ($1, $2, $3, $4) + ` + + getKeyMetadataQuery = ` + SELECT public_key_g1, public_key_g2, created_at, updated_at + FROM public.keys_metadata + WHERE public_key_g1 = $1 + ` + + updateKeyMetadataQuery = ` + UPDATE public.keys_metadata + SET updated_at = $1 + WHERE public_key_g1 = $2 + ` + + deleteKeyMetadataQuery = ` + DELETE FROM public.keys_metadata + WHERE public_key_g1 = $1 + ` + + listKeyMetadataQuery = ` + SELECT public_key_g1, public_key_g2, created_at, updated_at + FROM public.keys_metadata + ORDER BY created_at DESC + ` +) + +func (r *keyMetadataRepo) Create(ctx context.Context, metadata *model.KeyMetadata) error { + now := time.Now().UTC() + metadata.CreatedAt = now + metadata.UpdatedAt = now + + _, err := r.db.ExecContext(ctx, createKeyMetadataQuery, + metadata.PublicKeyG1, + metadata.PublicKeyG2, + metadata.CreatedAt, + metadata.UpdatedAt, + ) + return err +} + +func (r *keyMetadataRepo) Get(ctx context.Context, publicKeyG1 string) (*model.KeyMetadata, error) { + metadata := &model.KeyMetadata{} + err := r.db.QueryRowContext(ctx, getKeyMetadataQuery, publicKeyG1).Scan( + &metadata.PublicKeyG1, + &metadata.PublicKeyG2, + &metadata.CreatedAt, + &metadata.UpdatedAt, + ) + if err == sql.ErrNoRows { + return nil, errors.New("key metadata not found") + } + if err != nil { + return nil, err + } + return metadata, nil +} + +func (r *keyMetadataRepo) Update(ctx context.Context, metadata *model.KeyMetadata) error { + metadata.UpdatedAt = time.Now().UTC() + result, err := r.db.ExecContext(ctx, updateKeyMetadataQuery, + metadata.UpdatedAt, + metadata.PublicKeyG1, + ) + if err != nil { + return err + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return err + } + if rowsAffected == 0 { + return errors.New("key metadata not found") + } + return nil +} + +func (r *keyMetadataRepo) Delete(ctx context.Context, publicKeyG1 string) error { + result, err := r.db.ExecContext(ctx, deleteKeyMetadataQuery, publicKeyG1) + if err != nil { + return err + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return err + } + if rowsAffected == 0 { + return errors.New("key metadata not found") + } + return nil +} + +func (r *keyMetadataRepo) List(ctx context.Context) ([]*model.KeyMetadata, error) { + rows, err := r.db.QueryContext(ctx, listKeyMetadataQuery) + if err != nil { + return nil, err + } + defer rows.Close() + + var metadata []*model.KeyMetadata + for rows.Next() { + m := &model.KeyMetadata{} + err := rows.Scan( + &m.PublicKeyG1, + &m.PublicKeyG2, + &m.CreatedAt, + &m.UpdatedAt, + ) + if err != nil { + return nil, err + } + metadata = append(metadata, m) + } + + if err = rows.Err(); err != nil { + return nil, err + } + return metadata, nil +} diff --git a/internal/database/repository/postgres/key_metadata_test.go b/internal/database/repository/postgres/key_metadata_test.go new file mode 100644 index 0000000..46e9790 --- /dev/null +++ b/internal/database/repository/postgres/key_metadata_test.go @@ -0,0 +1,267 @@ +package postgres + +import ( + "context" + "testing" + "time" + + "github.com/Layr-Labs/cerberus/internal/database/model" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + _ "github.com/lib/pq" +) + +func TestWithContainer_KeyMetadataRepository_Create(t *testing.T) { + testDB := SetupTestDB(t) + + metadata := &model.KeyMetadata{ + PublicKeyG1: "test_key_1", + PublicKeyG2: "test_key_2", + } + + err := testDB.Repo.Create(context.Background(), metadata) + require.NoError(t, err) + + // Verify the record was created + var result model.KeyMetadata + err = testDB.db.QueryRow( + "SELECT public_key_g1, public_key_g2, created_at, updated_at FROM public.keys_metadata WHERE public_key_g1 = $1", + metadata.PublicKeyG1, + ).Scan(&result.PublicKeyG1, &result.PublicKeyG2, &result.CreatedAt, &result.UpdatedAt) + + assert.NoError(t, err) + assert.Equal(t, metadata.PublicKeyG1, result.PublicKeyG1) + assert.Equal(t, metadata.PublicKeyG2, result.PublicKeyG2) + assert.WithinDuration(t, time.Now().UTC(), result.CreatedAt, 2*time.Second) +} + +func TestKeyMetadataRepository_Create(t *testing.T) { + testDB := SetupTestDB(t) + // No need to defer db.Close() as it's handled by t.Cleanup + + tests := []struct { + name string + input *model.KeyMetadata + wantErr bool + }{ + { + name: "successful creation", + input: &model.KeyMetadata{ + PublicKeyG1: "test_key_1", + PublicKeyG2: "test_key_2", + }, + wantErr: false, + }, + { + name: "duplicate key", + input: &model.KeyMetadata{ + PublicKeyG1: "test_key_1", + PublicKeyG2: "test_key_2", + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := testDB.Repo.Create(context.Background(), tt.input) + if tt.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + + // Verify the record was created + var result model.KeyMetadata + err = testDB.db.QueryRow( + "SELECT public_key_g1, public_key_g2, created_at, updated_at FROM public.keys_metadata WHERE public_key_g1 = $1", + tt.input.PublicKeyG1, + ).Scan(&result.PublicKeyG1, &result.PublicKeyG2, &result.CreatedAt, &result.UpdatedAt) + + assert.NoError(t, err) + assert.Equal(t, tt.input.PublicKeyG1, result.PublicKeyG1) + assert.Equal(t, tt.input.PublicKeyG2, result.PublicKeyG2) + assert.WithinDuration(t, time.Now(), result.CreatedAt, 2*time.Second) + assert.WithinDuration(t, time.Now(), result.UpdatedAt, 2*time.Second) + }) + } +} + +func TestKeyMetadataRepository_Get(t *testing.T) { + testDB := SetupTestDB(t) + // No need to defer db.Close() as it's handled by t.Cleanup + + // Create test data + testKey := &model.KeyMetadata{ + PublicKeyG1: "test_key_1", + PublicKeyG2: "test_key_2", + } + err := testDB.Repo.Create(context.Background(), testKey) + require.NoError(t, err) + + tests := []struct { + name string + keyG1 string + wantKey string + wantError bool + }{ + { + name: "existing key", + keyG1: "test_key_1", + wantKey: "test_key_2", + wantError: false, + }, + { + name: "non-existing key", + keyG1: "non_existing_key", + wantKey: "", + wantError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := testDB.Repo.Get(context.Background(), tt.keyG1) + if tt.wantError { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.keyG1, result.PublicKeyG1) + assert.Equal(t, tt.wantKey, result.PublicKeyG2) + }) + } +} + +func TestKeyMetadataRepository_Update(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", + } + err := testDB.Repo.Create(context.Background(), initialKey) + require.NoError(t, err) + + tests := []struct { + name string + input *model.KeyMetadata + wantErr bool + }{ + { + name: "successful update", + input: &model.KeyMetadata{ + PublicKeyG1: "test_key_1", + }, + wantErr: false, + }, + { + name: "non-existing key", + input: &model.KeyMetadata{ + PublicKeyG1: "non_existing_key", + PublicKeyG2: "updated_key_2", + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := testDB.Repo.Update(context.Background(), tt.input) + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + + // Verify the update + result, err := testDB.Repo.Get(context.Background(), tt.input.PublicKeyG1) + assert.NoError(t, err) + assert.WithinDuration(t, time.Now(), result.UpdatedAt, 2*time.Second) + }) + } +} + +func TestKeyMetadataRepository_Delete(t *testing.T) { + testDB := SetupTestDB(t) + // No need to defer db.Close() as it's handled by t.Cleanup + + // Create test data + testKey := &model.KeyMetadata{ + PublicKeyG1: "test_key_1", + PublicKeyG2: "test_key_2", + } + err := testDB.Repo.Create(context.Background(), testKey) + require.NoError(t, err) + + tests := []struct { + name string + keyG1 string + wantErr bool + }{ + { + name: "existing key", + keyG1: "test_key_1", + wantErr: false, + }, + { + name: "non-existing key", + keyG1: "non_existing_key", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := testDB.Repo.Delete(context.Background(), tt.keyG1) + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + + // Verify the deletion + _, err = testDB.Repo.Get(context.Background(), tt.keyG1) + assert.Error(t, err) + }) + } +} + +func TestKeyMetadataRepository_List(t *testing.T) { + testDB := SetupTestDB(t) + // No need to defer db.Close() as it's handled by t.Cleanup + + // Create test data + testKeys := []*model.KeyMetadata{ + { + PublicKeyG1: "test_key_1", + PublicKeyG2: "test_key_2", + }, + { + PublicKeyG1: "test_key_3", + PublicKeyG2: "test_key_4", + }, + } + + for _, key := range testKeys { + err := testDB.Repo.Create(context.Background(), key) + require.NoError(t, err) + } + + t.Run("list all keys", func(t *testing.T) { + results, err := testDB.Repo.List(context.Background()) + assert.NoError(t, err) + assert.Len(t, results, len(testKeys)) + + // Verify the order (should be ordered by created_at DESC) + assert.Equal(t, "test_key_3", results[0].PublicKeyG1) + assert.Equal(t, "test_key_1", results[1].PublicKeyG1) + }) +} diff --git a/internal/database/repository/postgres/test_helper.go b/internal/database/repository/postgres/test_helper.go new file mode 100644 index 0000000..64653d2 --- /dev/null +++ b/internal/database/repository/postgres/test_helper.go @@ -0,0 +1,122 @@ +package postgres + +import ( + "context" + "database/sql" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +type TestContainer struct { + Container testcontainers.Container + DB *sql.DB +} + +func CreateTestContainer(t *testing.T) (*TestContainer, error) { + ctx := context.Background() + + // Container configuration + req := testcontainers.ContainerRequest{ + Image: "postgres:15-alpine", + ExposedPorts: []string{"5432/tcp"}, + WaitingFor: wait.ForListeningPort("5432/tcp"), + Env: map[string]string{ + "POSTGRES_DB": "testdb", + "POSTGRES_USER": "testuser", + "POSTGRES_PASSWORD": "testpass", + }, + } + + // Start container + container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + if err != nil { + return nil, fmt.Errorf("failed to start container: %v", err) + } + + // Get host and port + host, err := container.Host(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get container host: %v", err) + } + + port, err := container.MappedPort(ctx, "5432") + if err != nil { + return nil, fmt.Errorf("failed to get container port: %v", err) + } + + // Connection string + dsn := fmt.Sprintf( + "host=%s port=%s user=testuser password=testpass dbname=testdb sslmode=disable", + host, + port.Port(), + ) + + // Connect and create test schema + var db *sql.DB + // Retry logic for database connection + for i := 0; i < 5; i++ { + db, err = sql.Open("postgres", dsn) + if err == nil { + err = db.Ping() + if err == nil { + break + } + } + time.Sleep(time.Second) + } + if err != nil { + return nil, fmt.Errorf("failed to connect to database: %v", err) + } + + // Create test schema + _, err = db.Exec(` + CREATE TABLE IF NOT EXISTS public.keys_metadata ( + 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() + ); + `) + if err != nil { + return nil, fmt.Errorf("failed to create schema: %v", err) + } + + return &TestContainer{ + Container: container, + DB: db, + }, nil +} + +type testDB struct { + db *sql.DB + Repo *keyMetadataRepo +} + +// Modified test setup function +func SetupTestDB(t *testing.T) *testDB { + container, err := CreateTestContainer(t) + require.NoError(t, err) + + // Register cleanup + t.Cleanup(func() { + if err := container.DB.Close(); err != nil { + t.Errorf("Failed to close db connection: %v", err) + } + if err := container.Container.Terminate(context.Background()); err != nil { + t.Errorf("Failed to terminate container: %v", err) + } + }) + + return &testDB{ + db: container.DB, + Repo: &keyMetadataRepo{db: container.DB}, + } +} diff --git a/internal/database/sql/cerberus.sql b/internal/database/sql/cerberus.sql new file mode 100644 index 0000000..3add411 --- /dev/null +++ b/internal/database/sql/cerberus.sql @@ -0,0 +1,8 @@ +CREATE SCHEMA IF NOT EXISTS public; + +CREATE TABLE IF NOT EXISTS public.keys_metadata ( + 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() +); \ No newline at end of file diff --git a/internal/server/server.go b/internal/server/server.go index af87e43..3e48015 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -1,6 +1,7 @@ package server import ( + "database/sql" "fmt" "log" "log/slog" @@ -15,6 +16,8 @@ import ( v1 "github.com/Layr-Labs/cerberus-api/pkg/api/v1" "github.com/Layr-Labs/cerberus/internal/configuration" + "github.com/Layr-Labs/cerberus/internal/database" + "github.com/Layr-Labs/cerberus/internal/database/repository/postgres" "github.com/Layr-Labs/cerberus/internal/metrics" "github.com/Layr-Labs/cerberus/internal/middleware" "github.com/Layr-Labs/cerberus/internal/services/kms" @@ -27,6 +30,8 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection" + + _ "github.com/lib/pq" ) func Start(config *configuration.Configuration, logger *slog.Logger) { @@ -95,12 +100,27 @@ func Start(config *configuration.Configuration, logger *slog.Logger) { opts = append(opts, grpc.Creds(creds)) } + // Initialize database + db, err := sql.Open("postgres", config.PostgresDatabaseURL) + if err != nil { + logger.Error(fmt.Sprintf("Failed to connect to database: %v", err)) + os.Exit(1) + } + defer db.Close() + + if err := database.MigrateDB(config.PostgresDatabaseURL, logger); err != nil { + logger.Error(fmt.Sprintf("Failed to migrate database: %v", err)) + os.Exit(1) + } + + keyMetadataRepo := postgres.NewKeyMetadataRepository(db) + // Register metrics middleware metricsMiddleware := middleware.NewMetricsMiddleware(registry, rpcMetrics) opts = append(opts, grpc.UnaryInterceptor(metricsMiddleware.UnaryServerInterceptor())) s := grpc.NewServer(opts...) - kmsService := kms.NewService(config, keystore, logger, rpcMetrics) + kmsService := kms.NewService(config, keystore, keyMetadataRepo, logger, rpcMetrics) signingService := signing.NewService(config, keystore, logger, rpcMetrics) v1.RegisterKeyManagerServer(s, kmsService) diff --git a/internal/services/kms/kms.go b/internal/services/kms/kms.go index 936ef0e..fab59dc 100644 --- a/internal/services/kms/kms.go +++ b/internal/services/kms/kms.go @@ -11,9 +11,12 @@ import ( "github.com/Layr-Labs/cerberus/internal/common" "github.com/Layr-Labs/cerberus/internal/configuration" + "github.com/Layr-Labs/cerberus/internal/database/model" + "github.com/Layr-Labs/cerberus/internal/database/repository" "github.com/Layr-Labs/cerberus/internal/metrics" "github.com/Layr-Labs/cerberus/internal/store" + "github.com/Layr-Labs/bn254-keystore-go/curve" "github.com/Layr-Labs/bn254-keystore-go/keystore" "github.com/Layr-Labs/bn254-keystore-go/mnemonic" @@ -22,10 +25,11 @@ import ( ) type Service struct { - config *configuration.Configuration - logger *slog.Logger - store store.Store - metrics metrics.Recorder + config *configuration.Configuration + logger *slog.Logger + store store.Store + metrics metrics.Recorder + keyMetadataRepo repository.KeyMetadataRepository v1.UnimplementedKeyManagerServer } @@ -33,14 +37,16 @@ type Service struct { func NewService( config *configuration.Configuration, store store.Store, + keyMetadataRepo repository.KeyMetadataRepository, logger *slog.Logger, metrics metrics.Recorder, ) *Service { return &Service{ - config: config, - store: store, - metrics: metrics, - logger: logger.With("component", "kms"), + config: config, + store: store, + metrics: metrics, + keyMetadataRepo: keyMetadataRepo, + logger: logger.With("component", "kms"), } } @@ -57,21 +63,37 @@ func (k *Service) GenerateKeyPair( return nil, status.Error(codes.Internal, err.Error()) } + g2PubKey, err := keyPair.GetG2PublicKey(curve.BN254) + if err != nil { + k.logger.Error(fmt.Sprintf("Failed to get G2 public key: %v", err)) + return nil, status.Error(codes.Internal, err.Error()) + } + pubKeyHex, err := k.store.StoreKey(ctx, keyPair) if err != nil { k.logger.Error(fmt.Sprintf("Failed to save BLS key pair to file: %v", err)) return nil, status.Error(codes.Internal, err.Error()) } + err = k.keyMetadataRepo.Create(ctx, &model.KeyMetadata{ + PublicKeyG1: pubKeyHex, + PublicKeyG2: g2PubKey, + }) + if err != nil { + k.logger.Error(fmt.Sprintf("Failed to save key metadata: %v", err)) + return nil, status.Error(codes.Internal, err.Error()) + } + // Convert the private key to a hex string pkBytesSlice := make([]byte, len(keyPair.PrivateKey)) copy(pkBytesSlice, keyPair.PrivateKey[:]) privKeyHex := common.Trim0x(hex.EncodeToString(pkBytesSlice)) return &v1.GenerateKeyPairResponse{ - PublicKey: pubKeyHex, - PrivateKey: privKeyHex, - Mnemonic: keyPair.Mnemonic, + PublicKeyG1: pubKeyHex, + PublicKeyG2: g2PubKey, + PrivateKey: privKeyHex, + Mnemonic: keyPair.Mnemonic, }, nil } @@ -117,18 +139,61 @@ func (k *Service) ImportKey( return nil, status.Error(codes.Internal, err.Error()) } - return &v1.ImportKeyResponse{PublicKey: pubKeyHex}, nil + ks := &keystore.KeyPair{ + PrivateKey: pkBytes, + } + + g2PubKey, err := ks.GetG2PublicKey(curve.BN254) + if err != nil { + k.logger.Error(fmt.Sprintf("Failed to get G2 public key: %v", err)) + return nil, status.Error(codes.Internal, err.Error()) + } + + err = k.keyMetadataRepo.Create(ctx, &model.KeyMetadata{ + PublicKeyG1: pubKeyHex, + PublicKeyG2: g2PubKey, + }) + if err != nil { + k.logger.Error(fmt.Sprintf("Failed to save key metadata: %v", err)) + return nil, status.Error(codes.Internal, err.Error()) + } + + return &v1.ImportKeyResponse{PublicKeyG1: pubKeyHex, PublicKeyG2: g2PubKey}, nil } func (k *Service) ListKeys( ctx context.Context, req *v1.ListKeysRequest, ) (*v1.ListKeysResponse, error) { - pubKeys, err := k.store.ListKeys(ctx) + keys, err := k.keyMetadataRepo.List(ctx) if err != nil { k.logger.Error(fmt.Sprintf("Failed to list keys: %v", err)) return nil, status.Error(codes.Internal, err.Error()) } + pubKeys := make([]*v1.PublicKey, len(keys)) + for i, key := range keys { + pubKeys[i] = &v1.PublicKey{ + PublicKeyG1: key.PublicKeyG1, + PublicKeyG2: key.PublicKeyG2, + } + } return &v1.ListKeysResponse{PublicKeys: pubKeys}, nil } + +func (k *Service) GetKeyMetadata( + ctx context.Context, + req *v1.GetKeyMetadataRequest, +) (*v1.GetKeyMetadataResponse, error) { + metadata, err := k.keyMetadataRepo.Get(ctx, req.GetPublicKeyG1()) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &v1.GetKeyMetadataResponse{ + PublicKeyG1: metadata.PublicKeyG1, + PublicKeyG2: metadata.PublicKeyG2, + CreatedAt: metadata.CreatedAt.Unix(), + UpdatedAt: metadata.UpdatedAt.Unix(), + }, nil +} diff --git a/internal/services/kms/kms_test.go b/internal/services/kms/kms_test.go index 58dfdd5..314fb2d 100644 --- a/internal/services/kms/kms_test.go +++ b/internal/services/kms/kms_test.go @@ -3,34 +3,45 @@ package kms import ( "context" "encoding/hex" + "os" "testing" + "github.com/Layr-Labs/bn254-keystore-go/keystore" + "github.com/Layr-Labs/bn254-keystore-go/mnemonic" v1 "github.com/Layr-Labs/cerberus-api/pkg/api/v1" "github.com/Layr-Labs/cerberus/internal/common/testutils" "github.com/Layr-Labs/cerberus/internal/configuration" + "github.com/Layr-Labs/cerberus/internal/database/repository/postgres" "github.com/Layr-Labs/cerberus/internal/metrics" "github.com/Layr-Labs/cerberus/internal/store/filesystem" "github.com/stretchr/testify/assert" + + _ "github.com/lib/pq" ) const testPassword = "p@$$w0rd" -func setup() (*Service, *filesystem.FileStore) { +func setup(t *testing.T) (*Service, *filesystem.FileStore, func()) { logger := testutils.GetTestLogger() config := &configuration.Configuration{ KeystoreDir: "testdata/keystore", } fs := filesystem.NewStore(config.KeystoreDir, logger) noopMetrics := metrics.NewNoopRPCMetrics() - service := NewService(config, fs, logger, noopMetrics) + testDB := postgres.SetupTestDB(t) + service := NewService(config, fs, testDB.Repo, logger, noopMetrics) + cleanup := func() { + os.RemoveAll("testdata") + } - return service, fs + return service, fs, cleanup } func TestCreateKey(t *testing.T) { - service, fs := setup() + service, fs, cleanup := setup(t) + defer cleanup() ctx := context.Background() @@ -40,12 +51,12 @@ func TestCreateKey(t *testing.T) { ) assert.NoError(t, err) - storedKeyPair, err := fs.RetrieveKey(ctx, createResp.PublicKey, testPassword) + storedKeyPair, err := fs.RetrieveKey(ctx, createResp.PublicKeyG1, testPassword) assert.NoError(t, err) pubKeyBytes := storedKeyPair.PubKey.Bytes() pubKeyHex := hex.EncodeToString(pubKeyBytes[:]) - assert.Equal(t, createResp.PublicKey, pubKeyHex) + assert.Equal(t, createResp.PublicKeyG1, pubKeyHex) privBytes := storedKeyPair.PrivKey.Bytes() privKeyHex := hex.EncodeToString(privBytes[:]) @@ -53,26 +64,29 @@ func TestCreateKey(t *testing.T) { } func TestImportKey(t *testing.T) { - service, _ := setup() + service, fs, cleanup := setup(t) + defer cleanup() ctx := context.Background() - createResp, err := service.GenerateKeyPair( - ctx, - &v1.GenerateKeyPairRequest{Password: testPassword}, - ) + keyPair, err := keystore.NewKeyPair(testPassword, mnemonic.English) assert.NoError(t, err) importResp, err := service.ImportKey(ctx, &v1.ImportKeyRequest{ - PrivateKey: createResp.PrivateKey, + PrivateKey: hex.EncodeToString(keyPair.PrivateKey), Password: testPassword, }) assert.NoError(t, err) - assert.Equal(t, createResp.PublicKey, importResp.PublicKey) + + storedKeyPair, err := fs.RetrieveKey(ctx, importResp.PublicKeyG1, testPassword) + assert.NoError(t, err) + privKeyBytes := storedKeyPair.PrivKey.Bytes() + assert.Equal(t, hex.EncodeToString(keyPair.PrivateKey), hex.EncodeToString(privKeyBytes[:])) } func TestListKeys(t *testing.T) { - service, fs := setup() + service, fs, cleanup := setup(t) + defer cleanup() ctx := context.Background() @@ -84,9 +98,9 @@ func TestListKeys(t *testing.T) { listResp, err := service.ListKeys(ctx, &v1.ListKeysRequest{}) assert.NoError(t, err) - assert.Contains(t, listResp.PublicKeys, createResp.PublicKey) + assert.Equal(t, listResp.PublicKeys[0].PublicKeyG1, createResp.PublicKeyG1) storedKeys, err := fs.ListKeys(ctx) assert.NoError(t, err) - assert.Contains(t, storedKeys, createResp.PublicKey) + assert.Contains(t, storedKeys, createResp.PublicKeyG1) } diff --git a/internal/services/signing/signing.go b/internal/services/signing/signing.go index 3609e6c..c55fcea 100644 --- a/internal/services/signing/signing.go +++ b/internal/services/signing/signing.go @@ -46,11 +46,11 @@ func (s *Service) SignGeneric( req *v1.SignGenericRequest, ) (*v1.SignGenericResponse, error) { // Take the public key and data from the request - pubKeyHex := common.Trim0x(req.GetPublicKey()) + pubKeyHex := common.Trim0x(req.GetPublicKeyG1()) password := req.GetPassword() if _, ok := s.keyCache[pubKeyHex]; !ok { - s.logger.Info(fmt.Sprintf("In memory cache miss. Retrieving key for %s", req.PublicKey)) + s.logger.Info(fmt.Sprintf("In memory cache miss. Retrieving key for %s", pubKeyHex)) blsKey, err := s.store.RetrieveKey(ctx, pubKeyHex, password) if err != nil { s.logger.Error(fmt.Sprintf("Failed to retrieve key: %v", err)) @@ -70,6 +70,6 @@ func (s *Service) SignGeneric( copy(byteArray[:], data) // Sign the data with the private key sig := blsKey.SignMessage(byteArray) - s.logger.Info(fmt.Sprintf("Signed a message successfully using %s", req.PublicKey)) + s.logger.Info(fmt.Sprintf("Signed a message successfully using %s", pubKeyHex)) return &v1.SignGenericResponse{Signature: sig.Serialize()}, nil } diff --git a/internal/services/signing/signing_test.go b/internal/services/signing/signing_test.go index 8411133..b171c15 100644 --- a/internal/services/signing/signing_test.go +++ b/internal/services/signing/signing_test.go @@ -33,9 +33,9 @@ func TestSigning(t *testing.T) { signingService := NewService(config, store, logger, m) resp, err := signingService.SignGeneric(context.Background(), &v1.SignGenericRequest{ - PublicKey: pubKeyHex, - Data: bytes[:], - Password: password, + PublicKeyG1: pubKeyHex, + Data: bytes[:], + Password: password, }) assert.NoError(t, err) assert.Equal(t, expectedSig, hex.EncodeToString(resp.Signature)) diff --git a/internal/store/awssecretmanager/aws_secret_manager.go b/internal/store/awssecretmanager/aws_secret_manager.go index d03a5b8..5f9141b 100644 --- a/internal/store/awssecretmanager/aws_secret_manager.go +++ b/internal/store/awssecretmanager/aws_secret_manager.go @@ -106,7 +106,7 @@ func (k *Keystore) RetrieveKey( } func (k *Keystore) StoreKey(ctx context.Context, keyPair *keystore.KeyPair) (string, error) { - pubKey, err := keystore.BlsSkToPk(keyPair.PrivateKey, string(curve.BN254)) + pubKey, err := keystore.BlsSkToG1Pk(keyPair.PrivateKey, string(curve.BN254)) if err != nil { return "", err } diff --git a/internal/store/googlesm/google_secret_manager.go b/internal/store/googlesm/google_secret_manager.go index 2cef7af..c247ba7 100644 --- a/internal/store/googlesm/google_secret_manager.go +++ b/internal/store/googlesm/google_secret_manager.go @@ -75,7 +75,7 @@ func (k Keystore) RetrieveKey( } func (k Keystore) StoreKey(ctx context.Context, keyPair *keystore.KeyPair) (string, error) { - pubKey, err := keystore.BlsSkToPk(keyPair.PrivateKey, string(curve.BN254)) + pubKey, err := keystore.BlsSkToG1Pk(keyPair.PrivateKey, string(curve.BN254)) if err != nil { return "", err }