mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
Merge pull request #323 from AndreyZamyslov/lockbox
Add support for Yandex Lockbox
This commit is contained in:
commit
ee830e47e3
27 changed files with 1654 additions and 10 deletions
|
@ -17,6 +17,7 @@ Multiple people and organizations are joining efforts to create a single Externa
|
|||
- [Google Cloud Secrets Manager](https://external-secrets.io/provider-google-secrets-manager/)
|
||||
- [Azure Key Vault](https://external-secrets.io/provider-azure-key-vault/)
|
||||
- [IBM Cloud Secrets Manager](https://external-secrets.io/provider-ibm-secrets-manager/)
|
||||
- [Yandex Lockbox](https://external-secrets.io/provider-yandex-lockbox/)
|
||||
|
||||
## Stability and Support Level
|
||||
|
||||
|
@ -35,6 +36,7 @@ Multiple people and organizations are joining efforts to create a single Externa
|
|||
| ------------------------------------------------------------------- | :-------: | :----------------------------------------: |
|
||||
| [Azure KV](https://external-secrets.io/provider-azure-key-vault/) | alpha | @ahmedmus-1A @asnowfix @ncourbet-1A @1A-mj |
|
||||
| [IBM SM](https://external-secrets.io/provider-ibm-secrets-manager/) | alpha | @knelasevero @sebagomez @ricardoptcosta |
|
||||
| [Yandex Lockbox](https://external-secrets.io/provider-yandex-lockbox/) | alpha | @AndreyZamyslov @knelasevero |
|
||||
|
||||
## Documentation
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
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 authorized key 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 {
|
||||
// Yandex.Cloud API endpoint (e.g. 'api.cloud.yandex.net:443')
|
||||
// +optional
|
||||
APIEndpoint string `json:"apiEndpoint,omitempty"`
|
||||
|
||||
// Auth defines the information necessary to authenticate against Yandex Lockbox
|
||||
Auth YandexLockboxAuth `json:"auth"`
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -580,6 +580,39 @@ spec:
|
|||
- path
|
||||
- server
|
||||
type: object
|
||||
yandexlockbox:
|
||||
description: YandexLockbox configures this store to sync secrets
|
||||
using Yandex Lockbox provider
|
||||
properties:
|
||||
apiEndpoint:
|
||||
description: Yandex.Cloud API endpoint (e.g. 'api.cloud.yandex.net:443')
|
||||
type: string
|
||||
auth:
|
||||
description: Auth defines the information necessary to authenticate
|
||||
against Yandex Lockbox
|
||||
properties:
|
||||
authorizedKeySecretRef:
|
||||
description: The authorized key 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
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- auth
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- provider
|
||||
|
|
|
@ -580,6 +580,39 @@ spec:
|
|||
- path
|
||||
- server
|
||||
type: object
|
||||
yandexlockbox:
|
||||
description: YandexLockbox configures this store to sync secrets
|
||||
using Yandex Lockbox provider
|
||||
properties:
|
||||
apiEndpoint:
|
||||
description: Yandex.Cloud API endpoint (e.g. 'api.cloud.yandex.net:443')
|
||||
type: string
|
||||
auth:
|
||||
description: Auth defines the information necessary to authenticate
|
||||
against Yandex Lockbox
|
||||
properties:
|
||||
authorizedKeySecretRef:
|
||||
description: The authorized key 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
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- auth
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- provider
|
||||
|
|
86
docs/provider-yandex-lockbox.md
Normal file
86
docs/provider-yandex-lockbox.md
Normal file
|
@ -0,0 +1,86 @@
|
|||
## Yandex Lockbox
|
||||
|
||||
External Secrets Operator integrates with [Yandex Lockbox](https://cloud.yandex.com/docs/lockbox/)
|
||||
for secret management.
|
||||
|
||||
### Prerequisites
|
||||
* [External Secrets Operator installed](../guides-getting-started/#installing-with-helm)
|
||||
* [Yandex.Cloud CLI installed](https://cloud.yandex.com/docs/cli/quickstart)
|
||||
|
||||
### Authentication
|
||||
At the moment, [authorized key](https://cloud.yandex.com/docs/iam/concepts/authorization/key) authentication is only supported:
|
||||
|
||||
* Create a [service account](https://cloud.yandex.com/docs/iam/concepts/users/service-accounts) in Yandex.Cloud:
|
||||
```bash
|
||||
yc iam service-account create --name eso-service-account
|
||||
```
|
||||
* Create an authorized key for the service account and save it to `authorized-key.json` file:
|
||||
```bash
|
||||
yc iam key create \
|
||||
--service-account-name eso-service-account \
|
||||
--output authorized-key.json
|
||||
```
|
||||
* Create a k8s secret containing the authorized key saved above:
|
||||
```bash
|
||||
kubectl create secret generic yc-auth --from-file=authorized-key=authorized-key.json
|
||||
```
|
||||
* Create a [SecretStore](../api-secretstore/) pointing to `yc-auth` k8s secret:
|
||||
```yaml
|
||||
apiVersion: external-secrets.io/v1alpha1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: secret-store
|
||||
spec:
|
||||
provider:
|
||||
yandexlockbox:
|
||||
auth:
|
||||
authorizedKeySecretRef:
|
||||
name: yc-auth
|
||||
key: authorized-key
|
||||
```
|
||||
|
||||
### Creating external secret
|
||||
To make External Secrets Operator sync a k8s secret with a Lockbox secret:
|
||||
|
||||
* Create a Lockbox secret, if not already created:
|
||||
```bash
|
||||
yc lockbox secret create \
|
||||
--name lockbox-secret \
|
||||
--payload '[{"key": "password","textValue": "p@$$w0rd"}]'
|
||||
```
|
||||
* Assign the [`lockbox.payloadViewer`](https://cloud.yandex.com/docs/lockbox/security/#roles-list) role
|
||||
for accessing the `lockbox-secret` payload to the service account used for authentication:
|
||||
```bash
|
||||
yc lockbox secret add-access-binding \
|
||||
--name lockbox-secret \
|
||||
--service-account-name eso-service-account \
|
||||
--role lockbox.payloadViewer
|
||||
```
|
||||
Run the following command to ensure that the correct access binding has been added:
|
||||
```bash
|
||||
yc lockbox secret list-access-bindings --name lockbox-secret
|
||||
```
|
||||
* Create an [ExternalSecret](../api-externalsecret/) pointing to `secret-store` and `lockbox-secret`:
|
||||
```yaml
|
||||
apiVersion: external-secrets.io/v1alpha1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: external-secret
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
name: secret-store
|
||||
kind: SecretStore
|
||||
target:
|
||||
name: k8s-secret # the target k8s secret name
|
||||
data:
|
||||
- secretKey: password # the target k8s secret key
|
||||
remoteRef:
|
||||
key: ***** # ID of lockbox-secret
|
||||
property: password # (optional) payload entry key of lockbox-secret
|
||||
```
|
||||
|
||||
The operator will fetch the Yandex Lockbox secret and inject it as a `Kind=Secret`
|
||||
```yaml
|
||||
kubectl get secret k8s-secret -n <namespace> | -o jsonpath='{.data.password}' | base64 -d
|
||||
```
|
88
docs/spec.md
88
docs/spec.md
|
@ -1469,6 +1469,20 @@ IBMProvider
|
|||
<p>IBM configures this store to sync secrets using IBM Cloud provider</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>yandexlockbox</code></br>
|
||||
<em>
|
||||
<a href="#external-secrets.io/v1alpha1.YandexLockboxProvider">
|
||||
YandexLockboxProvider
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>YandexLockbox configures this store to sync secrets using Yandex Lockbox provider</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1alpha1.SecretStoreRef">SecretStoreRef
|
||||
|
@ -2277,6 +2291,80 @@ are used to validate the TLS connection.</p>
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1alpha1.YandexLockboxAuth">YandexLockboxAuth
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#external-secrets.io/v1alpha1.YandexLockboxProvider">YandexLockboxProvider</a>)
|
||||
</p>
|
||||
<p>
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>authorizedKeySecretRef</code></br>
|
||||
<em>
|
||||
github.com/external-secrets/external-secrets/apis/meta/v1.SecretKeySelector
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>The authorized key used for authentication</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3 id="external-secrets.io/v1alpha1.YandexLockboxProvider">YandexLockboxProvider
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#external-secrets.io/v1alpha1.SecretStoreProvider">SecretStoreProvider</a>)
|
||||
</p>
|
||||
<p>
|
||||
<p>YandexLockboxProvider Configures a store to sync secrets using the Yandex Lockbox provider.</p>
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>apiEndpoint</code></br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Yandex.Cloud API endpoint (e.g. ‘api.cloud.yandex.net:443’)</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>auth</code></br>
|
||||
<em>
|
||||
<a href="#external-secrets.io/v1alpha1.YandexLockboxAuth">
|
||||
YandexLockboxAuth
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<p>Auth defines the information necessary to authenticate against Yandex Lockbox</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr/>
|
||||
<p><em>
|
||||
Generated with <code>gen-crd-api-reference-docs</code>.
|
||||
|
|
3
go.mod
3
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
|
||||
|
@ -72,6 +74,7 @@ require (
|
|||
golang.org/x/tools v0.1.2-0.20210512205948-8287d5da45e4 // indirect
|
||||
google.golang.org/api v0.30.0
|
||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a
|
||||
google.golang.org/grpc v1.31.0
|
||||
honnef.co/go/tools v0.1.4 // indirect
|
||||
k8s.io/api v0.21.2
|
||||
k8s.io/apimachinery v0.21.2
|
||||
|
|
10
go.sum
10
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=
|
||||
|
|
|
@ -46,6 +46,8 @@ nav:
|
|||
- IBM:
|
||||
- Secrets Manager: provider-ibm-secrets-manager.md
|
||||
- HashiCorp Vault: provider-hashicorp-vault.md
|
||||
- Yandex:
|
||||
- Lockbox: provider-yandex-lockbox.md
|
||||
- References:
|
||||
- API specification: spec.md
|
||||
- Contributing:
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -123,6 +123,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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -210,7 +210,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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
39
pkg/provider/yandex/lockbox/client/client.go
Normal file
39
pkg/provider/yandex/lockbox/client/client.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
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 client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/yandex-cloud/go-genproto/yandex/cloud/lockbox/v1"
|
||||
"github.com/yandex-cloud/go-sdk/iamkey"
|
||||
)
|
||||
|
||||
// Creates Lockbox clients and Yandex.Cloud IAM tokens.
|
||||
type YandexCloudCreator interface {
|
||||
CreateLockboxClient(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (LockboxClient, error)
|
||||
CreateIamToken(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (*IamToken, error)
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
type IamToken struct {
|
||||
Token string
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
// Responsible for accessing Lockbox secrets.
|
||||
type LockboxClient interface {
|
||||
GetPayloadEntries(ctx context.Context, iamToken, secretID, versionID string) ([]*lockbox.Payload_Entry, error)
|
||||
}
|
151
pkg/provider/yandex/lockbox/client/fake/fake.go
Normal file
151
pkg/provider/yandex/lockbox/client/fake/fake.go
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
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 fake
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/uuid"
|
||||
"github.com/yandex-cloud/go-genproto/yandex/cloud/lockbox/v1"
|
||||
"github.com/yandex-cloud/go-sdk/iamkey"
|
||||
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client"
|
||||
)
|
||||
|
||||
// Fake implementation of YandexCloudCreator.
|
||||
type YandexCloudCreator struct {
|
||||
Backend *LockboxBackend
|
||||
}
|
||||
|
||||
func (c *YandexCloudCreator) CreateLockboxClient(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (client.LockboxClient, error) {
|
||||
return &LockboxClient{c.Backend}, nil
|
||||
}
|
||||
|
||||
func (c *YandexCloudCreator) CreateIamToken(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (*client.IamToken, error) {
|
||||
return c.Backend.getToken(authorizedKey)
|
||||
}
|
||||
|
||||
func (c *YandexCloudCreator) Now() time.Time {
|
||||
return c.Backend.now
|
||||
}
|
||||
|
||||
// Fake implementation of LockboxClient.
|
||||
type LockboxClient struct {
|
||||
fakeLockboxBackend *LockboxBackend
|
||||
}
|
||||
|
||||
func (c *LockboxClient) GetPayloadEntries(ctx context.Context, iamToken, secretID, versionID string) ([]*lockbox.Payload_Entry, error) {
|
||||
return c.fakeLockboxBackend.getEntries(iamToken, secretID, versionID)
|
||||
}
|
||||
|
||||
// Fakes Yandex Lockbox service backend.
|
||||
type LockboxBackend struct {
|
||||
secretMap map[secretKey]secretValue // secret specific data
|
||||
versionMap map[versionKey]versionValue // version specific data
|
||||
tokenMap map[tokenKey]tokenValue // token specific data
|
||||
|
||||
tokenExpirationDuration time.Duration
|
||||
now time.Time // fakes the current time
|
||||
}
|
||||
|
||||
type secretKey struct {
|
||||
secretID string
|
||||
}
|
||||
|
||||
type secretValue struct {
|
||||
expectedAuthorizedKey *iamkey.Key // authorized key expected to access the secret
|
||||
}
|
||||
|
||||
type versionKey struct {
|
||||
secretID string
|
||||
versionID string
|
||||
}
|
||||
|
||||
type versionValue struct {
|
||||
entries []*lockbox.Payload_Entry
|
||||
}
|
||||
|
||||
type tokenKey struct {
|
||||
token string
|
||||
}
|
||||
|
||||
type tokenValue struct {
|
||||
authorizedKey *iamkey.Key
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
func NewLockboxBackend(tokenExpirationDuration time.Duration) *LockboxBackend {
|
||||
return &LockboxBackend{
|
||||
secretMap: make(map[secretKey]secretValue),
|
||||
versionMap: make(map[versionKey]versionValue),
|
||||
tokenMap: make(map[tokenKey]tokenValue),
|
||||
tokenExpirationDuration: tokenExpirationDuration,
|
||||
now: time.Time{},
|
||||
}
|
||||
}
|
||||
|
||||
func (lb *LockboxBackend) CreateSecret(authorizedKey *iamkey.Key, entries ...*lockbox.Payload_Entry) (string, string) {
|
||||
secretID := uuid.NewString()
|
||||
versionID := uuid.NewString()
|
||||
|
||||
lb.secretMap[secretKey{secretID}] = secretValue{authorizedKey}
|
||||
lb.versionMap[versionKey{secretID, ""}] = versionValue{entries} // empty versionID corresponds to the latest version
|
||||
lb.versionMap[versionKey{secretID, versionID}] = versionValue{entries}
|
||||
|
||||
return secretID, versionID
|
||||
}
|
||||
|
||||
func (lb *LockboxBackend) AddVersion(secretID string, entries ...*lockbox.Payload_Entry) string {
|
||||
versionID := uuid.NewString()
|
||||
|
||||
lb.versionMap[versionKey{secretID, ""}] = versionValue{entries} // empty versionID corresponds to the latest version
|
||||
lb.versionMap[versionKey{secretID, versionID}] = versionValue{entries}
|
||||
|
||||
return versionID
|
||||
}
|
||||
|
||||
func (lb *LockboxBackend) AdvanceClock(duration time.Duration) {
|
||||
lb.now = lb.now.Add(duration)
|
||||
}
|
||||
|
||||
func (lb *LockboxBackend) getToken(authorizedKey *iamkey.Key) (*client.IamToken, error) {
|
||||
token := uuid.NewString()
|
||||
expiresAt := lb.now.Add(lb.tokenExpirationDuration)
|
||||
lb.tokenMap[tokenKey{token}] = tokenValue{authorizedKey, expiresAt}
|
||||
return &client.IamToken{Token: token, ExpiresAt: expiresAt}, nil
|
||||
}
|
||||
|
||||
func (lb *LockboxBackend) getEntries(iamToken, secretID, versionID string) ([]*lockbox.Payload_Entry, error) {
|
||||
if _, ok := lb.secretMap[secretKey{secretID}]; !ok {
|
||||
return nil, fmt.Errorf("secret not found")
|
||||
}
|
||||
if _, ok := lb.versionMap[versionKey{secretID, versionID}]; !ok {
|
||||
return nil, fmt.Errorf("version not found")
|
||||
}
|
||||
if _, ok := lb.tokenMap[tokenKey{iamToken}]; !ok {
|
||||
return nil, fmt.Errorf("unauthenticated")
|
||||
}
|
||||
|
||||
if lb.tokenMap[tokenKey{iamToken}].expiresAt.Before(lb.now) {
|
||||
return nil, fmt.Errorf("iam token expired")
|
||||
}
|
||||
if !cmp.Equal(lb.tokenMap[tokenKey{iamToken}].authorizedKey, lb.secretMap[secretKey{secretID}].expectedAuthorizedKey) {
|
||||
return nil, fmt.Errorf("permission denied")
|
||||
}
|
||||
|
||||
return lb.versionMap[versionKey{secretID, versionID}].entries, nil
|
||||
}
|
144
pkg/provider/yandex/lockbox/client/grpc/grpc.go
Normal file
144
pkg/provider/yandex/lockbox/client/grpc/grpc.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
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 grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"time"
|
||||
|
||||
"github.com/yandex-cloud/go-genproto/yandex/cloud/endpoint"
|
||||
"github.com/yandex-cloud/go-genproto/yandex/cloud/lockbox/v1"
|
||||
ycsdk "github.com/yandex-cloud/go-sdk"
|
||||
"github.com/yandex-cloud/go-sdk/iamkey"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client"
|
||||
)
|
||||
|
||||
// Implementation of YandexCloudCreator.
|
||||
type YandexCloudCreator struct {
|
||||
}
|
||||
|
||||
func (lb *YandexCloudCreator) CreateLockboxClient(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (client.LockboxClient, error) {
|
||||
sdk, err := buildSDK(ctx, apiEndpoint, authorizedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payloadAPIEndpoint, err := sdk.ApiEndpoint().ApiEndpoint().Get(ctx, &endpoint.GetApiEndpointRequest{
|
||||
ApiEndpointId: "lockbox-payload", // the ID from https://api.cloud.yandex.net/endpoints
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = closeSDK(ctx, sdk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := grpc.Dial(payloadAPIEndpoint.Address,
|
||||
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{MinVersion: tls.VersionTLS12})),
|
||||
grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||
Time: time.Second * 30,
|
||||
Timeout: time.Second * 10,
|
||||
PermitWithoutStream: false,
|
||||
}),
|
||||
grpc.WithUserAgent("external-secrets"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &LockboxClient{lockbox.NewPayloadServiceClient(conn)}, nil
|
||||
}
|
||||
|
||||
func (lb *YandexCloudCreator) CreateIamToken(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (*client.IamToken, error) {
|
||||
sdk, err := buildSDK(ctx, apiEndpoint, authorizedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iamToken, err := sdk.CreateIAMToken(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = closeSDK(ctx, sdk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &client.IamToken{Token: iamToken.IamToken, ExpiresAt: iamToken.ExpiresAt.AsTime()}, nil
|
||||
}
|
||||
|
||||
func (lb *YandexCloudCreator) Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func buildSDK(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (*ycsdk.SDK, error) {
|
||||
creds, err := ycsdk.ServiceAccountKey(authorizedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sdk, err := ycsdk.Build(ctx, ycsdk.Config{
|
||||
Credentials: creds,
|
||||
Endpoint: apiEndpoint,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sdk, nil
|
||||
}
|
||||
|
||||
func closeSDK(ctx context.Context, sdk *ycsdk.SDK) error {
|
||||
return sdk.Shutdown(ctx)
|
||||
}
|
||||
|
||||
// Implementation of LockboxClient.
|
||||
type LockboxClient struct {
|
||||
lockboxPayloadClient lockbox.PayloadServiceClient
|
||||
}
|
||||
|
||||
func (lc *LockboxClient) GetPayloadEntries(ctx context.Context, iamToken, secretID, versionID string) ([]*lockbox.Payload_Entry, error) {
|
||||
payload, err := lc.lockboxPayloadClient.Get(
|
||||
ctx,
|
||||
&lockbox.GetPayloadRequest{
|
||||
SecretId: secretID,
|
||||
VersionId: versionID,
|
||||
},
|
||||
grpc.PerRPCCredentials(perRPCCredentials{iamToken: iamToken}),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return payload.Entries, nil
|
||||
}
|
||||
|
||||
type perRPCCredentials struct {
|
||||
iamToken string
|
||||
}
|
||||
|
||||
func (t perRPCCredentials) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) {
|
||||
return map[string]string{"Authorization": "Bearer " + t.iamToken}, nil
|
||||
}
|
||||
|
||||
func (perRPCCredentials) RequireTransportSecurity() bool {
|
||||
return true
|
||||
}
|
299
pkg/provider/yandex/lockbox/lockbox.go
Normal file
299
pkg/provider/yandex/lockbox/lockbox.go
Normal file
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
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"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/yandex-cloud/go-genproto/yandex/cloud/lockbox/v1"
|
||||
"github.com/yandex-cloud/go-sdk/iamkey"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
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/provider/yandex/lockbox/client"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client/grpc"
|
||||
)
|
||||
|
||||
const maxSecretsClientLifetime = 5 * time.Minute // supposed SecretsClient lifetime is quite short
|
||||
const iamTokenCleanupDelay = 1 * time.Hour // specifies how often cleanUpIamTokenMap() is performed
|
||||
|
||||
var log = ctrl.Log.WithName("provider").WithName("yandex").WithName("lockbox")
|
||||
|
||||
type iamTokenKey struct {
|
||||
authorizedKeyID string
|
||||
serviceAccountID string
|
||||
privateKeyHash string
|
||||
}
|
||||
|
||||
// lockboxProvider is a provider for Yandex Lockbox.
|
||||
type lockboxProvider struct {
|
||||
yandexCloudCreator client.YandexCloudCreator
|
||||
|
||||
lockboxClientMap map[string]client.LockboxClient // apiEndpoint -> LockboxClient
|
||||
lockboxClientMapMutex sync.Mutex
|
||||
iamTokenMap map[iamTokenKey]*client.IamToken
|
||||
iamTokenMapMutex sync.Mutex
|
||||
}
|
||||
|
||||
func newLockboxProvider(yandexCloudCreator client.YandexCloudCreator) *lockboxProvider {
|
||||
return &lockboxProvider{
|
||||
yandexCloudCreator: yandexCloudCreator,
|
||||
lockboxClientMap: make(map[string]client.LockboxClient),
|
||||
iamTokenMap: make(map[iamTokenKey]*client.IamToken),
|
||||
}
|
||||
}
|
||||
|
||||
// NewClient constructs a Yandex Lockbox Provider.
|
||||
func (p *lockboxProvider) 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)
|
||||
}
|
||||
|
||||
lockboxClient, err := p.getOrCreateLockboxClient(ctx, storeSpecYandexLockbox.APIEndpoint, &authorizedKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create Yandex Lockbox client: %w", err)
|
||||
}
|
||||
|
||||
iamToken, err := p.getOrCreateIamToken(ctx, storeSpecYandexLockbox.APIEndpoint, &authorizedKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create IAM token: %w", err)
|
||||
}
|
||||
|
||||
return &lockboxSecretsClient{lockboxClient, iamToken.Token}, nil
|
||||
}
|
||||
|
||||
func (p *lockboxProvider) getOrCreateLockboxClient(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (client.LockboxClient, error) {
|
||||
p.lockboxClientMapMutex.Lock()
|
||||
defer p.lockboxClientMapMutex.Unlock()
|
||||
|
||||
if _, ok := p.lockboxClientMap[apiEndpoint]; !ok {
|
||||
log.Info("creating LockboxClient", "apiEndpoint", apiEndpoint)
|
||||
|
||||
lockboxClient, err := p.yandexCloudCreator.CreateLockboxClient(ctx, apiEndpoint, authorizedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.lockboxClientMap[apiEndpoint] = lockboxClient
|
||||
}
|
||||
return p.lockboxClientMap[apiEndpoint], nil
|
||||
}
|
||||
|
||||
func (p *lockboxProvider) getOrCreateIamToken(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (*client.IamToken, error) {
|
||||
p.iamTokenMapMutex.Lock()
|
||||
defer p.iamTokenMapMutex.Unlock()
|
||||
|
||||
iamTokenKey := buildIamTokenKey(authorizedKey)
|
||||
if iamToken, ok := p.iamTokenMap[iamTokenKey]; !ok || !p.isIamTokenUsable(iamToken) {
|
||||
log.Info("creating IAM token", "authorizedKeyId", authorizedKey.Id)
|
||||
|
||||
iamToken, err := p.yandexCloudCreator.CreateIamToken(ctx, apiEndpoint, authorizedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Info("created IAM token", "authorizedKeyId", authorizedKey.Id, "expiresAt", iamToken.ExpiresAt)
|
||||
|
||||
p.iamTokenMap[iamTokenKey] = iamToken
|
||||
}
|
||||
return p.iamTokenMap[iamTokenKey], nil
|
||||
}
|
||||
|
||||
func (p *lockboxProvider) isIamTokenUsable(iamToken *client.IamToken) bool {
|
||||
now := p.yandexCloudCreator.Now()
|
||||
return now.Add(maxSecretsClientLifetime).Before(iamToken.ExpiresAt)
|
||||
}
|
||||
|
||||
func buildIamTokenKey(authorizedKey *iamkey.Key) iamTokenKey {
|
||||
privateKeyHash := sha256.Sum256([]byte(authorizedKey.PrivateKey))
|
||||
return iamTokenKey{
|
||||
authorizedKey.GetId(),
|
||||
authorizedKey.GetServiceAccountId(),
|
||||
hex.EncodeToString(privateKeyHash[:]),
|
||||
}
|
||||
}
|
||||
|
||||
// Used for testing.
|
||||
func (p *lockboxProvider) isIamTokenCached(authorizedKey *iamkey.Key) bool {
|
||||
p.iamTokenMapMutex.Lock()
|
||||
defer p.iamTokenMapMutex.Unlock()
|
||||
|
||||
_, ok := p.iamTokenMap[buildIamTokenKey(authorizedKey)]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (p *lockboxProvider) cleanUpIamTokenMap() {
|
||||
p.iamTokenMapMutex.Lock()
|
||||
defer p.iamTokenMapMutex.Unlock()
|
||||
|
||||
for key, value := range p.iamTokenMap {
|
||||
if p.yandexCloudCreator.Now().After(value.ExpiresAt) {
|
||||
log.Info("deleting IAM token", "authorizedKeyId", key.authorizedKeyID)
|
||||
delete(p.iamTokenMap, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lockboxSecretsClient is a secrets client for Yandex Lockbox.
|
||||
type lockboxSecretsClient struct {
|
||||
lockboxClient client.LockboxClient
|
||||
iamToken string
|
||||
}
|
||||
|
||||
// GetSecret returns a single secret from the provider.
|
||||
func (c *lockboxSecretsClient) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
entries, err := c.lockboxClient.GetPayloadEntries(ctx, c.iamToken, ref.Key, ref.Version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to request secret payload to get secret: %w", 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)
|
||||
}
|
||||
|
||||
// GetSecretMap returns multiple k/v pairs from the provider.
|
||||
func (c *lockboxSecretsClient) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||
entries, err := c.lockboxClient.GetPayloadEntries(ctx, c.iamToken, ref.Key, ref.Version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to request secret payload to get secret map: %w", 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 (c *lockboxSecretsClient) Close(ctx context.Context) error {
|
||||
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() {
|
||||
lockboxProvider := newLockboxProvider(&grpc.YandexCloudCreator{})
|
||||
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(iamTokenCleanupDelay)
|
||||
lockboxProvider.cleanUpIamTokenMap()
|
||||
}
|
||||
}()
|
||||
|
||||
schema.Register(
|
||||
lockboxProvider,
|
||||
&esv1alpha1.SecretStoreProvider{
|
||||
YandexLockbox: &esv1alpha1.YandexLockboxProvider{},
|
||||
},
|
||||
)
|
||||
}
|
677
pkg/provider/yandex/lockbox/lockbox_test.go
Normal file
677
pkg/provider/yandex/lockbox/lockbox_test.go
Normal file
|
@ -0,0 +1,677 @@
|
|||
/*
|
||||
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"
|
||||
b64 "encoding/base64"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
tassert "github.com/stretchr/testify/assert"
|
||||
"github.com/yandex-cloud/go-genproto/yandex/cloud/lockbox/v1"
|
||||
"github.com/yandex-cloud/go-sdk/iamkey"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client/fake"
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const namespace = "namespace"
|
||||
|
||||
store := &esv1alpha1.SecretStore{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: esv1alpha1.SecretStoreSpec{
|
||||
Provider: &esv1alpha1.SecretStoreProvider{
|
||||
YandexLockbox: &esv1alpha1.YandexLockboxProvider{},
|
||||
},
|
||||
},
|
||||
}
|
||||
provider, err := schema.GetProvider(store)
|
||||
tassert.Nil(t, err)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
secretClient, err := provider.NewClient(context.Background(), store, k8sClient, namespace)
|
||||
tassert.EqualError(t, err, "invalid Yandex Lockbox SecretStore resource: missing AuthorizedKey Name")
|
||||
tassert.Nil(t, secretClient)
|
||||
|
||||
store.Spec.Provider.YandexLockbox.Auth = esv1alpha1.YandexLockboxAuth{}
|
||||
secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
|
||||
tassert.EqualError(t, err, "invalid Yandex Lockbox SecretStore resource: missing AuthorizedKey Name")
|
||||
tassert.Nil(t, secretClient)
|
||||
|
||||
store.Spec.Provider.YandexLockbox.Auth.AuthorizedKey = esmeta.SecretKeySelector{}
|
||||
secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
|
||||
tassert.EqualError(t, err, "invalid Yandex Lockbox SecretStore resource: missing AuthorizedKey Name")
|
||||
tassert.Nil(t, secretClient)
|
||||
|
||||
const authorizedKeySecretName = "authorizedKeySecretName"
|
||||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
store.Spec.Provider.YandexLockbox.Auth.AuthorizedKey.Name = authorizedKeySecretName
|
||||
store.Spec.Provider.YandexLockbox.Auth.AuthorizedKey.Key = authorizedKeySecretKey
|
||||
secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
|
||||
tassert.EqualError(t, err, "could not fetch AuthorizedKey secret: secrets \"authorizedKeySecretName\" not found")
|
||||
tassert.Nil(t, secretClient)
|
||||
|
||||
err = createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, newFakeAuthorizedKey())
|
||||
tassert.Nil(t, err)
|
||||
secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
|
||||
tassert.EqualError(t, err, "failed to create Yandex Lockbox client: private key parsing failed: Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key")
|
||||
tassert.Nil(t, secretClient)
|
||||
}
|
||||
|
||||
func TestGetSecretForAllEntries(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
k1, v1 := "k1", "v1"
|
||||
k2, v2 := "k2", []byte("v2")
|
||||
secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
|
||||
textEntry(k1, v1),
|
||||
binaryEntry(k2, v2),
|
||||
)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName = "authorizedKeySecretName"
|
||||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
})
|
||||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
data, err := secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID})
|
||||
tassert.Nil(t, err)
|
||||
|
||||
tassert.Equal(
|
||||
t,
|
||||
map[string]string{
|
||||
k1: v1,
|
||||
k2: base64(v2),
|
||||
},
|
||||
unmarshalStringMap(t, data),
|
||||
)
|
||||
}
|
||||
|
||||
func TestGetSecretForTextEntry(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
k1, v1 := "k1", "v1"
|
||||
k2, v2 := "k2", []byte("v2")
|
||||
secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
|
||||
textEntry(k1, v1),
|
||||
binaryEntry(k2, v2),
|
||||
)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName = "authorizedKeySecretName"
|
||||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
})
|
||||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
data, err := secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Property: k1})
|
||||
tassert.Nil(t, err)
|
||||
|
||||
tassert.Equal(t, v1, string(data))
|
||||
}
|
||||
|
||||
func TestGetSecretForBinaryEntry(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
k1, v1 := "k1", "v1"
|
||||
k2, v2 := "k2", []byte("v2")
|
||||
secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
|
||||
textEntry(k1, v1),
|
||||
binaryEntry(k2, v2),
|
||||
)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName = "authorizedKeySecretName"
|
||||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
})
|
||||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
data, err := secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Property: k2})
|
||||
tassert.Nil(t, err)
|
||||
|
||||
tassert.Equal(t, v2, data)
|
||||
}
|
||||
|
||||
func TestGetSecretByVersionID(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
oldKey, oldVal := "oldKey", "oldVal"
|
||||
secretID, oldVersionID := lockboxBackend.CreateSecret(authorizedKey,
|
||||
textEntry(oldKey, oldVal),
|
||||
)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName = "authorizedKeySecretName"
|
||||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
})
|
||||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
data, err := secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Version: oldVersionID})
|
||||
tassert.Nil(t, err)
|
||||
|
||||
tassert.Equal(t, map[string]string{oldKey: oldVal}, unmarshalStringMap(t, data))
|
||||
|
||||
newKey, newVal := "newKey", "newVal"
|
||||
newVersionID := lockboxBackend.AddVersion(secretID,
|
||||
textEntry(newKey, newVal),
|
||||
)
|
||||
|
||||
data, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Version: oldVersionID})
|
||||
tassert.Nil(t, err)
|
||||
tassert.Equal(t, map[string]string{oldKey: oldVal}, unmarshalStringMap(t, data))
|
||||
|
||||
data, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Version: newVersionID})
|
||||
tassert.Nil(t, err)
|
||||
tassert.Equal(t, map[string]string{newKey: newVal}, unmarshalStringMap(t, data))
|
||||
}
|
||||
|
||||
func TestGetSecretUnauthorized(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
namespace := uuid.NewString()
|
||||
authorizedKeyA := newFakeAuthorizedKey()
|
||||
authorizedKeyB := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
secretID, _ := lockboxBackend.CreateSecret(authorizedKeyA,
|
||||
textEntry("k1", "v1"),
|
||||
)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName = "authorizedKeySecretName"
|
||||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKeyB)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
})
|
||||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
_, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID})
|
||||
tassert.EqualError(t, err, "unable to request secret payload to get secret: permission denied")
|
||||
}
|
||||
|
||||
func TestGetSecretNotFound(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName = "authorizedKeySecretName"
|
||||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
})
|
||||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
_, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: "no-secret-with-this-id"})
|
||||
tassert.EqualError(t, err, "unable to request secret payload to get secret: secret not found")
|
||||
|
||||
secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
|
||||
textEntry("k1", "v1"),
|
||||
)
|
||||
_, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Version: "no-version-with-this-id"})
|
||||
tassert.EqualError(t, err, "unable to request secret payload to get secret: version not found")
|
||||
}
|
||||
|
||||
func TestGetSecretWithTwoNamespaces(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
namespace1 := uuid.NewString()
|
||||
namespace2 := uuid.NewString()
|
||||
authorizedKey1 := newFakeAuthorizedKey()
|
||||
authorizedKey2 := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
k1, v1 := "k1", "v1"
|
||||
secretID1, _ := lockboxBackend.CreateSecret(authorizedKey1,
|
||||
textEntry(k1, v1),
|
||||
)
|
||||
k2, v2 := "k2", "v2"
|
||||
secretID2, _ := lockboxBackend.CreateSecret(authorizedKey2,
|
||||
textEntry(k2, v2),
|
||||
)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName = "authorizedKeySecretName"
|
||||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace1, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey1)
|
||||
tassert.Nil(t, err)
|
||||
err = createK8sSecret(ctx, k8sClient, namespace2, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey2)
|
||||
tassert.Nil(t, err)
|
||||
store1 := newYandexLockboxSecretStore("", namespace1, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
store2 := newYandexLockboxSecretStore("", namespace2, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
})
|
||||
secretsClient1, err := provider.NewClient(ctx, store1, k8sClient, namespace1)
|
||||
tassert.Nil(t, err)
|
||||
secretsClient2, err := provider.NewClient(ctx, store2, k8sClient, namespace2)
|
||||
tassert.Nil(t, err)
|
||||
|
||||
data, err := secretsClient1.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID1, Property: k1})
|
||||
tassert.Equal(t, v1, string(data))
|
||||
tassert.Nil(t, err)
|
||||
data, err = secretsClient1.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2})
|
||||
tassert.Nil(t, data)
|
||||
tassert.EqualError(t, err, "unable to request secret payload to get secret: permission denied")
|
||||
|
||||
data, err = secretsClient2.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID1, Property: k1})
|
||||
tassert.Nil(t, data)
|
||||
tassert.EqualError(t, err, "unable to request secret payload to get secret: permission denied")
|
||||
data, err = secretsClient2.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2})
|
||||
tassert.Equal(t, v2, string(data))
|
||||
tassert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestGetSecretWithTwoApiEndpoints(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
apiEndpoint1 := uuid.NewString()
|
||||
apiEndpoint2 := uuid.NewString()
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey1 := newFakeAuthorizedKey()
|
||||
authorizedKey2 := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend1 := fake.NewLockboxBackend(time.Hour)
|
||||
k1, v1 := "k1", "v1"
|
||||
secretID1, _ := lockboxBackend1.CreateSecret(authorizedKey1,
|
||||
textEntry(k1, v1),
|
||||
)
|
||||
lockboxBackend2 := fake.NewLockboxBackend(time.Hour)
|
||||
k2, v2 := "k2", "v2"
|
||||
secretID2, _ := lockboxBackend2.CreateSecret(authorizedKey2,
|
||||
textEntry(k2, v2),
|
||||
)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName1 = "authorizedKeySecretName1"
|
||||
const authorizedKeySecretKey1 = "authorizedKeySecretKey1"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName1, authorizedKeySecretKey1, authorizedKey1)
|
||||
tassert.Nil(t, err)
|
||||
const authorizedKeySecretName2 = "authorizedKeySecretName2"
|
||||
const authorizedKeySecretKey2 = "authorizedKeySecretKey2"
|
||||
err = createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName2, authorizedKeySecretKey2, authorizedKey2)
|
||||
tassert.Nil(t, err)
|
||||
|
||||
store1 := newYandexLockboxSecretStore(apiEndpoint1, namespace, authorizedKeySecretName1, authorizedKeySecretKey1)
|
||||
store2 := newYandexLockboxSecretStore(apiEndpoint2, namespace, authorizedKeySecretName2, authorizedKeySecretKey2)
|
||||
|
||||
provider1 := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend1,
|
||||
})
|
||||
provider2 := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend2,
|
||||
})
|
||||
|
||||
secretsClient1, err := provider1.NewClient(ctx, store1, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
secretsClient2, err := provider2.NewClient(ctx, store2, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
|
||||
var data []byte
|
||||
|
||||
data, err = secretsClient1.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID1, Property: k1})
|
||||
tassert.Equal(t, v1, string(data))
|
||||
tassert.Nil(t, err)
|
||||
data, err = secretsClient1.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2})
|
||||
tassert.Nil(t, data)
|
||||
tassert.EqualError(t, err, "unable to request secret payload to get secret: secret not found")
|
||||
|
||||
data, err = secretsClient2.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID1, Property: k1})
|
||||
tassert.Nil(t, data)
|
||||
tassert.EqualError(t, err, "unable to request secret payload to get secret: secret not found")
|
||||
data, err = secretsClient2.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2})
|
||||
tassert.Equal(t, v2, string(data))
|
||||
tassert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestGetSecretWithIamTokenExpiration(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey := newFakeAuthorizedKey()
|
||||
|
||||
tokenExpirationTime := time.Hour
|
||||
lockboxBackend := fake.NewLockboxBackend(tokenExpirationTime)
|
||||
k1, v1 := "k1", "v1"
|
||||
secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
|
||||
textEntry(k1, v1),
|
||||
)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName = "authorizedKeySecretName"
|
||||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
})
|
||||
|
||||
var data []byte
|
||||
|
||||
oldSecretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
data, err = oldSecretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Property: k1})
|
||||
tassert.Equal(t, v1, string(data))
|
||||
tassert.Nil(t, err)
|
||||
|
||||
lockboxBackend.AdvanceClock(2 * tokenExpirationTime)
|
||||
|
||||
data, err = oldSecretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Property: k1})
|
||||
tassert.Nil(t, data)
|
||||
tassert.EqualError(t, err, "unable to request secret payload to get secret: iam token expired")
|
||||
|
||||
newSecretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
data, err = newSecretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Property: k1})
|
||||
tassert.Equal(t, v1, string(data))
|
||||
tassert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestGetSecretWithIamTokenCleanup(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey1 := newFakeAuthorizedKey()
|
||||
authorizedKey2 := newFakeAuthorizedKey()
|
||||
|
||||
tokenExpirationDuration := time.Hour
|
||||
lockboxBackend := fake.NewLockboxBackend(tokenExpirationDuration)
|
||||
secretID1, _ := lockboxBackend.CreateSecret(authorizedKey1,
|
||||
textEntry("k1", "v1"),
|
||||
)
|
||||
secretID2, _ := lockboxBackend.CreateSecret(authorizedKey2,
|
||||
textEntry("k2", "v2"),
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName1 = "authorizedKeySecretName1"
|
||||
const authorizedKeySecretKey1 = "authorizedKeySecretKey1"
|
||||
err = createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName1, authorizedKeySecretKey1, authorizedKey1)
|
||||
tassert.Nil(t, err)
|
||||
const authorizedKeySecretName2 = "authorizedKeySecretName2"
|
||||
const authorizedKeySecretKey2 = "authorizedKeySecretKey2"
|
||||
err = createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName2, authorizedKeySecretKey2, authorizedKey2)
|
||||
tassert.Nil(t, err)
|
||||
|
||||
store1 := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName1, authorizedKeySecretKey1)
|
||||
store2 := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName2, authorizedKeySecretKey2)
|
||||
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
})
|
||||
|
||||
tassert.False(t, provider.isIamTokenCached(authorizedKey1))
|
||||
tassert.False(t, provider.isIamTokenCached(authorizedKey2))
|
||||
|
||||
// Access secretID1 with authorizedKey1, IAM token for authorizedKey1 should be cached
|
||||
secretsClient, err := provider.NewClient(ctx, store1, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
_, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID1})
|
||||
tassert.Nil(t, err)
|
||||
|
||||
tassert.True(t, provider.isIamTokenCached(authorizedKey1))
|
||||
tassert.False(t, provider.isIamTokenCached(authorizedKey2))
|
||||
|
||||
lockboxBackend.AdvanceClock(tokenExpirationDuration * 2)
|
||||
|
||||
// Access secretID2 with authorizedKey2, IAM token for authorizedKey2 should be cached
|
||||
secretsClient, err = provider.NewClient(ctx, store2, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
_, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2})
|
||||
tassert.Nil(t, err)
|
||||
|
||||
tassert.True(t, provider.isIamTokenCached(authorizedKey1))
|
||||
tassert.True(t, provider.isIamTokenCached(authorizedKey2))
|
||||
|
||||
lockboxBackend.AdvanceClock(tokenExpirationDuration)
|
||||
|
||||
tassert.True(t, provider.isIamTokenCached(authorizedKey1))
|
||||
tassert.True(t, provider.isIamTokenCached(authorizedKey2))
|
||||
|
||||
provider.cleanUpIamTokenMap()
|
||||
|
||||
tassert.False(t, provider.isIamTokenCached(authorizedKey1))
|
||||
tassert.True(t, provider.isIamTokenCached(authorizedKey2))
|
||||
|
||||
lockboxBackend.AdvanceClock(tokenExpirationDuration)
|
||||
|
||||
tassert.False(t, provider.isIamTokenCached(authorizedKey1))
|
||||
tassert.True(t, provider.isIamTokenCached(authorizedKey2))
|
||||
|
||||
provider.cleanUpIamTokenMap()
|
||||
|
||||
tassert.False(t, provider.isIamTokenCached(authorizedKey1))
|
||||
tassert.False(t, provider.isIamTokenCached(authorizedKey2))
|
||||
}
|
||||
|
||||
func TestGetSecretMap(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
k1, v1 := "k1", "v1"
|
||||
k2, v2 := "k2", []byte("v2")
|
||||
secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
|
||||
textEntry(k1, v1),
|
||||
binaryEntry(k2, v2),
|
||||
)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName = "authorizedKeySecretName"
|
||||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
})
|
||||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
data, err := secretsClient.GetSecretMap(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID})
|
||||
tassert.Nil(t, err)
|
||||
|
||||
tassert.Equal(
|
||||
t,
|
||||
map[string][]byte{
|
||||
k1: []byte(v1),
|
||||
k2: v2,
|
||||
},
|
||||
data,
|
||||
)
|
||||
}
|
||||
|
||||
func TestGetSecretMapByVersionID(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
oldKey, oldVal := "oldKey", "oldVal"
|
||||
secretID, oldVersionID := lockboxBackend.CreateSecret(authorizedKey,
|
||||
textEntry(oldKey, oldVal),
|
||||
)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName = "authorizedKeySecretName"
|
||||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
})
|
||||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
data, err := secretsClient.GetSecretMap(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Version: oldVersionID})
|
||||
tassert.Nil(t, err)
|
||||
|
||||
tassert.Equal(t, map[string][]byte{oldKey: []byte(oldVal)}, data)
|
||||
|
||||
newKey, newVal := "newKey", "newVal"
|
||||
newVersionID := lockboxBackend.AddVersion(secretID,
|
||||
textEntry(newKey, newVal),
|
||||
)
|
||||
|
||||
data, err = secretsClient.GetSecretMap(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Version: oldVersionID})
|
||||
tassert.Nil(t, err)
|
||||
tassert.Equal(t, map[string][]byte{oldKey: []byte(oldVal)}, data)
|
||||
|
||||
data, err = secretsClient.GetSecretMap(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Version: newVersionID})
|
||||
tassert.Nil(t, err)
|
||||
tassert.Equal(t, map[string][]byte{newKey: []byte(newVal)}, data)
|
||||
}
|
||||
|
||||
// helper functions
|
||||
|
||||
func newYandexLockboxSecretStore(apiEndpoint, namespace, authorizedKeySecretName, authorizedKeySecretKey string) esv1alpha1.GenericStore {
|
||||
return &esv1alpha1.SecretStore{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: esv1alpha1.SecretStoreSpec{
|
||||
Provider: &esv1alpha1.SecretStoreProvider{
|
||||
YandexLockbox: &esv1alpha1.YandexLockboxProvider{
|
||||
APIEndpoint: apiEndpoint,
|
||||
Auth: esv1alpha1.YandexLockboxAuth{
|
||||
AuthorizedKey: esmeta.SecretKeySelector{
|
||||
Name: authorizedKeySecretName,
|
||||
Key: authorizedKeySecretKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createK8sSecret(ctx context.Context, k8sClient client.Client, namespace, secretName, secretKey string, secretContent interface{}) error {
|
||||
data, err := json.Marshal(secretContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = k8sClient.Create(ctx, &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: secretName,
|
||||
},
|
||||
Data: map[string][]byte{secretKey: data},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newFakeAuthorizedKey() *iamkey.Key {
|
||||
uniqueLabel := uuid.NewString()
|
||||
return &iamkey.Key{
|
||||
Id: uniqueLabel,
|
||||
Subject: &iamkey.Key_ServiceAccountId{
|
||||
ServiceAccountId: uniqueLabel,
|
||||
},
|
||||
PrivateKey: uniqueLabel,
|
||||
}
|
||||
}
|
||||
|
||||
func textEntry(key, value string) *lockbox.Payload_Entry {
|
||||
return &lockbox.Payload_Entry{
|
||||
Key: key,
|
||||
Value: &lockbox.Payload_Entry_TextValue{
|
||||
TextValue: value,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func binaryEntry(key string, value []byte) *lockbox.Payload_Entry {
|
||||
return &lockbox.Payload_Entry{
|
||||
Key: key,
|
||||
Value: &lockbox.Payload_Entry_BinaryValue{
|
||||
BinaryValue: value,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalStringMap(t *testing.T, data []byte) map[string]string {
|
||||
stringMap := make(map[string]string)
|
||||
err := json.Unmarshal(data, &stringMap)
|
||||
tassert.Nil(t, err)
|
||||
return stringMap
|
||||
}
|
||||
|
||||
func base64(data []byte) string {
|
||||
return b64.StdEncoding.EncodeToString(data)
|
||||
}
|
Loading…
Reference in a new issue