mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-15 17:51:01 +00:00
Merge branch 'main' into feature/conversion-webhook
Signed-off-by: Gustavo Carvalho <gustavo.carvalho@container-solutions.com>
This commit is contained in:
commit
82ddeb9de5
4 changed files with 547 additions and 55 deletions
|
@ -71,9 +71,92 @@ data:
|
||||||
foobar: czNjcjN0
|
foobar: czNjcjN0
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Limitations
|
#### Fetching Raw Values
|
||||||
|
|
||||||
Vault supports only simple key/value pairs - nested objects are not supported. Hence specifying `gjson` properties like other providers support it is not supported.
|
You can fetch all key/value pairs for a given path If you leave the `remoteRef.property` empty. This returns the json-encoded secret value for that path.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: external-secrets.io/v1alpha1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: vault-example
|
||||||
|
spec:
|
||||||
|
# ...
|
||||||
|
data:
|
||||||
|
- secretKey: foobar
|
||||||
|
remoteRef:
|
||||||
|
key: /dev/package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Nested Values
|
||||||
|
|
||||||
|
Vault supports nested key/value pairs. You can specify a [gjson](https://github.com/tidwall/gjson) expression at `remoteRef.property` to get a nested value.
|
||||||
|
|
||||||
|
Given the following secret - assume its path is `/dev/config`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"foo": {
|
||||||
|
"nested": {
|
||||||
|
"bar": "mysecret"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can set the `remoteRef.property` to point to the nested key using a [gjson](https://github.com/tidwall/gjson) expression.
|
||||||
|
```yaml
|
||||||
|
apiVersion: external-secrets.io/v1alpha1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: vault-example
|
||||||
|
spec:
|
||||||
|
# ...
|
||||||
|
data:
|
||||||
|
- secretKey: foobar
|
||||||
|
remoteRef:
|
||||||
|
key: /dev/config
|
||||||
|
property: foo.nested.bar
|
||||||
|
---
|
||||||
|
# creates a secret with:
|
||||||
|
# foobar=mysecret
|
||||||
|
```
|
||||||
|
|
||||||
|
If you would set the `remoteRef.property` to just `foo` then you would get the json-encoded value of that property: `{"nested":{"bar":"mysecret"}}`.
|
||||||
|
|
||||||
|
#### Multiple nested Values
|
||||||
|
|
||||||
|
You can extract multiple keys from a nested secret using `dataFrom`.
|
||||||
|
|
||||||
|
Given the following secret - assume its path is `/dev/config`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"foo": {
|
||||||
|
"nested": {
|
||||||
|
"bar": "mysecret",
|
||||||
|
"baz": "bang"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can set the `remoteRef.property` to point to the nested key using a [gjson](https://github.com/tidwall/gjson) expression.
|
||||||
|
```yaml
|
||||||
|
apiVersion: external-secrets.io/v1alpha1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: vault-example
|
||||||
|
spec:
|
||||||
|
# ...
|
||||||
|
dataFrom:
|
||||||
|
- key: /dev/config
|
||||||
|
property: foo.nested
|
||||||
|
```
|
||||||
|
|
||||||
|
That results in a secret with these values:
|
||||||
|
```
|
||||||
|
bar=mysecret
|
||||||
|
baz=bang
|
||||||
|
```
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,13 @@ limitations under the License.
|
||||||
package vault
|
package vault
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
// nolint
|
// nolint
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
|
||||||
|
esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
||||||
"github.com/external-secrets/external-secrets/e2e/framework"
|
"github.com/external-secrets/external-secrets/e2e/framework"
|
||||||
"github.com/external-secrets/external-secrets/e2e/suite/common"
|
"github.com/external-secrets/external-secrets/e2e/suite/common"
|
||||||
)
|
)
|
||||||
|
@ -72,6 +75,11 @@ var _ = Describe("[vault]", Label("vault"), func() {
|
||||||
framework.Compose(withK8s, f, common.JSONDataWithTemplate, useKubernetesProvider),
|
framework.Compose(withK8s, f, common.JSONDataWithTemplate, useKubernetesProvider),
|
||||||
framework.Compose(withK8s, f, common.DataPropertyDockerconfigJSON, useKubernetesProvider),
|
framework.Compose(withK8s, f, common.DataPropertyDockerconfigJSON, useKubernetesProvider),
|
||||||
framework.Compose(withK8s, f, common.JSONDataWithoutTargetName, useKubernetesProvider),
|
framework.Compose(withK8s, f, common.JSONDataWithoutTargetName, useKubernetesProvider),
|
||||||
|
// vault-specific test cases
|
||||||
|
Entry("secret value via data without property should return json-encoded string", Label("json"), testJSONWithoutProperty),
|
||||||
|
Entry("secret value via data with property should return json-encoded string", Label("json"), testJSONWithProperty),
|
||||||
|
Entry("dataFrom without property should extract key/value pairs", Label("json"), testDataFromJSONWithoutProperty),
|
||||||
|
Entry("dataFrom with property should extract key/value pairs", Label("json"), testDataFromJSONWithProperty),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -98,3 +106,92 @@ func useJWTProvider(tc *framework.TestCase) {
|
||||||
func useKubernetesProvider(tc *framework.TestCase) {
|
func useKubernetesProvider(tc *framework.TestCase) {
|
||||||
tc.ExternalSecret.Spec.SecretStoreRef.Name = kubernetesProviderName
|
tc.ExternalSecret.Spec.SecretStoreRef.Name = kubernetesProviderName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const jsonVal = `{"foo":{"nested":{"bar":"mysecret","baz":"bang"}}}`
|
||||||
|
|
||||||
|
// when no property is set it should return the json-encoded at path.
|
||||||
|
func testJSONWithoutProperty(tc *framework.TestCase) {
|
||||||
|
secretKey := fmt.Sprintf("%s-%s", tc.Framework.Namespace.Name, "json")
|
||||||
|
tc.Secrets = map[string]string{
|
||||||
|
secretKey: jsonVal,
|
||||||
|
}
|
||||||
|
tc.ExpectedSecret = &v1.Secret{
|
||||||
|
Type: v1.SecretTypeOpaque,
|
||||||
|
Data: map[string][]byte{
|
||||||
|
secretKey: []byte(jsonVal),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tc.ExternalSecret.Spec.Data = []esapi.ExternalSecretData{
|
||||||
|
{
|
||||||
|
SecretKey: secretKey,
|
||||||
|
RemoteRef: esapi.ExternalSecretDataRemoteRef{
|
||||||
|
Key: secretKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when property is set it should return the json-encoded at path.
|
||||||
|
func testJSONWithProperty(tc *framework.TestCase) {
|
||||||
|
secretKey := fmt.Sprintf("%s-%s", tc.Framework.Namespace.Name, "json")
|
||||||
|
expectedVal := `{"bar":"mysecret","baz":"bang"}`
|
||||||
|
tc.Secrets = map[string]string{
|
||||||
|
secretKey: jsonVal,
|
||||||
|
}
|
||||||
|
tc.ExpectedSecret = &v1.Secret{
|
||||||
|
Type: v1.SecretTypeOpaque,
|
||||||
|
Data: map[string][]byte{
|
||||||
|
secretKey: []byte(expectedVal),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tc.ExternalSecret.Spec.Data = []esapi.ExternalSecretData{
|
||||||
|
{
|
||||||
|
SecretKey: secretKey,
|
||||||
|
RemoteRef: esapi.ExternalSecretDataRemoteRef{
|
||||||
|
Key: secretKey,
|
||||||
|
Property: "foo.nested",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when no property is set it should extract the key/value pairs at the given path
|
||||||
|
// note: it should json-encode if a value contains nested data
|
||||||
|
func testDataFromJSONWithoutProperty(tc *framework.TestCase) {
|
||||||
|
secretKey := fmt.Sprintf("%s-%s", tc.Framework.Namespace.Name, "json")
|
||||||
|
tc.Secrets = map[string]string{
|
||||||
|
secretKey: jsonVal,
|
||||||
|
}
|
||||||
|
tc.ExpectedSecret = &v1.Secret{
|
||||||
|
Type: v1.SecretTypeOpaque,
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"foo": []byte(`{"nested":{"bar":"mysecret","baz":"bang"}}`),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tc.ExternalSecret.Spec.DataFrom = []esapi.ExternalSecretDataRemoteRef{
|
||||||
|
{
|
||||||
|
Key: secretKey,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when property is set it should extract values with dataFrom at the given path.
|
||||||
|
func testDataFromJSONWithProperty(tc *framework.TestCase) {
|
||||||
|
secretKey := fmt.Sprintf("%s-%s", tc.Framework.Namespace.Name, "json")
|
||||||
|
tc.Secrets = map[string]string{
|
||||||
|
secretKey: jsonVal,
|
||||||
|
}
|
||||||
|
tc.ExpectedSecret = &v1.Secret{
|
||||||
|
Type: v1.SecretTypeOpaque,
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"bar": []byte(`mysecret`),
|
||||||
|
"baz": []byte(`bang`),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tc.ExternalSecret.Spec.DataFrom = []esapi.ExternalSecretDataRemoteRef{
|
||||||
|
{
|
||||||
|
Key: secretKey,
|
||||||
|
Property: "foo.nested",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,15 +18,18 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
vault "github.com/hashicorp/vault/api"
|
vault "github.com/hashicorp/vault/api"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
|
@ -165,15 +168,56 @@ func (v *client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretData
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
value, exists := data[ref.Property]
|
|
||||||
if !exists {
|
// return raw json if no property is defined
|
||||||
|
if ref.Property == "" {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
val := gjson.Get(string(data), ref.Property)
|
||||||
|
if !val.Exists() {
|
||||||
return nil, fmt.Errorf(errSecretKeyFmt, ref.Property)
|
return nil, fmt.Errorf(errSecretKeyFmt, ref.Property)
|
||||||
}
|
}
|
||||||
return value, nil
|
return []byte(val.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
func (v *client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||||
return v.readSecret(ctx, ref.Key, ref.Version)
|
data, err := v.GetSecret(ctx, ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var secretData map[string]interface{}
|
||||||
|
err = json.Unmarshal(data, &secretData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
byteMap := make(map[string][]byte, len(secretData))
|
||||||
|
for k, v := range secretData {
|
||||||
|
switch t := v.(type) {
|
||||||
|
case string:
|
||||||
|
byteMap[k] = []byte(t)
|
||||||
|
case map[string]interface{}:
|
||||||
|
jsonData, err := json.Marshal(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
byteMap[k] = jsonData
|
||||||
|
case []byte:
|
||||||
|
byteMap[k] = t
|
||||||
|
// also covers int and float32 due to json.Marshal
|
||||||
|
case float64:
|
||||||
|
byteMap[k] = []byte(strconv.FormatFloat(t, 'f', -1, 64))
|
||||||
|
case bool:
|
||||||
|
byteMap[k] = []byte(strconv.FormatBool(t))
|
||||||
|
case nil:
|
||||||
|
byteMap[k] = []byte(nil)
|
||||||
|
default:
|
||||||
|
return nil, errors.New(errSecretFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return byteMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *client) Close(ctx context.Context) error {
|
func (v *client) Close(ctx context.Context) error {
|
||||||
|
@ -224,7 +268,7 @@ func (v *client) buildPath(path string) string {
|
||||||
return returnPath
|
return returnPath
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *client) readSecret(ctx context.Context, path, version string) (map[string][]byte, error) {
|
func (v *client) readSecret(ctx context.Context, path, version string) ([]byte, error) {
|
||||||
dataPath := v.buildPath(path)
|
dataPath := v.buildPath(path)
|
||||||
|
|
||||||
// path formated according to vault docs for v1 and v2 API
|
// path formated according to vault docs for v1 and v2 API
|
||||||
|
@ -260,21 +304,8 @@ func (v *client) readSecret(ctx context.Context, path, version string) (map[stri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byteMap := make(map[string][]byte, len(secretData))
|
// return json string
|
||||||
for k, v := range secretData {
|
return json.Marshal(secretData)
|
||||||
switch t := v.(type) {
|
|
||||||
case string:
|
|
||||||
byteMap[k] = []byte(t)
|
|
||||||
case []byte:
|
|
||||||
byteMap[k] = t
|
|
||||||
case nil:
|
|
||||||
byteMap[k] = []byte(nil)
|
|
||||||
default:
|
|
||||||
return nil, errors.New(errSecretFormat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return byteMap, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *client) newConfig() (*vault.Config, error) {
|
func (v *client) newConfig() (*vault.Config, error) {
|
||||||
|
|
|
@ -551,7 +551,7 @@ func vaultTest(t *testing.T, name string, tc testCase) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetSecretMap(t *testing.T) {
|
func TestGetSecret(t *testing.T) {
|
||||||
errBoom := errors.New("boom")
|
errBoom := errors.New("boom")
|
||||||
secret := map[string]interface{}{
|
secret := map[string]interface{}{
|
||||||
"access_key": "access_key",
|
"access_key": "access_key",
|
||||||
|
@ -562,6 +562,13 @@ func TestGetSecretMap(t *testing.T) {
|
||||||
"access_secret": "access_secret",
|
"access_secret": "access_secret",
|
||||||
"token": nil,
|
"token": nil,
|
||||||
}
|
}
|
||||||
|
secretWithNestedVal := map[string]interface{}{
|
||||||
|
"access_key": "access_key",
|
||||||
|
"access_secret": "access_secret",
|
||||||
|
"nested": map[string]string{
|
||||||
|
"foo": "oke",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
store *esv1beta1.VaultProvider
|
store *esv1beta1.VaultProvider
|
||||||
|
@ -573,6 +580,7 @@ func TestGetSecretMap(t *testing.T) {
|
||||||
|
|
||||||
type want struct {
|
type want struct {
|
||||||
err error
|
err error
|
||||||
|
val []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
cases := map[string]struct {
|
cases := map[string]struct {
|
||||||
|
@ -580,10 +588,13 @@ func TestGetSecretMap(t *testing.T) {
|
||||||
args args
|
args args
|
||||||
want want
|
want want
|
||||||
}{
|
}{
|
||||||
"ReadSecretKV1": {
|
"ReadSecret": {
|
||||||
reason: "Should map the secret even if it has a nil value",
|
reason: "Should return the secret with property",
|
||||||
args: args{
|
args: args{
|
||||||
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
|
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
|
||||||
|
data: esv1beta1.ExternalSecretDataRemoteRef{
|
||||||
|
Property: "access_key",
|
||||||
|
},
|
||||||
vClient: &fake.VaultClient{
|
vClient: &fake.VaultClient{
|
||||||
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
|
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
|
||||||
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
|
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
|
||||||
|
@ -593,31 +604,16 @@ func TestGetSecretMap(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: want{
|
want: want{
|
||||||
err: nil,
|
err: nil,
|
||||||
|
val: []byte("access_key"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"ReadSecretKV2": {
|
"ReadSecretWithNil": {
|
||||||
reason: "Should map the secret even if it has a nil value",
|
reason: "Should return the secret with property if it has a nil val",
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"ReadSecretWithNilValueKV1": {
|
|
||||||
reason: "Should map the secret even if it has a nil value",
|
|
||||||
args: args{
|
args: args{
|
||||||
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
|
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
|
||||||
|
data: esv1beta1.ExternalSecretDataRemoteRef{
|
||||||
|
Property: "access_key",
|
||||||
|
},
|
||||||
vClient: &fake.VaultClient{
|
vClient: &fake.VaultClient{
|
||||||
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
|
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
|
||||||
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
|
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
|
||||||
|
@ -627,25 +623,61 @@ func TestGetSecretMap(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: want{
|
want: want{
|
||||||
err: nil,
|
err: nil,
|
||||||
|
val: []byte("access_key"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"ReadSecretWithNilValueKV2": {
|
"ReadSecretWithoutProperty": {
|
||||||
reason: "Should map the secret even if it has a nil value",
|
reason: "Should return the json encoded secret without property",
|
||||||
args: args{
|
args: args{
|
||||||
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV2).Spec.Provider.Vault,
|
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
|
||||||
|
data: esv1beta1.ExternalSecretDataRemoteRef{},
|
||||||
vClient: &fake.VaultClient{
|
vClient: &fake.VaultClient{
|
||||||
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
|
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
|
||||||
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
|
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
|
||||||
newVaultResponseWithData(
|
newVaultResponseWithData(secret), nil,
|
||||||
map[string]interface{}{
|
|
||||||
"data": secretWithNilVal,
|
|
||||||
},
|
|
||||||
), nil,
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: want{
|
want: want{
|
||||||
err: nil,
|
err: nil,
|
||||||
|
val: []byte(`{"access_key":"access_key","access_secret":"access_secret"}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ReadSecretWithNestedValue": {
|
||||||
|
reason: "Should return a nested property",
|
||||||
|
args: args{
|
||||||
|
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
|
||||||
|
data: esv1beta1.ExternalSecretDataRemoteRef{
|
||||||
|
Property: "nested.foo",
|
||||||
|
},
|
||||||
|
vClient: &fake.VaultClient{
|
||||||
|
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
|
||||||
|
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
|
||||||
|
newVaultResponseWithData(secretWithNestedVal), nil,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
err: nil,
|
||||||
|
val: []byte("oke"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"NonexistentProperty": {
|
||||||
|
reason: "Should return error property does not exist.",
|
||||||
|
args: args{
|
||||||
|
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
|
||||||
|
data: esv1beta1.ExternalSecretDataRemoteRef{
|
||||||
|
Property: "nop.doesnt.exist",
|
||||||
|
},
|
||||||
|
vClient: &fake.VaultClient{
|
||||||
|
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
|
||||||
|
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
|
||||||
|
newVaultResponseWithData(secretWithNestedVal), nil,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
err: fmt.Errorf(errSecretKeyFmt, "nop.doesnt.exist"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"ReadSecretError": {
|
"ReadSecretError": {
|
||||||
|
@ -671,10 +703,259 @@ func TestGetSecretMap(t *testing.T) {
|
||||||
store: tc.args.store,
|
store: tc.args.store,
|
||||||
namespace: tc.args.ns,
|
namespace: tc.args.ns,
|
||||||
}
|
}
|
||||||
_, err := vStore.GetSecretMap(context.Background(), tc.args.data)
|
val, err := vStore.GetSecret(context.Background(), tc.args.data)
|
||||||
|
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||||
|
t.Errorf("\n%s\nvault.GetSecret(...): -want error, +got error:\n%s", tc.reason, diff)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(string(tc.want.val), string(val)); diff != "" {
|
||||||
|
t.Errorf("\n%s\nvault.GetSecret(...): -want val, +got val:\n%s", tc.reason, diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSecretMap(t *testing.T) {
|
||||||
|
errBoom := errors.New("boom")
|
||||||
|
secret := map[string]interface{}{
|
||||||
|
"access_key": "access_key",
|
||||||
|
"access_secret": "access_secret",
|
||||||
|
}
|
||||||
|
secretWithNilVal := map[string]interface{}{
|
||||||
|
"access_key": "access_key",
|
||||||
|
"access_secret": "access_secret",
|
||||||
|
"token": nil,
|
||||||
|
}
|
||||||
|
secretWithNestedVal := map[string]interface{}{
|
||||||
|
"access_key": "access_key",
|
||||||
|
"access_secret": "access_secret",
|
||||||
|
"nested": map[string]interface{}{
|
||||||
|
"foo": map[string]string{
|
||||||
|
"oke": "yup",
|
||||||
|
"mhkeih": "yada yada",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
secretWithTypes := map[string]interface{}{
|
||||||
|
"access_secret": "access_secret",
|
||||||
|
"f32": float32(2.12),
|
||||||
|
"f64": float64(2.1234534153423423),
|
||||||
|
"int": 42,
|
||||||
|
"bool": true,
|
||||||
|
"bt": []byte("foobar"),
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}{
|
||||||
|
"ReadSecretKV1": {
|
||||||
|
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(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 != "" {
|
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)
|
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)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue