mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
bdf437c2e1
* Add support for Delinea DevOps Secrets Vault Closes #1709. Signed-off-by: Michael Sauter <michael.sauter@boehringer-ingelheim.com> * fix: remove merge conflict Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> * Improve documentation Signed-off-by: Michael Sauter <michael.sauter@boehringer-ingelheim.com> --------- Signed-off-by: Michael Sauter <michael.sauter@boehringer-ingelheim.com> Signed-off-by: Moritz Johner <beller.moritz@googlemail.com> Co-authored-by: Moritz Johner <beller.moritz@googlemail.com> Co-authored-by: Moritz Johner <moolen@users.noreply.github.com>
369 lines
12 KiB
Go
369 lines
12 KiB
Go
/*
|
|
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 delinea
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/DelineaXPM/dsv-sdk-go/v2/vault"
|
|
"github.com/stretchr/testify/assert"
|
|
corev1 "k8s.io/api/core/v1"
|
|
kubeErrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
kubeClient "sigs.k8s.io/controller-runtime/pkg/client"
|
|
clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
|
|
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
|
v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
|
|
"github.com/external-secrets/external-secrets/pkg/utils"
|
|
)
|
|
|
|
func TestDoesConfigDependOnNamespace(t *testing.T) {
|
|
tests := map[string]struct {
|
|
cfg esv1beta1.DelineaProvider
|
|
want bool
|
|
}{
|
|
"true when client ID references a secret without explicit namespace": {
|
|
cfg: esv1beta1.DelineaProvider{
|
|
ClientID: &esv1beta1.DelineaProviderSecretRef{
|
|
SecretRef: &v1.SecretKeySelector{Name: "foo"},
|
|
},
|
|
ClientSecret: &esv1beta1.DelineaProviderSecretRef{SecretRef: nil},
|
|
},
|
|
want: true,
|
|
},
|
|
"true when client secret references a secret without explicit namespace": {
|
|
cfg: esv1beta1.DelineaProvider{
|
|
ClientID: &esv1beta1.DelineaProviderSecretRef{SecretRef: nil},
|
|
ClientSecret: &esv1beta1.DelineaProviderSecretRef{
|
|
SecretRef: &v1.SecretKeySelector{Name: "foo"},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
"false when neither client ID nor secret reference a secret": {
|
|
cfg: esv1beta1.DelineaProvider{
|
|
ClientID: &esv1beta1.DelineaProviderSecretRef{SecretRef: nil},
|
|
ClientSecret: &esv1beta1.DelineaProviderSecretRef{SecretRef: nil},
|
|
},
|
|
want: false,
|
|
},
|
|
}
|
|
for name, tc := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
got := doesConfigDependOnNamespace(&tc.cfg)
|
|
assert.Equal(t, tc.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateStore(t *testing.T) {
|
|
validSecretRefUsingValue := makeSecretRefUsingValue("foo")
|
|
ambiguousSecretRef := &esv1beta1.DelineaProviderSecretRef{
|
|
SecretRef: &v1.SecretKeySelector{Name: "foo"}, Value: "foo",
|
|
}
|
|
tests := map[string]struct {
|
|
cfg esv1beta1.DelineaProvider
|
|
want error
|
|
}{
|
|
"invalid without tenant": {
|
|
cfg: esv1beta1.DelineaProvider{
|
|
Tenant: "",
|
|
ClientID: validSecretRefUsingValue,
|
|
ClientSecret: validSecretRefUsingValue,
|
|
},
|
|
want: errEmptyTenant,
|
|
},
|
|
"invalid without clientID": {
|
|
cfg: esv1beta1.DelineaProvider{
|
|
Tenant: "foo",
|
|
// ClientID omitted
|
|
ClientSecret: validSecretRefUsingValue,
|
|
},
|
|
want: errEmptyClientID,
|
|
},
|
|
"invalid without clientSecret": {
|
|
cfg: esv1beta1.DelineaProvider{
|
|
Tenant: "foo",
|
|
ClientID: validSecretRefUsingValue,
|
|
// ClientSecret omitted
|
|
},
|
|
want: errEmptyClientSecret,
|
|
},
|
|
"invalid with ambiguous clientID": {
|
|
cfg: esv1beta1.DelineaProvider{
|
|
Tenant: "foo",
|
|
ClientID: ambiguousSecretRef,
|
|
ClientSecret: validSecretRefUsingValue,
|
|
},
|
|
want: errSecretRefAndValueConflict,
|
|
},
|
|
"invalid with ambiguous clientSecret": {
|
|
cfg: esv1beta1.DelineaProvider{
|
|
Tenant: "foo",
|
|
ClientID: validSecretRefUsingValue,
|
|
ClientSecret: ambiguousSecretRef,
|
|
},
|
|
want: errSecretRefAndValueConflict,
|
|
},
|
|
"invalid with invalid clientID": {
|
|
cfg: esv1beta1.DelineaProvider{
|
|
Tenant: "foo",
|
|
ClientID: makeSecretRefUsingValue(""),
|
|
ClientSecret: validSecretRefUsingValue,
|
|
},
|
|
want: errSecretRefAndValueMissing,
|
|
},
|
|
"invalid with invalid clientSecret": {
|
|
cfg: esv1beta1.DelineaProvider{
|
|
Tenant: "foo",
|
|
ClientID: validSecretRefUsingValue,
|
|
ClientSecret: makeSecretRefUsingValue(""),
|
|
},
|
|
want: errSecretRefAndValueMissing,
|
|
},
|
|
"valid with tenant/clientID/clientSecret": {
|
|
cfg: esv1beta1.DelineaProvider{
|
|
Tenant: "foo",
|
|
ClientID: validSecretRefUsingValue,
|
|
ClientSecret: validSecretRefUsingValue,
|
|
},
|
|
want: nil,
|
|
},
|
|
}
|
|
for name, tc := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
s := esv1beta1.SecretStore{
|
|
Spec: esv1beta1.SecretStoreSpec{
|
|
Provider: &esv1beta1.SecretStoreProvider{
|
|
Delinea: &tc.cfg,
|
|
},
|
|
},
|
|
}
|
|
p := &Provider{}
|
|
got := p.ValidateStore(&s)
|
|
assert.Equal(t, tc.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateStoreBailsOnUnexpectedStore(t *testing.T) {
|
|
tests := map[string]struct {
|
|
store esv1beta1.GenericStore
|
|
want error
|
|
}{
|
|
"missing store": {nil, errMissingStore},
|
|
"missing spec": {&esv1beta1.SecretStore{}, errInvalidSpec},
|
|
"missing provider": {&esv1beta1.SecretStore{
|
|
Spec: esv1beta1.SecretStoreSpec{Provider: nil},
|
|
}, errInvalidSpec},
|
|
"missing delinea": {&esv1beta1.SecretStore{
|
|
Spec: esv1beta1.SecretStoreSpec{Provider: &esv1beta1.SecretStoreProvider{
|
|
Delinea: nil,
|
|
}},
|
|
}, errInvalidSpec},
|
|
}
|
|
for name, tc := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
p := &Provider{}
|
|
got := p.ValidateStore(tc.store)
|
|
assert.Equal(t, tc.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewClient(t *testing.T) {
|
|
tenant := "foo"
|
|
clientIDKey := "username"
|
|
clientIDValue := "client id"
|
|
clientSecretKey := "password"
|
|
clientSecretValue := "client secret"
|
|
|
|
clientSecret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
|
|
Data: map[string][]byte{
|
|
clientIDKey: []byte(clientIDValue),
|
|
clientSecretKey: []byte(clientSecretValue),
|
|
},
|
|
}
|
|
|
|
validProvider := &esv1beta1.DelineaProvider{
|
|
Tenant: tenant,
|
|
ClientID: makeSecretRefUsingRef(clientSecret.Name, clientIDKey),
|
|
ClientSecret: makeSecretRefUsingRef(clientSecret.Name, clientSecretKey),
|
|
}
|
|
|
|
tests := map[string]struct {
|
|
store esv1beta1.GenericStore // leave nil for namespaced store
|
|
provider *esv1beta1.DelineaProvider // discarded when store is set
|
|
kube kubeClient.Client
|
|
errCheck func(t *testing.T, err error)
|
|
}{
|
|
"missing provider config": {
|
|
provider: nil,
|
|
errCheck: func(t *testing.T, err error) {
|
|
assert.ErrorIs(t, err, errInvalidSpec)
|
|
},
|
|
},
|
|
"namespace-dependent cluster secret store": {
|
|
store: &esv1beta1.ClusterSecretStore{
|
|
TypeMeta: metav1.TypeMeta{Kind: esv1beta1.ClusterSecretStoreKind},
|
|
Spec: esv1beta1.SecretStoreSpec{
|
|
Provider: &esv1beta1.SecretStoreProvider{
|
|
Delinea: validProvider,
|
|
},
|
|
},
|
|
},
|
|
errCheck: func(t *testing.T, err error) {
|
|
assert.ErrorIs(t, err, errClusterStoreRequiresNamespace)
|
|
},
|
|
},
|
|
"dangling client ID ref": {
|
|
provider: &esv1beta1.DelineaProvider{
|
|
Tenant: tenant,
|
|
ClientID: makeSecretRefUsingRef("typo", clientIDKey),
|
|
ClientSecret: makeSecretRefUsingRef(clientSecret.Name, clientSecretKey),
|
|
},
|
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
|
errCheck: func(t *testing.T, err error) {
|
|
assert.True(t, kubeErrors.IsNotFound(err))
|
|
},
|
|
},
|
|
"dangling client secret ref": {
|
|
provider: &esv1beta1.DelineaProvider{
|
|
Tenant: tenant,
|
|
ClientID: makeSecretRefUsingRef(clientSecret.Name, clientIDKey),
|
|
ClientSecret: makeSecretRefUsingRef("typo", clientSecretKey),
|
|
},
|
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
|
errCheck: func(t *testing.T, err error) {
|
|
assert.True(t, kubeErrors.IsNotFound(err))
|
|
},
|
|
},
|
|
"secret ref without name": {
|
|
provider: &esv1beta1.DelineaProvider{
|
|
Tenant: tenant,
|
|
ClientID: makeSecretRefUsingRef("", clientIDKey),
|
|
ClientSecret: makeSecretRefUsingRef(clientSecret.Name, clientSecretKey),
|
|
},
|
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
|
errCheck: func(t *testing.T, err error) {
|
|
assert.ErrorIs(t, err, errMissingSecretName)
|
|
},
|
|
},
|
|
"secret ref without key": {
|
|
provider: &esv1beta1.DelineaProvider{
|
|
Tenant: tenant,
|
|
ClientID: makeSecretRefUsingRef(clientSecret.Name, ""),
|
|
ClientSecret: makeSecretRefUsingRef(clientSecret.Name, clientSecretKey),
|
|
},
|
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
|
errCheck: func(t *testing.T, err error) {
|
|
assert.ErrorIs(t, err, errMissingSecretKey)
|
|
},
|
|
},
|
|
"secret ref with non-existent keys": {
|
|
provider: &esv1beta1.DelineaProvider{
|
|
Tenant: tenant,
|
|
ClientID: makeSecretRefUsingRef(clientSecret.Name, "typo"),
|
|
ClientSecret: makeSecretRefUsingRef(clientSecret.Name, clientSecretKey),
|
|
},
|
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
|
errCheck: func(t *testing.T, err error) {
|
|
assert.EqualError(t, err, fmt.Sprintf(errNoSuchKeyFmt, "typo"))
|
|
},
|
|
},
|
|
"valid secret refs": {
|
|
provider: validProvider,
|
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
|
},
|
|
"secret values": {
|
|
provider: &esv1beta1.DelineaProvider{
|
|
Tenant: tenant,
|
|
ClientID: makeSecretRefUsingValue(clientIDValue),
|
|
ClientSecret: makeSecretRefUsingValue(clientSecretValue),
|
|
},
|
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
|
},
|
|
"cluster secret store": {
|
|
store: &esv1beta1.ClusterSecretStore{
|
|
TypeMeta: metav1.TypeMeta{Kind: esv1beta1.ClusterSecretStoreKind},
|
|
Spec: esv1beta1.SecretStoreSpec{
|
|
Provider: &esv1beta1.SecretStoreProvider{
|
|
Delinea: &esv1beta1.DelineaProvider{
|
|
Tenant: tenant,
|
|
ClientID: makeSecretRefUsingNamespacedRef(clientSecret.Namespace, clientSecret.Name, clientIDKey),
|
|
ClientSecret: makeSecretRefUsingNamespacedRef(clientSecret.Namespace, clientSecret.Name, clientSecretKey),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
|
|
},
|
|
}
|
|
for name, tc := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
p := &Provider{}
|
|
store := tc.store
|
|
if store == nil {
|
|
store = &esv1beta1.SecretStore{
|
|
TypeMeta: metav1.TypeMeta{Kind: esv1beta1.SecretStoreKind},
|
|
Spec: esv1beta1.SecretStoreSpec{
|
|
Provider: &esv1beta1.SecretStoreProvider{
|
|
Delinea: tc.provider,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
sc, err := p.NewClient(context.Background(), store, tc.kube, clientSecret.Namespace)
|
|
if tc.errCheck == nil {
|
|
assert.NoError(t, err)
|
|
delineaClient, ok := sc.(*client)
|
|
assert.True(t, ok)
|
|
dsvClient, ok := delineaClient.api.(*vault.Vault)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, vault.Configuration{
|
|
Credentials: vault.ClientCredential{
|
|
ClientID: clientIDValue,
|
|
ClientSecret: clientSecretValue,
|
|
},
|
|
Tenant: tenant,
|
|
TLD: "com", // Default from Delinea
|
|
URLTemplate: "https://%s.secretsvaultcloud.%s/v1/%s%s", // Default from Delinea
|
|
}, dsvClient.Configuration)
|
|
} else {
|
|
assert.Nil(t, sc)
|
|
tc.errCheck(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func makeSecretRefUsingRef(name, key string) *esv1beta1.DelineaProviderSecretRef {
|
|
return &esv1beta1.DelineaProviderSecretRef{
|
|
SecretRef: &v1.SecretKeySelector{Name: name, Key: key},
|
|
}
|
|
}
|
|
|
|
func makeSecretRefUsingNamespacedRef(namespace, name, key string) *esv1beta1.DelineaProviderSecretRef {
|
|
return &esv1beta1.DelineaProviderSecretRef{
|
|
SecretRef: &v1.SecretKeySelector{Namespace: utils.Ptr(namespace), Name: name, Key: key},
|
|
}
|
|
}
|
|
|
|
func makeSecretRefUsingValue(val string) *esv1beta1.DelineaProviderSecretRef {
|
|
return &esv1beta1.DelineaProviderSecretRef{Value: val}
|
|
}
|