From b27aa83c9fb4f109c464f43dd311a1dad75a2fe0 Mon Sep 17 00:00:00 2001 From: Patrick Deziel Date: Fri, 27 Oct 2023 08:46:52 -0500 Subject: [PATCH 1/2] Google secret manager store --- go.mod | 7 ++ go.sum | 19 ++++ pkg/secrets/client.go | 34 +++++-- pkg/secrets/interface.go | 17 +++- pkg/secrets/mock/errors.go | 7 ++ pkg/secrets/mock/mock.go | 66 ++++++++++++ pkg/secrets/options.go | 11 ++ pkg/store/gcloud/options.go | 13 +++ pkg/store/gcloud/store.go | 108 ++++++++++++++++++++ pkg/store/gcloud/store_test.go | 178 +++++++++++++++++++++++++++++++++ pkg/store/local/store.go | 23 ++--- pkg/store/local/store_test.go | 20 ++-- pkg/store/store.go | 15 ++- 13 files changed, 482 insertions(+), 36 deletions(-) create mode 100644 pkg/secrets/mock/errors.go create mode 100644 pkg/secrets/mock/mock.go create mode 100644 pkg/secrets/options.go create mode 100644 pkg/store/gcloud/options.go create mode 100644 pkg/store/gcloud/store.go create mode 100644 pkg/store/gcloud/store_test.go diff --git a/go.mod b/go.mod index eee059d..8aa9132 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.20 require ( cloud.google.com/go/secretmanager v1.11.2 github.com/gin-gonic/gin v1.9.1 + github.com/googleapis/gax-go v1.0.3 github.com/joho/godotenv v1.4.0 github.com/rotationalio/confire v1.0.0 github.com/rs/zerolog v1.31.0 @@ -19,6 +20,7 @@ require ( cloud.google.com/go/compute v1.19.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.0 // indirect + github.com/BurntSushi/toml v1.1.0 // indirect github.com/bytedance/sonic v1.9.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect @@ -51,15 +53,20 @@ require ( go.opencensus.io v0.24.0 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.14.0 // indirect + golang.org/x/exp v0.0.0-20190221220918-438050ddec5e // indirect + golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 // indirect + golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.6.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc // indirect software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect ) diff --git a/go.sum b/go.sum index 6066265..be6eff7 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ cloud.google.com/go/iam v1.1.0/go.mod h1:nxdHjaKfCr7fNYx/HJMM8LgiMugmveWlkatear5 cloud.google.com/go/secretmanager v1.11.2 h1:52Z78hH8NBWIqbvIG0wi0EoTaAmSx99KIOAmDXIlX0M= cloud.google.com/go/secretmanager v1.11.2/go.mod h1:MQm4t3deoSub7+WNwiC4/tRYgDBHJgJPvswqQVB1Vss= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= @@ -59,6 +61,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU 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= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -91,6 +94,9 @@ github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkj github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.4 h1:uGy6JWR/uMIILU8wbf+OkstIrNiMjGpEIyhx8f6W7s4= github.com/googleapis/enterprise-certificate-proxy v0.2.4/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/gax-go v1.0.3 h1:9dMLqhaibYONnDRcnHdUs9P8Mw64jLlZTYlDe3leBtQ= +github.com/googleapis/gax-go v1.0.3/go.mod h1:QyXYajJFdARxGzjwUfbDFIse7Spkw81SJ4LrBJXtlQ8= +github.com/googleapis/gax-go/v2 v2.0.2/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -98,6 +104,7 @@ github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= @@ -165,10 +172,16 @@ golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190221220918-438050ddec5e h1:dVreTP5bOOWt5GFwwvgTE2iU0TkIqi2x3r0b8qGlp6k= +golang.org/x/exp v0.0.0-20190221220918-438050ddec5e/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 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 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -220,6 +233,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -227,6 +241,8 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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= @@ -246,6 +262,7 @@ google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1: google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -276,7 +293,9 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= diff --git a/pkg/secrets/client.go b/pkg/secrets/client.go index 2679564..fa8fb2a 100644 --- a/pkg/secrets/client.go +++ b/pkg/secrets/client.go @@ -15,7 +15,7 @@ import ( ) // NewClient creates a secret manager client from the configuration. -func NewClient(conf config.SecretsConfig) (_ secretManagerClient, err error) { +func NewClient(conf config.SecretsConfig, opts ...SecretsOption) (_ SecretManagerClient, err error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -23,15 +23,24 @@ func NewClient(conf config.SecretsConfig) (_ secretManagerClient, err error) { parent: "projects/" + conf.Project, } - // Specify credentials path if provided - opts := []option.ClientOption{} - if conf.Credentials != "" { - opts = append(opts, option.WithCredentialsFile(conf.Credentials)) + // Apply provided options + for _, opt := range opts { + if err = opt(s); err != nil { + return nil, err + } } - // Create the client - if s.client, err = secretmanager.NewClient(ctx, opts...); err != nil { - return nil, err + if s.client == nil { + // Specify credentials path if provided + opts := []option.ClientOption{} + if conf.Credentials != "" { + opts = append(opts, option.WithCredentialsFile(conf.Credentials)) + } + + // Create the client + if s.client, err = secretmanager.NewClient(ctx, opts...); err != nil { + return nil, err + } } return s, nil @@ -40,10 +49,10 @@ func NewClient(conf config.SecretsConfig) (_ secretManagerClient, err error) { // GoogleSecrets implements the secret manager interface. type GoogleSecrets struct { parent string - client *secretmanager.Client + client GRPCSecretClient } -var _ secretManagerClient = &GoogleSecrets{} +var _ SecretManagerClient = &GoogleSecrets{} //=========================================================================== // Secret Manager Methods @@ -147,6 +156,11 @@ func (s *GoogleSecrets) GetLatestVersion(ctx context.Context, name string) (_ [] return nil, err } + serr, ok := status.FromError(err) + if ok && serr.Code() == codes.NotFound { + return nil, ErrSecretNotFound + } + // If the error is something else, something went wrong. return nil, err } diff --git a/pkg/secrets/interface.go b/pkg/secrets/interface.go index aca1fde..1859bd5 100644 --- a/pkg/secrets/interface.go +++ b/pkg/secrets/interface.go @@ -2,13 +2,26 @@ package secrets import ( "context" + + "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" + "github.com/googleapis/gax-go" ) -// secretManagerClient describes a high level interface for secret manager clients to +// SecretManagerClient describes a high level interface for secret manager clients to // enable mocking. -type secretManagerClient interface { +type SecretManagerClient interface { GetLatestVersion(ctx context.Context, name string) ([]byte, error) CreateSecret(ctx context.Context, name string) error AddSecretVersion(ctx context.Context, name string, payload []byte) error DeleteSecret(ctx context.Context, name string) error } + +// gRPCSecretClient describes a lower level interface in order to mock the google secret +// manager client. +type GRPCSecretClient interface { + CreateSecret(context.Context, *secretmanagerpb.CreateSecretRequest, ...gax.CallOption) (*secretmanagerpb.Secret, error) + GetSecretVersion(context.Context, *secretmanagerpb.GetSecretVersionRequest, ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) + AddSecretVersion(context.Context, *secretmanagerpb.AddSecretVersionRequest, ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) + AccessSecretVersion(context.Context, *secretmanagerpb.AccessSecretVersionRequest, ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) + DeleteSecret(context.Context, *secretmanagerpb.DeleteSecretRequest, ...gax.CallOption) error +} diff --git a/pkg/secrets/mock/errors.go b/pkg/secrets/mock/errors.go new file mode 100644 index 0000000..44f85cb --- /dev/null +++ b/pkg/secrets/mock/errors.go @@ -0,0 +1,7 @@ +package mock + +import "errors" + +var ( + ErrNotConfigured = errors.New("mock function not configured") +) diff --git a/pkg/secrets/mock/mock.go b/pkg/secrets/mock/mock.go new file mode 100644 index 0000000..9a4180c --- /dev/null +++ b/pkg/secrets/mock/mock.go @@ -0,0 +1,66 @@ +package mock + +import ( + "context" + + "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" + "github.com/googleapis/gax-go" + "github.com/trisacrypto/courier/pkg/secrets" +) + +// New returns a new secrets client mock. The On* functions can be used to configure +// the mock behavior directly. Functions that are not configured will return an error. +func New() (s *SecretManager) { + s = &SecretManager{} + s.Reset() + return s +} + +// Reset resets the state of the mock so all functions return an error. +func (s *SecretManager) Reset() { + s.OnCreateSecret = func(context.Context, *secretmanagerpb.CreateSecretRequest, ...gax.CallOption) (*secretmanagerpb.Secret, error) { + return nil, ErrNotConfigured + } + s.OnGetSecretVersion = func(context.Context, *secretmanagerpb.GetSecretVersionRequest, ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) { + return nil, ErrNotConfigured + } + s.OnAddSecretVersion = func(context.Context, *secretmanagerpb.AddSecretVersionRequest, ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) { + return nil, ErrNotConfigured + } + s.OnAccessSecretVersion = func(context.Context, *secretmanagerpb.AccessSecretVersionRequest, ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) { + return nil, ErrNotConfigured + } + s.OnDeleteSecret = func(context.Context, *secretmanagerpb.DeleteSecretRequest, ...gax.CallOption) error { + return ErrNotConfigured + } +} + +type SecretManager struct { + OnCreateSecret func(context.Context, *secretmanagerpb.CreateSecretRequest, ...gax.CallOption) (*secretmanagerpb.Secret, error) + OnGetSecretVersion func(context.Context, *secretmanagerpb.GetSecretVersionRequest, ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) + OnAddSecretVersion func(context.Context, *secretmanagerpb.AddSecretVersionRequest, ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) + OnAccessSecretVersion func(context.Context, *secretmanagerpb.AccessSecretVersionRequest, ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) + OnDeleteSecret func(context.Context, *secretmanagerpb.DeleteSecretRequest, ...gax.CallOption) error +} + +var _ secrets.GRPCSecretClient = &SecretManager{} + +func (s *SecretManager) CreateSecret(ctx context.Context, req *secretmanagerpb.CreateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error) { + return s.OnCreateSecret(ctx, req, opts...) +} + +func (s *SecretManager) GetSecretVersion(ctx context.Context, req *secretmanagerpb.GetSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) { + return s.OnGetSecretVersion(ctx, req, opts...) +} + +func (s *SecretManager) AddSecretVersion(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) { + return s.OnAddSecretVersion(ctx, req, opts...) +} + +func (s *SecretManager) AccessSecretVersion(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) { + return s.OnAccessSecretVersion(ctx, req, opts...) +} + +func (s *SecretManager) DeleteSecret(ctx context.Context, req *secretmanagerpb.DeleteSecretRequest, opts ...gax.CallOption) error { + return s.OnDeleteSecret(ctx, req, opts...) +} diff --git a/pkg/secrets/options.go b/pkg/secrets/options.go new file mode 100644 index 0000000..b7239a0 --- /dev/null +++ b/pkg/secrets/options.go @@ -0,0 +1,11 @@ +package secrets + +// SecretsOption allows us to configure the secrets client when it is created. +type SecretsOption func(s *GoogleSecrets) error + +func WithGRPCClient(client GRPCSecretClient) SecretsOption { + return func(s *GoogleSecrets) error { + s.client = client + return nil + } +} diff --git a/pkg/store/gcloud/options.go b/pkg/store/gcloud/options.go new file mode 100644 index 0000000..bbefad1 --- /dev/null +++ b/pkg/store/gcloud/options.go @@ -0,0 +1,13 @@ +package gcloud + +import "github.com/trisacrypto/courier/pkg/secrets" + +// StoreOption allows us to configure the store when it is created. +type StoreOption func(s *Store) error + +func WithClient(client secrets.SecretManagerClient) StoreOption { + return func(s *Store) error { + s.client = client + return nil + } +} diff --git a/pkg/store/gcloud/store.go b/pkg/store/gcloud/store.go new file mode 100644 index 0000000..bd104fa --- /dev/null +++ b/pkg/store/gcloud/store.go @@ -0,0 +1,108 @@ +package gcloud + +import ( + "context" + "errors" + + "github.com/trisacrypto/courier/pkg/config" + "github.com/trisacrypto/courier/pkg/secrets" + "github.com/trisacrypto/courier/pkg/store" +) + +// Open the google cloud storage backend. +func Open(conf config.SecretsConfig, opts ...StoreOption) (store *Store, err error) { + store = &Store{} + + // Apply provided options + for _, opt := range opts { + if err = opt(store); err != nil { + return nil, err + } + } + + if store.client == nil { + if store.client, err = secrets.NewClient(conf); err != nil { + return nil, err + } + } + + return store, nil +} + +// Store implements the store.Store interface for google cloud storage using secret +// manager +type Store struct { + client secrets.SecretManagerClient +} + +var _ store.Store = &Store{} + +// Close the google cloud storage backend. +func (s *Store) Close() error { + return nil +} + +//=========================================================================== +// Password Methods +//=========================================================================== + +// GetPassword retrieves a password by id from the google cloud storage backend. +func (s *Store) GetPassword(ctx context.Context, id string) (password []byte, err error) { + if password, err = s.client.GetLatestVersion(ctx, s.fullName(store.PasswordPrefix, id)); err != nil { + if errors.Is(err, secrets.ErrSecretNotFound) { + return nil, store.ErrNotFound + } + + return nil, err + } + + return password, nil +} + +// UpdatePassword updates a password by id in the google cloud storage backend. +func (s *Store) UpdatePassword(ctx context.Context, id string, password []byte) (err error) { + // Ensure the secret exists, this assumes that an error is not returned if the + // secret already exists. + if err = s.client.CreateSecret(ctx, s.fullName(store.PasswordPrefix, id)); err != nil { + return err + } + + return s.client.AddSecretVersion(ctx, s.fullName(store.PasswordPrefix, id), password) +} + +//=========================================================================== +// Certificate Methods +//=========================================================================== + +// GetCertificate retrieves a certificate by id from the google cloud storage backend. +func (s *Store) GetCertificate(ctx context.Context, id string) (cert []byte, err error) { + if cert, err = s.client.GetLatestVersion(ctx, s.fullName(store.CertificatePrefix, id)); err != nil { + if errors.Is(err, secrets.ErrSecretNotFound) { + return nil, store.ErrNotFound + } + + return nil, err + } + + return cert, nil +} + +// UpdateCertificate updates a certificate by id in the google cloud storage backend. +func (s *Store) UpdateCertificate(ctx context.Context, id string, cert []byte) (err error) { + // Ensure the secret exists, this assumes that an error is not returned if the + // secret already exists. + if err = s.client.CreateSecret(ctx, s.fullName(store.CertificatePrefix, id)); err != nil { + return err + } + + return s.client.AddSecretVersion(ctx, s.fullName(store.CertificatePrefix, id), cert) +} + +//=========================================================================== +// Helper methods +//=========================================================================== + +// fullName returns the full name of the secret with the given prefix and id. +func (s *Store) fullName(prefix, id string) string { + return prefix + "-" + id +} diff --git a/pkg/store/gcloud/store_test.go b/pkg/store/gcloud/store_test.go new file mode 100644 index 0000000..a487d14 --- /dev/null +++ b/pkg/store/gcloud/store_test.go @@ -0,0 +1,178 @@ +package gcloud_test + +import ( + "context" + "testing" + + "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" + "github.com/googleapis/gax-go" + "github.com/stretchr/testify/suite" + "github.com/trisacrypto/courier/pkg/config" + "github.com/trisacrypto/courier/pkg/secrets" + "github.com/trisacrypto/courier/pkg/secrets/mock" + "github.com/trisacrypto/courier/pkg/store" + "github.com/trisacrypto/courier/pkg/store/gcloud" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type gcloudStoreTestSuite struct { + suite.Suite + store *gcloud.Store + conf config.SecretsConfig + sm *mock.SecretManager +} + +func (s *gcloudStoreTestSuite) SetupSuite() { + // Open the storage backend using a mock secrets client + var err error + s.sm = mock.New() + s.conf = config.SecretsConfig{ + Enabled: true, + Credentials: "creds.json", + Project: "project", + } + client, err := secrets.NewClient(s.conf, secrets.WithGRPCClient(s.sm)) + s.NoError(err, "could not create mock secrets client") + s.store, err = gcloud.Open(s.conf, gcloud.WithClient(client)) + s.NoError(err, "could not open gcloud storage backend") +} + +func (s *gcloudStoreTestSuite) TearDownSuite() { + // Close the storage backend + s.NoError(s.store.Close(), "could not close gcloud storage backend") +} + +func TestGCloudStore(t *testing.T) { + suite.Run(t, new(gcloudStoreTestSuite)) +} + +func (s *gcloudStoreTestSuite) TestGetPassword() { + require := s.Require() + ctx := context.Background() + + s.Run("HappyPath", func() { + s.sm.OnAccessSecretVersion = func(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) { + return &secretmanagerpb.AccessSecretVersionResponse{ + Payload: &secretmanagerpb.SecretPayload{ + Data: []byte("password"), + }, + }, nil + } + defer s.sm.Reset() + password, err := s.store.GetPassword(ctx, "does-exist") + require.NoError(err, "should be able to get a password") + require.Equal([]byte("password"), password, "wrong password returned") + }) + + s.Run("NotFound", func() { + s.sm.OnAccessSecretVersion = func(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) { + return nil, status.Error(codes.NotFound, "not found") + } + defer s.sm.Reset() + _, err := s.store.GetPassword(ctx, "does-not-exist") + require.ErrorIs(err, store.ErrNotFound, "should return error if password does not exist") + }) + + s.Run("Error", func() { + statusErr := status.Error(codes.Internal, "internal error") + s.sm.OnAccessSecretVersion = func(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) { + return nil, statusErr + } + defer s.sm.Reset() + _, err := s.store.GetPassword(ctx, "does-not-exist") + require.EqualError(err, statusErr.Error(), "should return error if there was a gRPC error") + }) +} + +func (s *gcloudStoreTestSuite) TestUpdatePassword() { + requre := s.Require() + ctx := context.Background() + + s.Run("HappyPath", func() { + s.sm.OnCreateSecret = func(ctx context.Context, req *secretmanagerpb.CreateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error) { + return &secretmanagerpb.Secret{}, nil + } + s.sm.OnAddSecretVersion = func(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) { + return &secretmanagerpb.SecretVersion{}, nil + } + defer s.sm.Reset() + err := s.store.UpdatePassword(ctx, "password_id", []byte("password")) + requre.NoError(err, "should be able to create a password") + }) + + s.Run("Error", func() { + statusErr := status.Error(codes.Internal, "internal error") + s.sm.OnCreateSecret = func(ctx context.Context, req *secretmanagerpb.CreateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error) { + return nil, statusErr + } + defer s.sm.Reset() + err := s.store.UpdatePassword(ctx, "password_id", []byte("password")) + requre.EqualError(err, statusErr.Error(), "should return error if there was a gRPC error") + }) +} + +func (s *gcloudStoreTestSuite) TestGetCertificate() { + require := s.Require() + ctx := context.Background() + + s.Run("HappyPath", func() { + s.sm.OnAccessSecretVersion = func(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) { + return &secretmanagerpb.AccessSecretVersionResponse{ + Payload: &secretmanagerpb.SecretPayload{ + Data: []byte("cert"), + }, + }, nil + } + defer s.sm.Reset() + cert, err := s.store.GetCertificate(ctx, "does-exist") + require.NoError(err, "should be able to get a password") + require.Equal([]byte("cert"), cert, "wrong cert returned") + }) + + s.Run("NotFound", func() { + s.sm.OnAccessSecretVersion = func(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) { + return nil, status.Error(codes.NotFound, "not found") + } + defer s.sm.Reset() + _, err := s.store.GetCertificate(ctx, "does-not-exist") + require.ErrorIs(err, store.ErrNotFound, "should return error if cert does not exist") + }) + + s.Run("Error", func() { + statusErr := status.Error(codes.Internal, "internal error") + s.sm.OnAccessSecretVersion = func(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error) { + return nil, statusErr + } + defer s.sm.Reset() + _, err := s.store.GetCertificate(ctx, "does-not-exist") + require.EqualError(err, statusErr.Error(), "should return error if there was a gRPC error") + }) +} + +func (s *gcloudStoreTestSuite) TestUpdateCertificate() { + requre := s.Require() + ctx := context.Background() + + s.Run("HappyPath", func() { + s.sm.OnCreateSecret = func(ctx context.Context, req *secretmanagerpb.CreateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error) { + return &secretmanagerpb.Secret{}, nil + } + s.sm.OnAddSecretVersion = func(ctx context.Context, req *secretmanagerpb.AddSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.SecretVersion, error) { + return &secretmanagerpb.SecretVersion{}, nil + } + defer s.sm.Reset() + err := s.store.UpdateCertificate(ctx, "cert_id", []byte("cert")) + requre.NoError(err, "should be able to create a certificate") + }) + + s.Run("Error", func() { + statusErr := status.Error(codes.Internal, "internal error") + s.sm.OnCreateSecret = func(ctx context.Context, req *secretmanagerpb.CreateSecretRequest, opts ...gax.CallOption) (*secretmanagerpb.Secret, error) { + return nil, statusErr + } + defer s.sm.Reset() + err := s.store.UpdateCertificate(ctx, "cert_id", []byte("cert")) + requre.EqualError(err, statusErr.Error(), "should return error if there was a gRPC error") + }) +} diff --git a/pkg/store/local/store.go b/pkg/store/local/store.go index 33f8020..6d4374c 100644 --- a/pkg/store/local/store.go +++ b/pkg/store/local/store.go @@ -2,6 +2,7 @@ package local import ( "archive/zip" + "context" "io" "os" "path/filepath" @@ -12,10 +13,8 @@ import ( ) const ( - passwordPrefix = "pkcs12" - passwordFile = "pkcs12.password" - certificatePrefix = "certificate" - certificateFile = "certificate" + passwordFile = "pkcs12.password" + certificateFile = "certificate" ) // Open the local storage backend. @@ -50,18 +49,18 @@ func (s *Store) Close() error { //=========================================================================== // GetPassword retrieves a password by id from the local storage backend. -func (s *Store) GetPassword(id string) (password []byte, err error) { +func (s *Store) GetPassword(ctx context.Context, id string) (password []byte, err error) { s.RLock() defer s.RUnlock() - return s.load(s.fullPath(passwordPrefix, id)) + return s.load(s.fullPath(store.PasswordPrefix, id)) } // UpdatePassword updates a password by id in the local storage backend. If the // password does not exist, it is created. Otherwise, it is overwritten. -func (s *Store) UpdatePassword(id string, password []byte) (err error) { +func (s *Store) UpdatePassword(ctx context.Context, id string, password []byte) (err error) { s.Lock() defer s.Unlock() - return s.store(s.fullPath(passwordPrefix, id), passwordFile, password) + return s.store(s.fullPath(store.PasswordPrefix, id), passwordFile, password) } //=========================================================================== @@ -69,17 +68,17 @@ func (s *Store) UpdatePassword(id string, password []byte) (err error) { //=========================================================================== // GetCertificate retrieves a certificate by id from the local storage backend. -func (s *Store) GetCertificate(name string) (cert []byte, err error) { +func (s *Store) GetCertificate(ctx context.Context, name string) (cert []byte, err error) { s.RLock() defer s.RUnlock() - return s.load(s.fullPath(certificatePrefix, name)) + return s.load(s.fullPath(store.CertificatePrefix, name)) } // UpdateCertificate updates a certificate in the local storage backend. -func (s *Store) UpdateCertificate(name string, cert []byte) (err error) { +func (s *Store) UpdateCertificate(ctx context.Context, name string, cert []byte) (err error) { s.Lock() defer s.Unlock() - return s.store(s.fullPath(certificatePrefix, name), certificateFile, cert) + return s.store(s.fullPath(store.CertificatePrefix, name), certificateFile, cert) } //=========================================================================== diff --git a/pkg/store/local/store_test.go b/pkg/store/local/store_test.go index 544c855..3a0912e 100644 --- a/pkg/store/local/store_test.go +++ b/pkg/store/local/store_test.go @@ -1,6 +1,7 @@ package local_test import ( + "context" "os" "testing" @@ -20,10 +21,11 @@ func (s *localStoreTestSuite) SetupSuite() { // Open the storage backend in a temporary directory var err error path := s.T().TempDir() - s.store, err = local.Open(config.LocalStorageConfig{ + s.conf = config.LocalStorageConfig{ Enabled: true, Path: path, - }) + } + s.store, err = local.Open(s.conf) s.NoError(err, "could not open local storage backend") } @@ -39,36 +41,38 @@ func TestLocalStore(t *testing.T) { func (s *localStoreTestSuite) TestPasswordStore() { require := s.Require() + ctx := context.Background() // Try to get a password that does not exist - _, err := s.store.GetPassword("does-not-exist") + _, err := s.store.GetPassword(ctx, "does-not-exist") require.ErrorIs(err, store.ErrNotFound, "should return error if password does not exist") // Create a password password := []byte("password") - err = s.store.UpdatePassword("password_id", password) + err = s.store.UpdatePassword(ctx, "password_id", password) require.NoError(err, "should be able to create a password") // Get the password - actual, err := s.store.GetPassword("password_id") + actual, err := s.store.GetPassword(ctx, "password_id") require.NoError(err, "should be able to get a password") require.Equal(password, actual, "wrong password returned") } func (s *localStoreTestSuite) TestCertificateStore() { require := s.Require() + ctx := context.Background() // Try to get a certificate that does not exist - _, err := s.store.GetCertificate("does-not-exist") + _, err := s.store.GetCertificate(ctx, "does-not-exist") require.ErrorIs(err, store.ErrNotFound, "should return error if certificate does not exist") // Create a certificate cert := []byte("certificate") - err = s.store.UpdateCertificate("certificate_id", cert) + err = s.store.UpdateCertificate(ctx, "certificate_id", cert) require.NoError(err, "should be able to create a certificate") // Get the certificate - actual, err := s.store.GetCertificate("certificate_id") + actual, err := s.store.GetCertificate(ctx, "certificate_id") require.NoError(err, "should be able to get a certificate") require.Equal(cert, actual, "wrong certificate returned") } diff --git a/pkg/store/store.go b/pkg/store/store.go index b75bf4a..7b49432 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -1,5 +1,12 @@ package store +import "context" + +const ( + PasswordPrefix = "pkcs12" + CertificatePrefix = "certificate" +) + // Store is a generic interface for storing and retrieving data. type Store interface { Close() error @@ -9,12 +16,12 @@ type Store interface { // PasswordStore is a generic interface for storing and retrieving passwords. type PasswordStore interface { - GetPassword(name string) ([]byte, error) - UpdatePassword(name string, password []byte) error + GetPassword(ctx context.Context, name string) ([]byte, error) + UpdatePassword(ctx context.Context, name string, password []byte) error } // CertificateStore is a generic interface for storing and retrieving certificates. type CertificateStore interface { - GetCertificate(name string) ([]byte, error) - UpdateCertificate(name string, cert []byte) error + GetCertificate(ctx context.Context, name string) ([]byte, error) + UpdateCertificate(ctx context.Context, name string, cert []byte) error } From 764dee37e53d4ecfed8c4cc24c25a83c1b44a8bb Mon Sep 17 00:00:00 2001 From: Patrick Deziel Date: Fri, 27 Oct 2023 09:14:05 -0500 Subject: [PATCH 2/2] Fix merge conflicts --- pkg/certs.go | 2 +- pkg/certs_test.go | 4 ++-- pkg/store/mock/mock.go | 38 +++++++++++++++++++++----------------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/pkg/certs.go b/pkg/certs.go index 6dde361..77a9fe4 100644 --- a/pkg/certs.go +++ b/pkg/certs.go @@ -29,7 +29,7 @@ func (s *Server) StoreCertificatePassword(c *gin.Context) { } // Store the password - if err = s.store.UpdatePassword(c.Param("id"), []byte(req.Password)); err != nil { + if err = s.store.UpdatePassword(c.Request.Context(), c.Param("id"), []byte(req.Password)); err != nil { c.JSON(http.StatusInternalServerError, api.ErrorResponse(err)) return } diff --git a/pkg/certs_test.go b/pkg/certs_test.go index 13f1c49..3b4e0d5 100644 --- a/pkg/certs_test.go +++ b/pkg/certs_test.go @@ -17,7 +17,7 @@ func (s *courierTestSuite) TestStoreCertificatePassword() { ID: "certID", Password: "password", } - s.store.OnUpdatePassword = func(name string, password []byte) error { + s.store.OnUpdatePassword = func(ctx context.Context, name string, password []byte) error { require.Equal(req.ID, name, "wrong password name passed to store") require.Equal([]byte(req.Password), password, "wrong password passed to store") return nil @@ -38,7 +38,7 @@ func (s *courierTestSuite) TestStoreCertificatePassword() { }) s.Run("StoreError", func() { - s.store.OnUpdatePassword = func(name string, password []byte) error { + s.store.OnUpdatePassword = func(ctx context.Context, name string, password []byte) error { return errors.New("internal store error") } defer s.store.Reset() diff --git a/pkg/store/mock/mock.go b/pkg/store/mock/mock.go index 58f3a73..9318938 100644 --- a/pkg/store/mock/mock.go +++ b/pkg/store/mock/mock.go @@ -1,6 +1,10 @@ package mock -import "github.com/trisacrypto/courier/pkg/store" +import ( + "context" + + "github.com/trisacrypto/courier/pkg/store" +) // New returns a new mock store. The On* functions can be used to configure the mock // behavior directly. Functions that are not configured will return an error. @@ -12,29 +16,29 @@ func New() (s *Store) { // Reset resets the state of the mock so all functions return an error. func (s *Store) Reset() { - s.OnGetPassword = func(name string) ([]byte, error) { + s.OnGetPassword = func(ctx context.Context, name string) ([]byte, error) { return nil, ErrNotConfigured } - s.OnUpdatePassword = func(name string, password []byte) error { + s.OnUpdatePassword = func(ctx context.Context, name string, password []byte) error { return ErrNotConfigured } - s.OnGetCertificate = func(name string) ([]byte, error) { + s.OnGetCertificate = func(ctx context.Context, name string) ([]byte, error) { return nil, ErrNotConfigured } - s.OnUpdateCertificate = func(name string, cert []byte) error { + s.OnUpdateCertificate = func(ctx context.Context, name string, cert []byte) error { return ErrNotConfigured } } // Store implements the store.Store interface for mocking the store in tests. type Store struct { - OnGetPassword func(name string) ([]byte, error) - OnUpdatePassword func(name string, password []byte) error - OnGetCertificate func(name string) ([]byte, error) - OnUpdateCertificate func(name string, cert []byte) error + OnGetPassword func(ctx context.Context, name string) ([]byte, error) + OnUpdatePassword func(ctx context.Context, name string, password []byte) error + OnGetCertificate func(ctx context.Context, name string) ([]byte, error) + OnUpdateCertificate func(ctx context.Context, name string, cert []byte) error } var _ store.Store = &Store{} @@ -43,18 +47,18 @@ func (s *Store) Close() error { return nil } -func (s *Store) GetPassword(name string) ([]byte, error) { - return s.OnGetPassword(name) +func (s *Store) GetPassword(ctx context.Context, name string) ([]byte, error) { + return s.OnGetPassword(ctx, name) } -func (s *Store) UpdatePassword(name string, password []byte) error { - return s.OnUpdatePassword(name, password) +func (s *Store) UpdatePassword(ctx context.Context, name string, password []byte) error { + return s.OnUpdatePassword(ctx, name, password) } -func (s *Store) GetCertificate(name string) ([]byte, error) { - return s.OnGetCertificate(name) +func (s *Store) GetCertificate(ctx context.Context, name string) ([]byte, error) { + return s.OnGetCertificate(ctx, name) } -func (s *Store) UpdateCertificate(name string, cert []byte) error { - return s.OnUpdateCertificate(name, cert) +func (s *Store) UpdateCertificate(ctx context.Context, name string, cert []byte) error { + return s.OnUpdateCertificate(ctx, name, cert) }