Browse Source

enable configuration of environment_scope for gitlab provider (#1565)

* enable configuration of environment_scope for gitlab provider

Signed-off-by: Dominik Zeiger <dominik@zeiger.biz>
Dominik Zeiger 3 years ago
parent
commit
fa38fe1e60

+ 3 - 0
apis/externalsecrets/v1beta1/secretstore_gitlab_types.go

@@ -28,6 +28,9 @@ type GitlabProvider struct {
 
 	// ProjectID specifies a project where secrets are located.
 	ProjectID string `json:"projectID,omitempty"`
+
+	// Environment environment_scope of gitlab CI/CD variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment on how to create environments)
+	Environment string `json:"environment,omitempty"`
 }
 
 type GitlabAuth struct {

+ 5 - 0
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -2187,6 +2187,11 @@ spec:
                         required:
                         - SecretRef
                         type: object
+                      environment:
+                        description: Environment environment_scope of gitlab CI/CD
+                          variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment
+                          on how to create environments)
+                        type: string
                       projectID:
                         description: ProjectID specifies a project where secrets are
                           located.

+ 5 - 0
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -2187,6 +2187,11 @@ spec:
                         required:
                         - SecretRef
                         type: object
+                      environment:
+                        description: Environment environment_scope of gitlab CI/CD
+                          variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment
+                          on how to create environments)
+                        type: string
                       projectID:
                         description: ProjectID specifies a project where secrets are
                           located.

+ 6 - 0
deploy/crds/bundle.yaml

@@ -1968,6 +1968,9 @@ spec:
                           required:
                             - SecretRef
                           type: object
+                        environment:
+                          description: Environment environment_scope of gitlab CI/CD variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment on how to create environments)
+                          type: string
                         projectID:
                           description: ProjectID specifies a project where secrets are located.
                           type: string
@@ -4893,6 +4896,9 @@ spec:
                           required:
                             - SecretRef
                           type: object
+                        environment:
+                          description: Environment environment_scope of gitlab CI/CD variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment on how to create environments)
+                          type: string
                         projectID:
                           description: ProjectID specifies a project where secrets are located.
                           type: string

+ 2 - 1
docs/snippets/gitlab-secret-store.yaml

@@ -12,4 +12,5 @@ spec:
           accessToken:
             name: gitlab-secret
             key: token
-      projectID: "**project ID goes here**"
+      projectID: "**project ID goes here**"
+      environment: "**environment scope goes here**"

+ 1 - 0
e2e/run.sh

@@ -65,6 +65,7 @@ kubectl run --rm \
   --env="VAULT_URL=${VAULT_URL:-}" \
   --env="GITLAB_TOKEN=${GITLAB_TOKEN:-}" \
   --env="GITLAB_PROJECT_ID=${GITLAB_PROJECT_ID:-}" \
+  --env="GITLAB_ENVIRONMENT=${GITLAB_ENVIRONMENT:-}" \
   --env="ORACLE_USER_OCID=${ORACLE_USER_OCID:-}" \
   --env="ORACLE_TENANCY_OCID=${ORACLE_TENANCY_OCID:-}" \
   --env="ORACLE_REGION=${ORACLE_REGION:-}" \

+ 8 - 4
e2e/suites/provider/cases/gitlab/provider.go

@@ -35,13 +35,15 @@ import (
 type gitlabProvider struct {
 	credentials string
 	projectID   string
+	environment string
 	framework   *framework.Framework
 }
 
-func newGitlabProvider(f *framework.Framework, credentials, projectID string) *gitlabProvider {
+func newGitlabProvider(f *framework.Framework, credentials, projectID string, environment string) *gitlabProvider {
 	prov := &gitlabProvider{
 		credentials: credentials,
 		projectID:   projectID,
+		environment: environment,
 		framework:   f,
 	}
 	BeforeEach(prov.BeforeEach)
@@ -51,7 +53,8 @@ func newGitlabProvider(f *framework.Framework, credentials, projectID string) *g
 func newFromEnv(f *framework.Framework) *gitlabProvider {
 	credentials := os.Getenv("GITLAB_TOKEN")
 	projectID := os.Getenv("GITLAB_PROJECT_ID")
-	return newGitlabProvider(f, credentials, projectID)
+	environment := os.Getenv("GITLAB_ENVIRONMENT")
+	return newGitlabProvider(f, credentials, projectID, environment)
 }
 
 func (s *gitlabProvider) CreateSecret(key string, val framework.SecretEntry) {
@@ -102,8 +105,9 @@ func (s *gitlabProvider) BeforeEach() {
 		// Puts access token into StringData
 
 		StringData: map[string]string{
-			"token":     s.credentials,
-			"projectID": s.projectID,
+			"token":       s.credentials,
+			"projectID":   s.projectID,
+			"environment": s.environment,
 		},
 	}
 	err := s.framework.CRClient.Create(context.Background(), gitlabCreds)

+ 1 - 1
go.mod

@@ -54,7 +54,6 @@ require (
 	github.com/crossplane/crossplane-runtime v0.17.0
 	github.com/go-logr/logr v1.2.3
 	github.com/go-test/deep v1.0.4 // indirect
-	github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
 	github.com/google/go-cmp v0.5.9
 	github.com/google/uuid v1.3.0
 	github.com/googleapis/gax-go/v2 v2.5.1
@@ -134,6 +133,7 @@ require (
 	github.com/gobuffalo/flect v0.3.0 // indirect
 	github.com/goccy/go-json v0.9.11 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/golang/snappy v0.0.4 // indirect

+ 1 - 1
pkg/provider/gitlab/fake/fake.go

@@ -30,7 +30,7 @@ func (mc *GitlabMockClient) ListVariables(pid interface{}, opt *gitlab.ListProje
 	return mc.listVariables(pid)
 }
 
-func (mc *GitlabMockClient) WithValue(projectIDinput, keyInput string, output *gitlab.ProjectVariable, response *gitlab.Response, err error) {
+func (mc *GitlabMockClient) WithValue(projectIDinput, envInput, keyInput string, output *gitlab.ProjectVariable, response *gitlab.Response, err error) {
 	if mc != nil {
 		mc.getVariable = func(pid interface{}, key string, options ...gitlab.RequestOptionFunc) (*gitlab.ProjectVariable, *gitlab.Response, error) {
 			// type secretmanagerpb.AccessSecretVersionRequest contains unexported fields

+ 16 - 10
pkg/provider/gitlab/gitlab.go

@@ -30,8 +30,6 @@ import (
 	"github.com/external-secrets/external-secrets/pkg/utils"
 )
 
-// Requires GITLAB_TOKEN and GITLAB_PROJECT_ID to be set in environment variables
-
 const (
 	errGitlabCredSecretName                   = "credentials are empty"
 	errInvalidClusterStoreMissingSAKNamespace = "invalid clusterStore missing SAK namespace"
@@ -39,7 +37,7 @@ const (
 	errMissingSAK                             = "missing credentials while setting auth"
 	errList                                   = "could not verify if the client is valid: %w"
 	errAuth                                   = "client is not allowed to get secrets"
-	errUninitalizedGitlabProvider             = "provider gitlab is not initialized"
+	errUninitializedGitlabProvider            = "provider gitlab is not initialized"
 	errJSONSecretUnmarshal                    = "unable to unmarshal secret: %w"
 )
 
@@ -54,9 +52,10 @@ type Client interface {
 
 // Gitlab Provider struct with reference to a GitLab client and a projectID.
 type Gitlab struct {
-	client    Client
-	url       string
-	projectID interface{}
+	client      Client
+	url         string
+	projectID   interface{}
+	environment string
 }
 
 // Client for interacting with kubernetes cluster...?
@@ -113,7 +112,7 @@ func NewGitlabProvider() *Gitlab {
 	return &Gitlab{}
 }
 
-// Method on Gitlab Provider to set up client with credentials and populate projectID.
+// Method on Gitlab Provider to set up client with credentials, populate projectID and environment.
 func (g *Gitlab) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
 	storeSpec := store.GetSpec()
 	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Gitlab == nil {
@@ -150,6 +149,7 @@ func (g *Gitlab) NewClient(ctx context.Context, store esv1beta1.GenericStore, ku
 
 	g.client = gitlabClient.ProjectVariables
 	g.projectID = cliStore.store.ProjectID
+	g.environment = cliStore.store.Environment
 	g.url = cliStore.store.URL
 
 	return g, nil
@@ -163,7 +163,7 @@ func (g *Gitlab) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecret
 
 func (g *Gitlab) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
 	if utils.IsNil(g.client) {
-		return nil, fmt.Errorf(errUninitalizedGitlabProvider)
+		return nil, fmt.Errorf(errUninitializedGitlabProvider)
 	}
 	// Need to replace hyphens with underscores to work with Gitlab API
 	ref.Key = strings.ReplaceAll(ref.Key, "-", "_")
@@ -173,8 +173,14 @@ func (g *Gitlab) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretData
 	// 	"variable_type": "env_var",
 	// 	"value": "TEST_1",
 	// 	"protected": false,
-	// 	"masked": true
-	data, _, err := g.client.GetVariable(g.projectID, ref.Key, nil) // Optional 'filter' parameter could be added later
+	// 	"masked": true,
+	// 	"environment_scope": "*"
+	// }
+	var vopts *gitlab.GetProjectVariableOptions
+	if g.environment != "" {
+		vopts = &gitlab.GetProjectVariableOptions{Filter: &gitlab.VariableFilter{EnvironmentScope: g.environment}}
+	}
+	data, _, err := g.client.GetVariable(g.projectID, ref.Key, vopts)
 	if err != nil {
 		return nil, err
 	}

+ 24 - 14
pkg/provider/gitlab/gitlab_test.go

@@ -29,19 +29,22 @@ import (
 )
 
 const (
-	project  = "my-Project"
-	username = "user-name"
-	userkey  = "user-key"
+	project     = "my-Project"
+	username    = "user-name"
+	userkey     = "user-key"
+	environment = "prod"
 )
 
 type secretManagerTestCase struct {
 	mockClient               *fakegitlab.GitlabMockClient
 	apiInputProjectID        string
 	apiInputKey              string
+	apiInputEnv              string
 	apiOutput                *gitlab.ProjectVariable
 	apiResponse              *gitlab.Response
 	ref                      *esv1beta1.ExternalSecretDataRemoteRef
 	projectID                *string
+	environment              *string
 	apiErr                   error
 	expectError              string
 	expectedSecret           string
@@ -55,8 +58,10 @@ func makeValidSecretManagerTestCase() *secretManagerTestCase {
 		mockClient:               &fakegitlab.GitlabMockClient{},
 		apiInputProjectID:        makeValidAPIInputProjectID(),
 		apiInputKey:              makeValidAPIInputKey(),
+		apiInputEnv:              makeValidEnvironment(),
 		ref:                      makeValidRef(),
 		projectID:                nil,
+		environment:              nil,
 		apiOutput:                makeValidAPIOutput(),
 		apiResponse:              makeValidAPIResponse(),
 		apiErr:                   nil,
@@ -65,7 +70,7 @@ func makeValidSecretManagerTestCase() *secretManagerTestCase {
 		expectedValidationResult: esv1beta1.ValidationResultReady,
 		expectedData:             map[string][]byte{},
 	}
-	smtc.mockClient.WithValue(smtc.apiInputProjectID, smtc.apiInputKey, smtc.apiOutput, smtc.apiResponse, smtc.apiErr)
+	smtc.mockClient.WithValue(smtc.apiInputProjectID, smtc.apiInputEnv, smtc.apiInputKey, smtc.apiOutput, smtc.apiResponse, smtc.apiErr)
 	return &smtc
 }
 
@@ -84,6 +89,10 @@ func makeValidAPIInputKey() string {
 	return "testKey"
 }
 
+func makeValidEnvironment() string {
+	return "prod"
+}
+
 func makeValidAPIResponse() *gitlab.Response {
 	return &gitlab.Response{
 		Response: &http.Response{
@@ -104,7 +113,7 @@ func makeValidSecretManagerTestCaseCustom(tweaks ...func(smtc *secretManagerTest
 	for _, fn := range tweaks {
 		fn(smtc)
 	}
-	smtc.mockClient.WithValue(smtc.apiInputProjectID, smtc.apiInputKey, smtc.apiOutput, smtc.apiResponse, smtc.apiErr)
+	smtc.mockClient.WithValue(smtc.apiInputProjectID, smtc.apiInputEnv, smtc.apiInputKey, smtc.apiOutput, smtc.apiResponse, smtc.apiErr)
 	return smtc
 }
 
@@ -137,7 +146,7 @@ var setListAPIRespBadCode = func(smtc *secretManagerTestCase) {
 
 var setNilMockClient = func(smtc *secretManagerTestCase) {
 	smtc.mockClient = nil
-	smtc.expectError = errUninitalizedGitlabProvider
+	smtc.expectError = errUninitializedGitlabProvider
 }
 
 // test the sm<->gcp interface
@@ -240,13 +249,14 @@ func ErrorContains(out error, want string) bool {
 
 type storeModifier func(*esv1beta1.SecretStore) *esv1beta1.SecretStore
 
-func makeSecretStore(projectID string, fn ...storeModifier) *esv1beta1.SecretStore {
+func makeSecretStore(projectID, environment string, fn ...storeModifier) *esv1beta1.SecretStore {
 	store := &esv1beta1.SecretStore{
 		Spec: esv1beta1.SecretStoreSpec{
 			Provider: &esv1beta1.SecretStoreProvider{
 				Gitlab: &esv1beta1.GitlabProvider{
-					Auth:      esv1beta1.GitlabAuth{},
-					ProjectID: projectID,
+					Auth:        esv1beta1.GitlabAuth{},
+					ProjectID:   projectID,
+					Environment: environment,
 				},
 			},
 		},
@@ -277,23 +287,23 @@ func TestValidateStore(t *testing.T) {
 	namespace := "my-namespace"
 	testCases := []ValidateStoreTestCase{
 		{
-			store: makeSecretStore(""),
+			store: makeSecretStore("", environment),
 			err:   fmt.Errorf("projectID cannot be empty"),
 		},
 		{
-			store: makeSecretStore(project, withAccessToken("", userkey, nil)),
+			store: makeSecretStore(project, environment, withAccessToken("", userkey, nil)),
 			err:   fmt.Errorf("accessToken.name cannot be empty"),
 		},
 		{
-			store: makeSecretStore(project, withAccessToken(username, "", nil)),
+			store: makeSecretStore(project, environment, withAccessToken(username, "", nil)),
 			err:   fmt.Errorf("accessToken.key cannot be empty"),
 		},
 		{
-			store: makeSecretStore(project, withAccessToken("userName", "userKey", &namespace)),
+			store: makeSecretStore(project, environment, withAccessToken("userName", "userKey", &namespace)),
 			err:   fmt.Errorf("namespace not allowed with namespaced SecretStore"),
 		},
 		{
-			store: makeSecretStore(project, withAccessToken("userName", "userKey", nil)),
+			store: makeSecretStore(project, environment, withAccessToken("userName", "userKey", nil)),
 			err:   nil,
 		},
 	}