|
|
@@ -15,24 +15,34 @@ package gitlab
|
|
|
|
|
|
import (
|
|
|
"context"
|
|
|
+ "encoding/json"
|
|
|
"fmt"
|
|
|
"net/http"
|
|
|
"reflect"
|
|
|
"strings"
|
|
|
"testing"
|
|
|
|
|
|
- gitlab "github.com/xanzy/go-gitlab"
|
|
|
+ "github.com/google/uuid"
|
|
|
+ tassert "github.com/stretchr/testify/assert"
|
|
|
+ "github.com/xanzy/go-gitlab"
|
|
|
+ "github.com/yandex-cloud/go-sdk/iamkey"
|
|
|
+ corev1 "k8s.io/api/core/v1"
|
|
|
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
+ k8sclient "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"
|
|
|
+ esv1meta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
|
|
fakegitlab "github.com/external-secrets/external-secrets/pkg/provider/gitlab/fake"
|
|
|
)
|
|
|
|
|
|
const (
|
|
|
- project = "my-Project"
|
|
|
- username = "user-name"
|
|
|
- userkey = "user-key"
|
|
|
- environment = "prod"
|
|
|
+ project = "my-Project"
|
|
|
+ username = "user-name"
|
|
|
+ userkey = "user-key"
|
|
|
+ environment = "prod"
|
|
|
+ defaultErrorMessage = "[%d] unexpected error: %s, expected: '%s'"
|
|
|
+ errMissingCredentials = "credentials are empty"
|
|
|
)
|
|
|
|
|
|
type secretManagerTestCase struct {
|
|
|
@@ -43,8 +53,8 @@ type secretManagerTestCase struct {
|
|
|
apiOutput *gitlab.ProjectVariable
|
|
|
apiResponse *gitlab.Response
|
|
|
ref *esv1beta1.ExternalSecretDataRemoteRef
|
|
|
+ refFind *esv1beta1.ExternalSecretFind
|
|
|
projectID *string
|
|
|
- environment *string
|
|
|
apiErr error
|
|
|
expectError string
|
|
|
expectedSecret string
|
|
|
@@ -60,8 +70,8 @@ func makeValidSecretManagerTestCase() *secretManagerTestCase {
|
|
|
apiInputKey: makeValidAPIInputKey(),
|
|
|
apiInputEnv: makeValidEnvironment(),
|
|
|
ref: makeValidRef(),
|
|
|
+ refFind: makeValidFindRef(),
|
|
|
projectID: nil,
|
|
|
- environment: nil,
|
|
|
apiOutput: makeValidAPIOutput(),
|
|
|
apiResponse: makeValidAPIResponse(),
|
|
|
apiErr: nil,
|
|
|
@@ -81,6 +91,16 @@ func makeValidRef() *esv1beta1.ExternalSecretDataRemoteRef {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func makeValidFindRef() *esv1beta1.ExternalSecretFind {
|
|
|
+ return &esv1beta1.ExternalSecretFind{}
|
|
|
+}
|
|
|
+
|
|
|
+func makeFindName(regexp string) *esv1beta1.FindName {
|
|
|
+ return &esv1beta1.FindName{
|
|
|
+ RegExp: regexp,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
func makeValidAPIInputProjectID() string {
|
|
|
return "testID"
|
|
|
}
|
|
|
@@ -117,6 +137,17 @@ func makeValidSecretManagerTestCaseCustom(tweaks ...func(smtc *secretManagerTest
|
|
|
return smtc
|
|
|
}
|
|
|
|
|
|
+func makeValidSecretManagerGetAllTestCaseCustom(tweaks ...func(smtc *secretManagerTestCase)) *secretManagerTestCase {
|
|
|
+ smtc := makeValidSecretManagerTestCase()
|
|
|
+ smtc.ref = nil
|
|
|
+ smtc.refFind.Name = makeFindName(".*")
|
|
|
+ for _, fn := range tweaks {
|
|
|
+ fn(smtc)
|
|
|
+ }
|
|
|
+ smtc.mockClient.WithValue(smtc.apiInputProjectID, smtc.apiInputEnv, smtc.apiInputKey, smtc.apiOutput, smtc.apiResponse, smtc.apiErr)
|
|
|
+ return smtc
|
|
|
+}
|
|
|
+
|
|
|
// This case can be shared by both GetSecret and GetSecretMap tests.
|
|
|
// bad case: set apiErr.
|
|
|
var setAPIErr = func(smtc *secretManagerTestCase) {
|
|
|
@@ -149,9 +180,91 @@ var setNilMockClient = func(smtc *secretManagerTestCase) {
|
|
|
smtc.expectError = errUninitializedGitlabProvider
|
|
|
}
|
|
|
|
|
|
+func TestNewClient(t *testing.T) {
|
|
|
+ ctx := context.Background()
|
|
|
+ const namespace = "namespace"
|
|
|
+
|
|
|
+ store := &esv1beta1.SecretStore{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Namespace: namespace,
|
|
|
+ },
|
|
|
+ Spec: esv1beta1.SecretStoreSpec{
|
|
|
+ Provider: &esv1beta1.SecretStoreProvider{
|
|
|
+ Gitlab: &esv1beta1.GitlabProvider{},
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+ provider, err := esv1beta1.GetProvider(store)
|
|
|
+ tassert.Nil(t, err)
|
|
|
+
|
|
|
+ k8sClient := clientfake.NewClientBuilder().Build()
|
|
|
+ secretClient, err := provider.NewClient(context.Background(), store, k8sClient, namespace)
|
|
|
+ tassert.EqualError(t, err, errMissingCredentials)
|
|
|
+ tassert.Nil(t, secretClient)
|
|
|
+
|
|
|
+ store.Spec.Provider.Gitlab.Auth = esv1beta1.GitlabAuth{}
|
|
|
+ secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
|
|
|
+ tassert.EqualError(t, err, errMissingCredentials)
|
|
|
+ tassert.Nil(t, secretClient)
|
|
|
+
|
|
|
+ store.Spec.Provider.Gitlab.Auth.SecretRef = esv1beta1.GitlabSecretRef{}
|
|
|
+ secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
|
|
|
+ tassert.EqualError(t, err, errMissingCredentials)
|
|
|
+ tassert.Nil(t, secretClient)
|
|
|
+
|
|
|
+ store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken = esv1meta.SecretKeySelector{}
|
|
|
+ secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
|
|
|
+ tassert.EqualError(t, err, errMissingCredentials)
|
|
|
+ tassert.Nil(t, secretClient)
|
|
|
+
|
|
|
+ const authorizedKeySecretName = "authorizedKeySecretName"
|
|
|
+ const authorizedKeySecretKey = "authorizedKeySecretKey"
|
|
|
+ store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken.Name = authorizedKeySecretName
|
|
|
+ store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken.Key = authorizedKeySecretKey
|
|
|
+ secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
|
|
|
+ tassert.EqualError(t, err, "couldn't find secret on cluster: secrets \"authorizedKeySecretName\" not found")
|
|
|
+ tassert.Nil(t, secretClient)
|
|
|
+
|
|
|
+ err = createK8sSecret(ctx, t, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, toJSON(t, newFakeAuthorizedKey()))
|
|
|
+ tassert.Nil(t, err)
|
|
|
+
|
|
|
+ secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
|
|
|
+ tassert.Nil(t, err)
|
|
|
+ tassert.NotNil(t, secretClient)
|
|
|
+}
|
|
|
+
|
|
|
+func toJSON(t *testing.T, v interface{}) []byte {
|
|
|
+ jsonBytes, err := json.Marshal(v)
|
|
|
+ tassert.Nil(t, err)
|
|
|
+ return jsonBytes
|
|
|
+}
|
|
|
+
|
|
|
+func createK8sSecret(ctx context.Context, t *testing.T, k8sClient k8sclient.Client, namespace, secretName, secretKey string, secretValue []byte) error {
|
|
|
+ err := k8sClient.Create(ctx, &corev1.Secret{
|
|
|
+ ObjectMeta: metav1.ObjectMeta{
|
|
|
+ Namespace: namespace,
|
|
|
+ Name: secretName,
|
|
|
+ },
|
|
|
+ Data: map[string][]byte{secretKey: secretValue},
|
|
|
+ })
|
|
|
+ tassert.Nil(t, err)
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func newFakeAuthorizedKey() *iamkey.Key {
|
|
|
+ uniqueLabel := uuid.NewString()
|
|
|
+ return &iamkey.Key{
|
|
|
+ Id: uniqueLabel,
|
|
|
+ Subject: &iamkey.Key_ServiceAccountId{
|
|
|
+ ServiceAccountId: uniqueLabel,
|
|
|
+ },
|
|
|
+ PrivateKey: uniqueLabel,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// test the sm<->gcp interface
|
|
|
// make sure correct values are passed and errors are handled accordingly.
|
|
|
-func TestGitlabSecretManagerGetSecret(t *testing.T) {
|
|
|
+func TestGetSecret(t *testing.T) {
|
|
|
secretValue := "changedvalue"
|
|
|
// good case: default version is set
|
|
|
// key is passed in, output is sent back
|
|
|
@@ -175,7 +288,7 @@ func TestGitlabSecretManagerGetSecret(t *testing.T) {
|
|
|
sm.client = v.mockClient
|
|
|
out, err := sm.GetSecret(context.Background(), *v.ref)
|
|
|
if !ErrorContains(err, v.expectError) {
|
|
|
- t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
|
|
|
+ t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
|
|
|
}
|
|
|
if string(out) != v.expectedSecret {
|
|
|
t.Errorf("[%d] unexpected secret: expected %s, got %s", k, v.expectedSecret, string(out))
|
|
|
@@ -183,6 +296,77 @@ func TestGitlabSecretManagerGetSecret(t *testing.T) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func TestGetAllSecrets(t *testing.T) {
|
|
|
+ secretValue := "changedvalue"
|
|
|
+ // good case: default version is set
|
|
|
+ // key is passed in, output is sent back
|
|
|
+
|
|
|
+ setMissingFindRegex := func(smtc *secretManagerTestCase) {
|
|
|
+ smtc.refFind.Name = nil
|
|
|
+ smtc.expectError = "'find.name' is mandatory"
|
|
|
+ }
|
|
|
+ setUnsupportedFindTags := func(smtc *secretManagerTestCase) {
|
|
|
+ smtc.refFind.Tags = map[string]string{}
|
|
|
+ smtc.expectError = "'find.tags' is not currently supported by Gitlab provider"
|
|
|
+ }
|
|
|
+ setUnsupportedFindPath := func(smtc *secretManagerTestCase) {
|
|
|
+ path := "path"
|
|
|
+ smtc.refFind.Path = &path
|
|
|
+ smtc.expectError = "'find.path' is not implemented in the Gitlab provider"
|
|
|
+ }
|
|
|
+ setMatchingSecretFindString := func(smtc *secretManagerTestCase) {
|
|
|
+ smtc.apiOutput = &gitlab.ProjectVariable{
|
|
|
+ Key: "testkey",
|
|
|
+ Value: "changedvalue",
|
|
|
+ EnvironmentScope: "test",
|
|
|
+ }
|
|
|
+ smtc.expectedSecret = secretValue
|
|
|
+ smtc.refFind.Name = makeFindName("test.*")
|
|
|
+ }
|
|
|
+ setNoMatchingRegexpFindString := func(smtc *secretManagerTestCase) {
|
|
|
+ smtc.apiOutput = &gitlab.ProjectVariable{
|
|
|
+ Key: "testkey",
|
|
|
+ Value: "changedvalue",
|
|
|
+ EnvironmentScope: "test",
|
|
|
+ }
|
|
|
+ smtc.expectedSecret = ""
|
|
|
+ smtc.refFind.Name = makeFindName("foo.*")
|
|
|
+ }
|
|
|
+ setUnmatchedEnvironmentFindString := func(smtc *secretManagerTestCase) {
|
|
|
+ smtc.apiOutput = &gitlab.ProjectVariable{
|
|
|
+ Key: "testkey",
|
|
|
+ Value: "changedvalue",
|
|
|
+ EnvironmentScope: "prod",
|
|
|
+ }
|
|
|
+ smtc.expectedSecret = ""
|
|
|
+ smtc.refFind.Name = makeFindName("test.*")
|
|
|
+ }
|
|
|
+
|
|
|
+ cases := []*secretManagerTestCase{
|
|
|
+ makeValidSecretManagerGetAllTestCaseCustom(setMissingFindRegex),
|
|
|
+ makeValidSecretManagerGetAllTestCaseCustom(setUnsupportedFindTags),
|
|
|
+ makeValidSecretManagerGetAllTestCaseCustom(setUnsupportedFindPath),
|
|
|
+ makeValidSecretManagerGetAllTestCaseCustom(setMatchingSecretFindString),
|
|
|
+ makeValidSecretManagerGetAllTestCaseCustom(setNoMatchingRegexpFindString),
|
|
|
+ makeValidSecretManagerGetAllTestCaseCustom(setUnmatchedEnvironmentFindString),
|
|
|
+ makeValidSecretManagerGetAllTestCaseCustom(setAPIErr),
|
|
|
+ makeValidSecretManagerGetAllTestCaseCustom(setNilMockClient),
|
|
|
+ }
|
|
|
+
|
|
|
+ sm := Gitlab{}
|
|
|
+ sm.environment = "test"
|
|
|
+ for k, v := range cases {
|
|
|
+ sm.client = v.mockClient
|
|
|
+ out, err := sm.GetAllSecrets(context.Background(), *v.refFind)
|
|
|
+ if !ErrorContains(err, v.expectError) {
|
|
|
+ t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
|
|
|
+ }
|
|
|
+ if v.expectError == "" && string(out[v.apiOutput.Key]) != v.expectedSecret {
|
|
|
+ t.Errorf("[%d] unexpected secret: expected %s, got %s", k, v.expectedSecret, string(out[v.apiOutput.Key]))
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
func TestValidate(t *testing.T) {
|
|
|
successCases := []*secretManagerTestCase{
|
|
|
makeValidSecretManagerTestCaseCustom(),
|
|
|
@@ -196,7 +380,7 @@ func TestValidate(t *testing.T) {
|
|
|
t.Logf("%+v", v)
|
|
|
validationResult, err := sm.Validate()
|
|
|
if !ErrorContains(err, v.expectError) {
|
|
|
- t.Errorf("[%d], unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
|
|
|
+ t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
|
|
|
}
|
|
|
if validationResult != v.expectedValidationResult {
|
|
|
t.Errorf("[%d], unexpected validationResult: %s, expected: '%s'", k, validationResult, v.expectedValidationResult)
|
|
|
@@ -229,7 +413,7 @@ func TestGetSecretMap(t *testing.T) {
|
|
|
sm.client = v.mockClient
|
|
|
out, err := sm.GetSecretMap(context.Background(), *v.ref)
|
|
|
if !ErrorContains(err, v.expectError) {
|
|
|
- t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
|
|
|
+ t.Errorf(defaultErrorMessage, k, err.Error(), v.expectError)
|
|
|
}
|
|
|
if err == nil && !reflect.DeepEqual(out, v.expectedData) {
|
|
|
t.Errorf("[%d] unexpected secret data: expected %#v, got %#v", k, v.expectedData, out)
|
|
|
@@ -269,7 +453,7 @@ func makeSecretStore(projectID, environment string, fn ...storeModifier) *esv1be
|
|
|
|
|
|
func withAccessToken(name, key string, namespace *string) storeModifier {
|
|
|
return func(store *esv1beta1.SecretStore) *esv1beta1.SecretStore {
|
|
|
- store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken = v1.SecretKeySelector{
|
|
|
+ store.Spec.Provider.Gitlab.Auth.SecretRef.AccessToken = esv1meta.SecretKeySelector{
|
|
|
Name: name,
|
|
|
Key: key,
|
|
|
Namespace: namespace,
|