1
0
Fork 0
mirror of https://github.com/external-secrets/external-secrets.git synced 2024-12-14 11:57:59 +00:00

WIP: GetAllSecrets for vault method

Signed-off-by: Gustavo Carvalho <gustavo.carvalho@container-solutions.com>
This commit is contained in:
Gustavo Carvalho 2022-02-23 15:05:23 -03:00
parent 439ecfaf9d
commit 0086fe2342
2 changed files with 351 additions and 2 deletions

View file

@ -24,6 +24,7 @@ import (
"io/ioutil"
"net/http"
"os"
"regexp"
"strconv"
"strings"
@ -229,9 +230,129 @@ func (c *connector) ValidateStore(store esv1beta1.GenericStore) error {
}
// Empty GetAllSecrets.
// GetAllSecrets
// First load all secrets from secretStore path configuration
// Then, gets secrets from a matching name or matching custom_metadata
func (v *client) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
// TO be implemented
return nil, fmt.Errorf("GetAllSecrets not implemented")
potentialSecrets, err := v.listSecrets(ctx, "")
if err != nil {
return nil, err
}
if ref.Name != nil {
return v.findSecretsFromName(ctx, potentialSecrets, *ref.Name)
}
return v.findSecretsFromTags(ctx, potentialSecrets, ref.Tags)
}
func (v *client) findSecretsFromTags(ctx context.Context, candidates []string, tags map[string]string) (map[string][]byte, error) {
secrets := make(map[string][]byte)
for _, name := range candidates {
match := true
metadata, err := v.readSecretMetadata(ctx, name)
if err != nil {
return nil, err
}
for tk, tv := range tags {
p, ok := metadata[tk]
if !ok || p != tv {
match = false
break
}
}
if match {
secret, err := v.GetSecret(ctx, esv1beta1.ExternalSecretDataRemoteRef{Key: name})
if err != nil {
return nil, err
}
newName := strings.ReplaceAll(name, "/", "-")
secrets[newName] = secret
}
}
return secrets, nil
}
func (v *client) findSecretsFromName(ctx context.Context, candidates []string, ref esv1beta1.FindName) (map[string][]byte, error) {
secrets := make(map[string][]byte)
for _, name := range candidates {
ok, err := regexp.MatchString(ref.RegExp, name)
if err != nil {
return nil, err
}
if ok {
secret, err := v.GetSecret(ctx, esv1beta1.ExternalSecretDataRemoteRef{Key: name})
if err != nil {
return nil, err
}
newName := strings.ReplaceAll(name, "/", "-")
secrets[newName] = secret
}
}
return secrets, nil
}
func (v *client) listSecrets(ctx context.Context, path string) ([]string, error) {
secrets := make([]string, 0)
url := "/v1/" + *v.store.Path + "/metadata/" + path
r := v.client.NewRequest(http.MethodGet, url)
r.Params.Set("list", "true")
resp, err := v.client.RawRequestWithContext(ctx, r)
if err != nil {
return nil, fmt.Errorf(errReadSecret, err)
}
secret, parseErr := vault.ParseSecret(resp.Body)
if parseErr != nil {
return nil, parseErr
}
t, ok := secret.Data["keys"]
if !ok {
return nil, nil
}
paths := t.([]interface{})
for _, p := range paths {
strPath := p.(string)
fullPath := path + strPath // because path always ends with a /
if path == "" {
fullPath = strPath
}
// Recurrently find secrets
if strings.HasSuffix(p.(string), "/") {
var partial = make([]string, 0)
partial, err = v.listSecrets(ctx, fullPath)
if err != nil {
return nil, err
}
secrets = append(secrets, partial...)
} else {
secrets = append(secrets, fullPath)
}
}
return secrets, nil
}
func (v *client) readSecretMetadata(ctx context.Context, path string) (map[string]string, error) {
metadata := make(map[string]string)
url := "/v1/" + *v.store.Path + "/metadata/" + path
r := v.client.NewRequest(http.MethodGet, url)
resp, err := v.client.RawRequestWithContext(ctx, r)
if err != nil {
return nil, fmt.Errorf(errReadSecret, err)
}
secret, parseErr := vault.ParseSecret(resp.Body)
if parseErr != nil {
return nil, parseErr
}
t, ok := secret.Data["custom_metadata"]
if !ok {
return nil, nil
}
d, ok := t.(map[string]interface{})
if !ok {
return metadata, nil
}
for k, v := range d {
metadata[k] = v.(string)
}
return metadata, nil
}
// GetSecret supports two types:

View file

@ -983,6 +983,234 @@ func TestGetSecretMap(t *testing.T) {
}
}
func TestGetAllSecrets(t *testing.T) {
errBoom := errors.New("boom")
secret := map[string]interface{}{
"access_key": "access_key",
"access_secret": "access_secret",
}
secondSecret := map[string]interface{}{
"access_key": "access_key2",
"access_secret": "access_secret2",
"token": nil,
}
type args struct {
store *esv1beta1.VaultProvider
kube kclient.Client
vClient Client
ns string
data esv1beta1.ExternalSecretDataRemoteRef
}
type want struct {
err error
val map[string][]byte
}
cases := map[string]struct {
reason string
args args
want want
}{
"ReadSecretKV2": {
reason: "Should Map the Secret with DataFrom.Find.Name",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
vClient: &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
newVaultResponseWithData(secret), nil,
),
},
},
want: want{
err: nil,
val: map[string][]byte{
"access_key": []byte("access_key"),
"access_secret": []byte("access_secret"),
},
},
},
"ReadSecretKV2": {
reason: "Should map the secret even if it has a nil value",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
vClient: &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
newVaultResponseWithData(
map[string]interface{}{
"data": secret,
},
), nil,
),
},
},
want: want{
err: nil,
val: map[string][]byte{
"access_key": []byte("access_key"),
"access_secret": []byte("access_secret"),
},
},
},
"ReadSecretWithNilValueKV1": {
reason: "Should map the secret even if it has a nil value",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
vClient: &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
newVaultResponseWithData(secretWithNilVal), nil,
),
},
},
want: want{
err: nil,
val: map[string][]byte{
"access_key": []byte("access_key"),
"access_secret": []byte("access_secret"),
"token": []byte(nil),
},
},
},
"ReadSecretWithNilValueKV2": {
reason: "Should map the secret even if it has a nil value",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
vClient: &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
newVaultResponseWithData(
map[string]interface{}{
"data": secretWithNilVal,
},
), nil,
),
},
},
want: want{
err: nil,
val: map[string][]byte{
"access_key": []byte("access_key"),
"access_secret": []byte("access_secret"),
"token": []byte(nil),
},
},
},
"ReadSecretWithTypesKV2": {
reason: "Should map the secret even if it has other types",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
vClient: &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
newVaultResponseWithData(
map[string]interface{}{
"data": secretWithTypes,
},
), nil,
),
},
},
want: want{
err: nil,
val: map[string][]byte{
"access_secret": []byte("access_secret"),
"f32": []byte("2.12"),
"f64": []byte("2.1234534153423423"),
"int": []byte("42"),
"bool": []byte("true"),
"bt": []byte("Zm9vYmFy"), // base64
},
},
},
"ReadNestedSecret": {
reason: "Should map the secret for deeply nested property",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
data: esv1beta1.ExternalSecretDataRemoteRef{
Property: "nested",
},
vClient: &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
newVaultResponseWithData(
map[string]interface{}{
"data": secretWithNestedVal,
},
), nil,
),
},
},
want: want{
err: nil,
val: map[string][]byte{
"foo": []byte(`{"mhkeih":"yada yada","oke":"yup"}`),
},
},
},
"ReadDeeplyNestedSecret": {
reason: "Should map the secret for deeply nested property",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
data: esv1beta1.ExternalSecretDataRemoteRef{
Property: "nested.foo",
},
vClient: &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
newVaultResponseWithData(
map[string]interface{}{
"data": secretWithNestedVal,
},
), nil,
),
},
},
want: want{
err: nil,
val: map[string][]byte{
"oke": []byte("yup"),
"mhkeih": []byte("yada yada"),
},
},
},
"ReadSecretError": {
reason: "Should return error if vault client fails to read secret.",
args: args{
store: makeSecretStore().Spec.Provider.Vault,
vClient: &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(nil, errBoom),
},
},
want: want{
err: fmt.Errorf(errReadSecret, errBoom),
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
vStore := &client{
kube: tc.args.kube,
client: tc.args.vClient,
store: tc.args.store,
namespace: tc.args.ns,
}
val, err := vStore.GetSecretMap(context.Background(), tc.args.data)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nvault.GetSecretMap(...): -want error, +got error:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(tc.want.val, val); diff != "" {
t.Errorf("\n%s\nvault.GetSecretMap(...): -want val, +got val:\n%s", tc.reason, diff)
}
})
}
}
func TestGetSecretPath(t *testing.T) {
storeV2 := makeValidSecretStore()
storeV2NoPath := storeV2.DeepCopy()