From 70179358887c514a917d5a6fdcc82cf25f26cc74 Mon Sep 17 00:00:00 2001 From: zamysel Date: Thu, 12 Aug 2021 20:05:02 +0300 Subject: [PATCH] Add support for Yandex Lockbox --- .../v1alpha1/secretstore_types.go | 4 + .../secretstore_yandexlockbox_types.go | 31 +++ .../v1alpha1/zz_generated.deepcopy.go | 37 ++++ ...ternal-secrets.io_clustersecretstores.yaml | 32 +++ .../external-secrets.io_secretstores.yaml | 32 +++ go.mod | 2 + go.sum | 10 + .../externalsecret_controller.go | 2 +- .../aws/parameterstore/parameterstore.go | 2 +- .../aws/secretsmanager/secretsmanager.go | 2 +- pkg/provider/azure/keyvault/keyvault.go | 2 +- pkg/provider/fake/fake.go | 2 +- .../gcp/secretmanager/secretsmanager.go | 2 +- pkg/provider/ibm/provider.go | 2 +- pkg/provider/provider.go | 2 +- pkg/provider/register/register.go | 1 + pkg/provider/schema/schema_test.go | 2 +- pkg/provider/vault/vault.go | 2 +- pkg/provider/yandex/lockbox/lockbox.go | 206 ++++++++++++++++++ 19 files changed, 365 insertions(+), 10 deletions(-) create mode 100644 apis/externalsecrets/v1alpha1/secretstore_yandexlockbox_types.go create mode 100644 pkg/provider/yandex/lockbox/lockbox.go diff --git a/apis/externalsecrets/v1alpha1/secretstore_types.go b/apis/externalsecrets/v1alpha1/secretstore_types.go index 607f4fbf5..72bf8193b 100644 --- a/apis/externalsecrets/v1alpha1/secretstore_types.go +++ b/apis/externalsecrets/v1alpha1/secretstore_types.go @@ -53,6 +53,10 @@ type SecretStoreProvider struct { // IBM configures this store to sync secrets using IBM Cloud provider // +optional IBM *IBMProvider `json:"ibm,omitempty"` + + // YandexLockbox configures this store to sync secrets using Yandex Lockbox provider + // +optional + YandexLockbox *YandexLockboxProvider `json:"yandexlockbox,omitempty"` } type SecretStoreConditionType string diff --git a/apis/externalsecrets/v1alpha1/secretstore_yandexlockbox_types.go b/apis/externalsecrets/v1alpha1/secretstore_yandexlockbox_types.go new file mode 100644 index 000000000..27b944d0c --- /dev/null +++ b/apis/externalsecrets/v1alpha1/secretstore_yandexlockbox_types.go @@ -0,0 +1,31 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + esmeta "github.com/external-secrets/external-secrets/apis/meta/v1" +) + +type YandexLockboxAuth struct { + // The AuthorizedKey is used for authentication + // +optional + AuthorizedKey esmeta.SecretKeySelector `json:"authorizedKeySecretRef,omitempty"` +} + +// YandexLockboxProvider Configures a store to sync secrets using the Yandex Lockbox provider. +type YandexLockboxProvider struct { + // Auth defines the information necessary to authenticate against Yandex Lockbox + Auth YandexLockboxAuth `json:"auth"` +} diff --git a/apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go b/apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go index 4a0e8d6b7..59f9d6ce7 100644 --- a/apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go +++ b/apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go @@ -644,6 +644,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) { *out = new(IBMProvider) (*in).DeepCopyInto(*out) } + if in.YandexLockbox != nil { + in, out := &in.YandexLockbox, &out.YandexLockbox + *out = new(YandexLockboxProvider) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider. @@ -949,3 +954,35 @@ func (in *VaultProvider) DeepCopy() *VaultProvider { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *YandexLockboxAuth) DeepCopyInto(out *YandexLockboxAuth) { + *out = *in + in.AuthorizedKey.DeepCopyInto(&out.AuthorizedKey) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new YandexLockboxAuth. +func (in *YandexLockboxAuth) DeepCopy() *YandexLockboxAuth { + if in == nil { + return nil + } + out := new(YandexLockboxAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *YandexLockboxProvider) DeepCopyInto(out *YandexLockboxProvider) { + *out = *in + in.Auth.DeepCopyInto(&out.Auth) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new YandexLockboxProvider. +func (in *YandexLockboxProvider) DeepCopy() *YandexLockboxProvider { + if in == nil { + return nil + } + out := new(YandexLockboxProvider) + in.DeepCopyInto(out) + return out +} diff --git a/deploy/crds/external-secrets.io_clustersecretstores.yaml b/deploy/crds/external-secrets.io_clustersecretstores.yaml index eac6a56fc..595ebe933 100644 --- a/deploy/crds/external-secrets.io_clustersecretstores.yaml +++ b/deploy/crds/external-secrets.io_clustersecretstores.yaml @@ -608,6 +608,38 @@ spec: - path - server type: object + yandexlockbox: + description: YandexLockbox configures this store to sync secrets + using Yandex Lockbox provider + properties: + auth: + description: Auth defines the information necessary to authenticate + against Yandex Lockbox + properties: + authorizedKeySecretRef: + description: The AuthorizedKey is used for authentication + properties: + key: + description: The key of the entry in the Secret resource's + `data` field to be used. Some instances of this + field may be defaulted, in others it may be required. + type: string + name: + description: The name of the Secret resource being + referred to. + type: string + namespace: + description: Namespace of the resource being referred + to. Ignored if referent is not cluster-scoped. cluster-scoped + defaults to the namespace of the referent. + type: string + required: + - name + type: object + type: object + required: + - auth + type: object type: object required: - provider diff --git a/deploy/crds/external-secrets.io_secretstores.yaml b/deploy/crds/external-secrets.io_secretstores.yaml index b84336b5a..b9cb7cd25 100644 --- a/deploy/crds/external-secrets.io_secretstores.yaml +++ b/deploy/crds/external-secrets.io_secretstores.yaml @@ -608,6 +608,38 @@ spec: - path - server type: object + yandexlockbox: + description: YandexLockbox configures this store to sync secrets + using Yandex Lockbox provider + properties: + auth: + description: Auth defines the information necessary to authenticate + against Yandex Lockbox + properties: + authorizedKeySecretRef: + description: The AuthorizedKey is used for authentication + properties: + key: + description: The key of the entry in the Secret resource's + `data` field to be used. Some instances of this + field may be defaulted, in others it may be required. + type: string + name: + description: The name of the Secret resource being + referred to. + type: string + namespace: + description: Namespace of the resource being referred + to. Ignored if referent is not cluster-scoped. cluster-scoped + defaults to the namespace of the referent. + type: string + required: + - name + type: object + type: object + required: + - auth + type: object type: object required: - provider diff --git a/go.mod b/go.mod index b4c236d10..fa8486e40 100644 --- a/go.mod +++ b/go.mod @@ -64,6 +64,8 @@ require ( github.com/spf13/cobra v1.1.3 // indirect github.com/stretchr/testify v1.7.0 github.com/tidwall/gjson v1.7.5 + github.com/yandex-cloud/go-genproto v0.0.0-20210809082946-a97da516c588 + github.com/yandex-cloud/go-sdk v0.0.0-20210809100642-c13c40a429fa github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a go.uber.org/zap v1.17.0 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 diff --git a/go.sum b/go.sum index e96696710..2c725432b 100644 --- a/go.sum +++ b/go.sum @@ -98,6 +98,7 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -135,6 +136,7 @@ github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 h1:sgNeV1VRMDzs6rzyPpxyM0jp317hnwiq58Filgag2xw= github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0/go.mod h1:J70FGZSbzsjecRTiTzER+3f1KZLNaXkuv+yeFTKoxM8= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= @@ -166,6 +168,7 @@ github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -479,6 +482,7 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= @@ -645,6 +649,10 @@ github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+ github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yandex-cloud/go-genproto v0.0.0-20210809082946-a97da516c588 h1:Lbz8X5Nre0Lg5QgCblmo0AhScWxeN3CVnX+mZ5Hxksk= +github.com/yandex-cloud/go-genproto v0.0.0-20210809082946-a97da516c588/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= +github.com/yandex-cloud/go-sdk v0.0.0-20210809100642-c13c40a429fa h1:Un1jWl/YWbK1179aMbsEZ6uLlDjjBAjL8KXldho1Umo= +github.com/yandex-cloud/go-sdk v0.0.0-20210809100642-c13c40a429fa/go.mod h1:UkgAKjyQo+Pylt2HTYz/G0PgnxmKOJ9IX/3XiRYQ9Ns= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= @@ -763,6 +771,7 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -996,6 +1005,7 @@ google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200323114720-3f67cca34472/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= diff --git a/pkg/controllers/externalsecret/externalsecret_controller.go b/pkg/controllers/externalsecret/externalsecret_controller.go index 0d7ce2c7b..654938af8 100644 --- a/pkg/controllers/externalsecret/externalsecret_controller.go +++ b/pkg/controllers/externalsecret/externalsecret_controller.go @@ -147,7 +147,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu } defer func() { - err = secretClient.Close() + err = secretClient.Close(ctx) if err != nil { log.Error(err, errCloseStoreClient) } diff --git a/pkg/provider/aws/parameterstore/parameterstore.go b/pkg/provider/aws/parameterstore/parameterstore.go index 32a18df79..28443149d 100644 --- a/pkg/provider/aws/parameterstore/parameterstore.go +++ b/pkg/provider/aws/parameterstore/parameterstore.go @@ -90,6 +90,6 @@ func (pm *ParameterStore) GetSecretMap(ctx context.Context, ref esv1alpha1.Exter return secretData, nil } -func (pm *ParameterStore) Close() error { +func (pm *ParameterStore) Close(ctx context.Context) error { return nil } diff --git a/pkg/provider/aws/secretsmanager/secretsmanager.go b/pkg/provider/aws/secretsmanager/secretsmanager.go index 7355345ef..57f07d33c 100644 --- a/pkg/provider/aws/secretsmanager/secretsmanager.go +++ b/pkg/provider/aws/secretsmanager/secretsmanager.go @@ -103,6 +103,6 @@ func (sm *SecretsManager) GetSecretMap(ctx context.Context, ref esv1alpha1.Exter return secretData, nil } -func (sm *SecretsManager) Close() error { +func (sm *SecretsManager) Close(ctx context.Context) error { return nil } diff --git a/pkg/provider/azure/keyvault/keyvault.go b/pkg/provider/azure/keyvault/keyvault.go index f4358b74d..4a6a0c49a 100644 --- a/pkg/provider/azure/keyvault/keyvault.go +++ b/pkg/provider/azure/keyvault/keyvault.go @@ -227,7 +227,7 @@ func (a *Azure) secretKeyRef(ctx context.Context, namespace string, secretRef sm return value, nil } -func (a *Azure) Close() error { +func (a *Azure) Close(ctx context.Context) error { return nil } diff --git a/pkg/provider/fake/fake.go b/pkg/provider/fake/fake.go index 7854876c3..bbf66fd4b 100644 --- a/pkg/provider/fake/fake.go +++ b/pkg/provider/fake/fake.go @@ -74,7 +74,7 @@ func (v *Client) WithGetSecret(secData []byte, err error) *Client { func (v *Client) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) { return v.GetSecretMapFn(ctx, ref) } -func (v *Client) Close() error { +func (v *Client) Close(ctx context.Context) error { return nil } diff --git a/pkg/provider/gcp/secretmanager/secretsmanager.go b/pkg/provider/gcp/secretmanager/secretsmanager.go index 576b505af..7bd559b04 100644 --- a/pkg/provider/gcp/secretmanager/secretsmanager.go +++ b/pkg/provider/gcp/secretmanager/secretsmanager.go @@ -199,7 +199,7 @@ func (sm *ProviderGCP) GetSecretMap(ctx context.Context, ref esv1alpha1.External return secretData, nil } -func (sm *ProviderGCP) Close() error { +func (sm *ProviderGCP) Close(ctx context.Context) error { err := sm.SecretManagerClient.Close() if err != nil { return fmt.Errorf(errClientClose, err) diff --git a/pkg/provider/ibm/provider.go b/pkg/provider/ibm/provider.go index 14aeec052..fa64e6c55 100644 --- a/pkg/provider/ibm/provider.go +++ b/pkg/provider/ibm/provider.go @@ -289,7 +289,7 @@ func (ibm *providerIBM) GetSecretMap(ctx context.Context, ref esv1alpha1.Externa } } -func (ibm *providerIBM) Close() error { +func (ibm *providerIBM) Close(ctx context.Context) error { return nil } diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 55254f243..0a3ac201a 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -35,5 +35,5 @@ type SecretsClient interface { // GetSecretMap returns multiple k/v pairs from the provider GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) - Close() error + Close(ctx context.Context) error } diff --git a/pkg/provider/register/register.go b/pkg/provider/register/register.go index c71274795..24421660f 100644 --- a/pkg/provider/register/register.go +++ b/pkg/provider/register/register.go @@ -22,4 +22,5 @@ import ( _ "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager" _ "github.com/external-secrets/external-secrets/pkg/provider/ibm" _ "github.com/external-secrets/external-secrets/pkg/provider/vault" + _ "github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox" ) diff --git a/pkg/provider/schema/schema_test.go b/pkg/provider/schema/schema_test.go index 588cc7746..c3023fb14 100644 --- a/pkg/provider/schema/schema_test.go +++ b/pkg/provider/schema/schema_test.go @@ -41,7 +41,7 @@ func (p *PP) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretData return map[string][]byte{}, nil } -func (p *PP) Close() error { +func (p *PP) Close(ctx context.Context) error { return nil } diff --git a/pkg/provider/vault/vault.go b/pkg/provider/vault/vault.go index 398b78f38..ee6ab7dbf 100644 --- a/pkg/provider/vault/vault.go +++ b/pkg/provider/vault/vault.go @@ -155,7 +155,7 @@ func (v *client) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecret return v.readSecret(ctx, ref.Key, ref.Version) } -func (v *client) Close() error { +func (v *client) Close(ctx context.Context) error { return nil } diff --git a/pkg/provider/yandex/lockbox/lockbox.go b/pkg/provider/yandex/lockbox/lockbox.go new file mode 100644 index 000000000..0b813d89d --- /dev/null +++ b/pkg/provider/yandex/lockbox/lockbox.go @@ -0,0 +1,206 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package lockbox + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/yandex-cloud/go-genproto/yandex/cloud/lockbox/v1" + ycsdk "github.com/yandex-cloud/go-sdk" + "github.com/yandex-cloud/go-sdk/iamkey" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + kclient "sigs.k8s.io/controller-runtime/pkg/client" + + esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1" + "github.com/external-secrets/external-secrets/pkg/provider" + "github.com/external-secrets/external-secrets/pkg/provider/schema" + "github.com/external-secrets/external-secrets/pkg/utils" +) + +// providerLockbox is a provider for Yandex Lockbox. +type providerLockbox struct { + sdk *ycsdk.SDK +} + +// NewClient constructs a Yandex Lockbox Provider. +func (p *providerLockbox) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) { + storeSpec := store.GetSpec() + if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.YandexLockbox == nil { + return nil, fmt.Errorf("received invalid Yandex Lockbox SecretStore resource") + } + storeSpecYandexLockbox := storeSpec.Provider.YandexLockbox + + authorizedKeySecretName := storeSpecYandexLockbox.Auth.AuthorizedKey.Name + if authorizedKeySecretName == "" { + return nil, fmt.Errorf("invalid Yandex Lockbox SecretStore resource: missing AuthorizedKey Name") + } + objectKey := types.NamespacedName{ + Name: authorizedKeySecretName, + Namespace: namespace, + } + + // only ClusterStore is allowed to set namespace (and then it's required) + if store.GetObjectKind().GroupVersionKind().Kind == esv1alpha1.ClusterSecretStoreKind { + if storeSpecYandexLockbox.Auth.AuthorizedKey.Namespace == nil { + return nil, fmt.Errorf("invalid ClusterSecretStore: missing AuthorizedKey Namespace") + } + objectKey.Namespace = *storeSpecYandexLockbox.Auth.AuthorizedKey.Namespace + } + + authorizedKeySecret := &corev1.Secret{} + err := kube.Get(ctx, objectKey, authorizedKeySecret) + if err != nil { + return nil, fmt.Errorf("could not fetch AuthorizedKey secret: %w", err) + } + + authorizedKeySecretData := authorizedKeySecret.Data[storeSpecYandexLockbox.Auth.AuthorizedKey.Key] + if (authorizedKeySecretData == nil) || (len(authorizedKeySecretData) == 0) { + return nil, fmt.Errorf("missing AuthorizedKey") + } + + var authorizedKey iamkey.Key + err = json.Unmarshal(authorizedKeySecretData, &authorizedKey) + if err != nil { + return nil, fmt.Errorf("unable to unmarshal authorized key: %w", err) + } + + credentials, err := ycsdk.ServiceAccountKey(&authorizedKey) + if err != nil { + return nil, fmt.Errorf("failed to create credentials: %w", err) + } + + sdk, err := ycsdk.Build(ctx, ycsdk.Config{ + Credentials: credentials, + }) + if err != nil { + return nil, fmt.Errorf("failed to create Yandex.Cloud SDK: %w", err) + } + + p.sdk = sdk + + return p, nil +} + +// GetSecret returns a single secret from the provider. +func (p *providerLockbox) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) { + entries, err := requestPayload(ctx, p.sdk, ref.Key, ref.Version) + if err != nil { + return nil, err + } + + if ref.Property == "" { + keyToValue := make(map[string]interface{}, len(entries)) + for _, entry := range entries { + value, err := getValueAsIs(entry) + if err != nil { + return nil, err + } + keyToValue[entry.Key] = value + } + out, err := json.Marshal(keyToValue) + if err != nil { + return nil, fmt.Errorf("failed to marshal secret: %w", err) + } + return out, nil + } + + entry, err := findEntryByKey(entries, ref.Property) + if err != nil { + return nil, err + } + return getValueAsBinary(entry) +} + +// GetSecret returns a single secret from the provider. +func requestPayload(ctx context.Context, sdk *ycsdk.SDK, secretID, versionID string) ([]*lockbox.Payload_Entry, error) { + if utils.IsNil(sdk) { + return nil, fmt.Errorf("provider Yandex Lockbox is not initialized") + } + + payload, err := sdk.LockboxPayload().Payload().Get(ctx, &lockbox.GetPayloadRequest{ + SecretId: secretID, + VersionId: versionID, + }) + if err != nil { + return nil, fmt.Errorf("unable to get secret payload: %w", err) + } + + return payload.Entries, nil +} + +// GetSecretMap returns multiple k/v pairs from the provider. +func (p *providerLockbox) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) { + entries, err := requestPayload(ctx, p.sdk, ref.Key, ref.Version) + if err != nil { + return nil, err + } + + secretMap := make(map[string][]byte, len(entries)) + for _, entry := range entries { + value, err := getValueAsBinary(entry) + if err != nil { + return nil, err + } + secretMap[entry.Key] = value + } + return secretMap, nil +} + +func (p *providerLockbox) Close(ctx context.Context) error { + err := p.sdk.Shutdown(ctx) + if err != nil { + return fmt.Errorf("failed to shutdown Yandex.Cloud SDK: %w", err) + } + return nil +} + +func getValueAsIs(entry *lockbox.Payload_Entry) (interface{}, error) { + switch entry.Value.(type) { + case *lockbox.Payload_Entry_TextValue: + return entry.GetTextValue(), nil + case *lockbox.Payload_Entry_BinaryValue: + return entry.GetBinaryValue(), nil + default: + return nil, fmt.Errorf("unsupported payload value type, key: %v", entry.Key) + } +} + +func getValueAsBinary(entry *lockbox.Payload_Entry) ([]byte, error) { + switch entry.Value.(type) { + case *lockbox.Payload_Entry_TextValue: + return []byte(entry.GetTextValue()), nil + case *lockbox.Payload_Entry_BinaryValue: + return entry.GetBinaryValue(), nil + default: + return nil, fmt.Errorf("unsupported payload value type, key: %v", entry.Key) + } +} + +func findEntryByKey(entries []*lockbox.Payload_Entry, key string) (*lockbox.Payload_Entry, error) { + for i := range entries { + if entries[i].Key == key { + return entries[i], nil + } + } + return nil, fmt.Errorf("payload entry with key '%s' not found", key) +} + +func init() { + schema.Register(&providerLockbox{}, &esv1alpha1.SecretStoreProvider{ + YandexLockbox: &esv1alpha1.YandexLockboxProvider{}, + }) +}