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

Merge pull request #262 from external-secrets/feature/refactor-e2e

fix(e2e): refactor e2e tests
This commit is contained in:
paul-the-alien[bot] 2021-07-13 15:45:33 +00:00 committed by GitHub
commit 2ab5bf800e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 980 additions and 847 deletions

View file

@ -15,6 +15,7 @@ env:
# credentials have been provided before trying to run steps that need them.
GHCR_USERNAME: ${{ secrets.GHCR_USERNAME }}
GCP_SM_SA_JSON: ${{ secrets.GCP_SM_SA_JSON}}
GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID}}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID}}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET}}
TENANT_ID: ${{ secrets.TENANT_ID}}

View file

@ -78,7 +78,7 @@ check-diff: reviewable
.PHONY: test
test: generate ## Run tests
@$(INFO) go test unit-tests
go test -v $(shell go list ./... | grep -v e2e) -coverprofile cover.out
go test -race -v $(shell go list ./... | grep -v e2e) -coverprofile cover.out
@$(OK) go test unit-tests
.PHONY: test.e2e

View file

@ -6,6 +6,75 @@ External Secrets Operator integrates with [HashiCorp Vault](https://www.vaultpro
management. Vault itself implements lots of different secret engines, as of now we only support the
[KV Secrets Engine](https://www.vaultproject.io/docs/secrets/kv).
### Example
First, create a SecretStore with a vault backend. For the sake of simplicity we'll use a static token `root`:
```yaml
apiVersion: external-secrets.io/v1alpha1
kind: SecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: "http://my.vault.server:8200"
path: "secret"
version: "v2"
auth:
# points to a secret that contains a vault token
# https://www.vaultproject.io/docs/auth/token
tokenSecretRef:
name: "vault-token"
namespace: "default"
key: "token"
---
apiVersion: v1
kind: Secret
metadata:
name: vault-token
data:
token: cm9vdA== # "root"
```
Then create a simple k/v pair at path `secret/foo`:
```
vault kv put secret/foo my-value=s3cr3t
```
Now create a ExternalSecret that uses the above SecretStore:
```yaml
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
name: vault-example
spec:
refreshInterval: "15s"
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: example-sync
data:
- secretKey: foobar
remoteRef:
key: secret/foo
property: my-value
---
# will create a secret with:
kind: Secret
metadata:
name: example-sync
data:
foobar: czNjcjN0
```
#### Limitations
Vault supports only simple key/value pairs - nested objects are not supported. Hence specifying `gjson` properties like other providers support it is not supported.
### Authentication
We support three different modes for authentication:

View file

@ -5,6 +5,7 @@ SHELL := /bin/bash
IMG_TAG = test
IMG = local/external-secrets-e2e:$(IMG_TAG)
K8S_VERSION = "1.19.1"
export FOCUS := $(FOCUS)
start-kind: ## Start kind cluster
kind create cluster \

View file

@ -16,6 +16,7 @@ package framework
import (
"bytes"
"context"
"encoding/json"
"time"
v1 "k8s.io/api/core/v1"
@ -26,7 +27,7 @@ import (
// WaitForSecretValue waits until a secret comes into existence and compares the secret.Data
// with the provided values.
func (f *Framework) WaitForSecretValue(namespace, name string, values map[string][]byte) (*v1.Secret, error) {
func (f *Framework) WaitForSecretValue(namespace, name string, expected *v1.Secret) (*v1.Secret, error) {
secret := &v1.Secret{}
err := wait.PollImmediate(time.Second*2, time.Minute*2, func() (bool, error) {
err := f.CRClient.Get(context.Background(), types.NamespacedName{
@ -36,13 +37,28 @@ func (f *Framework) WaitForSecretValue(namespace, name string, values map[string
if apierrors.IsNotFound(err) {
return false, nil
}
for k, exp := range values {
if actual, ok := secret.Data[k]; ok && !bytes.Equal(actual, exp) {
return false, nil
}
}
return true, nil
return equalSecrets(expected, secret), nil
})
return secret, err
}
func equalSecrets(exp, ts *v1.Secret) bool {
if exp.Type != ts.Type {
return false
}
expLabels, _ := json.Marshal(exp.ObjectMeta.Labels)
tsLabels, _ := json.Marshal(ts.ObjectMeta.Labels)
if !bytes.Equal(expLabels, tsLabels) {
return false
}
expAnnotations, _ := json.Marshal(exp.ObjectMeta.Annotations)
tsAnnotations, _ := json.Marshal(ts.ObjectMeta.Annotations)
if !bytes.Equal(expAnnotations, tsAnnotations) {
return false
}
expData, _ := json.Marshal(exp.Data)
tsData, _ := json.Marshal(ts.Data)
return bytes.Equal(expData, tsData)
}

91
e2e/framework/testcase.go Normal file
View file

@ -0,0 +1,91 @@
/*
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 framework
import (
"context"
//nolint
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
)
const TargetSecretName = "target-secret"
// TestCase contains the test infra to run a table driven test.
type TestCase struct {
Framework *Framework
ExternalSecret *esv1alpha1.ExternalSecret
Secrets map[string]string
ExpectedSecret *v1.Secret
}
// SecretStoreProvider is a interface that must be implemented
// by a provider that runs the e2e test.
type SecretStoreProvider interface {
CreateSecret(key string, val string)
DeleteSecret(key string)
}
// TableFunc returns the main func that runs a TestCase in a table driven test.
func TableFunc(f *Framework, prov SecretStoreProvider) func(func(*TestCase)) {
return func(customize func(*TestCase)) {
var err error
// make default test case
// and apply customization to it
tc := makeDefaultTestCase(f)
customize(tc)
// create secrets & defer delete
for k, v := range tc.Secrets {
key := k
prov.CreateSecret(key, v)
defer func() {
prov.DeleteSecret(key)
}()
}
// create external secret
err = tc.Framework.CRClient.Create(context.Background(), tc.ExternalSecret)
Expect(err).ToNot(HaveOccurred())
// wait for Kind=Secret to have the expected data
_, err = tc.Framework.WaitForSecretValue(tc.Framework.Namespace.Name, TargetSecretName, tc.ExpectedSecret)
Expect(err).ToNot(HaveOccurred())
}
}
func makeDefaultTestCase(f *Framework) *TestCase {
return &TestCase{
Framework: f,
ExternalSecret: &esv1alpha1.ExternalSecret{
ObjectMeta: metav1.ObjectMeta{
Name: "e2e-es",
Namespace: f.Namespace.Name,
},
Spec: esv1alpha1.ExternalSecretSpec{
SecretStoreRef: esv1alpha1.SecretStoreRef{
Name: f.Namespace.Name,
},
Target: esv1alpha1.ExternalSecretTarget{
Name: TargetSecretName,
},
},
},
}
}

View file

@ -46,18 +46,17 @@ done
kubectl apply -f ${DIR}/k8s/deploy/crds
echo -e "Starting the e2e test pod"
FOCUS=${FOCUS:-.*}
export FOCUS
kubectl run --rm \
--attach \
--restart=Never \
--pod-running-timeout=10m \
--env="FOCUS=${FOCUS}" \
--env="GCP_SM_SA_JSON=${GCP_SM_SA_JSON}" \
--env="AZURE_CLIENT_ID=${AZURE_CLIENT_ID}" \
--env="AZURE_CLIENT_SECRET=${AZURE_CLIENT_SECRET}" \
--env="TENANT_ID=${TENANT_ID}" \
--env="VAULT_URL=${VAULT_URL}" \
--env="FOCUS=${FOCUS:-.*}" \
--env="GCP_SM_SA_JSON=${GCP_SM_SA_JSON:-}" \
--env="GCP_PROJECT_ID=${GCP_PROJECT_ID:-}" \
--env="AZURE_CLIENT_ID=${AZURE_CLIENT_ID:-}" \
--env="AZURE_CLIENT_SECRET=${AZURE_CLIENT_SECRET:-}" \
--env="TENANT_ID=${TENANT_ID:-}" \
--env="VAULT_URL=${VAULT_URL:-}" \
--overrides='{ "apiVersion": "v1", "spec":{"serviceAccountName": "external-secrets-e2e"}}' \
e2e --image=local/external-secrets-e2e:test

124
e2e/suite/aws/provider.go Normal file
View file

@ -0,0 +1,124 @@
/*
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 aws
import (
"context"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/secretsmanager"
//nolint
. "github.com/onsi/ginkgo"
// nolint
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
"github.com/external-secrets/external-secrets/e2e/framework"
prov "github.com/external-secrets/external-secrets/pkg/provider/aws"
)
type SMProvider struct {
url string
client *secretsmanager.SecretsManager
framework *framework.Framework
}
func newSMProvider(f *framework.Framework, url string) *SMProvider {
sess, err := session.NewSessionWithOptions(session.Options{
Config: aws.Config{
Credentials: credentials.NewStaticCredentials("foobar", "foobar", "secret-manager"),
EndpointResolver: prov.ResolveEndpointWithServiceMap(map[string]string{
"secretsmanager": url,
}),
Region: aws.String("eu-east-1"),
},
})
Expect(err).ToNot(HaveOccurred())
sm := secretsmanager.New(sess)
prov := &SMProvider{
url: url,
client: sm,
framework: f,
}
BeforeEach(prov.BeforeEach)
return prov
}
func (s *SMProvider) CreateSecret(key, val string) {
_, err := s.client.CreateSecret(&secretsmanager.CreateSecretInput{
Name: aws.String(key),
SecretString: aws.String(val),
})
Expect(err).ToNot(HaveOccurred())
}
func (s *SMProvider) DeleteSecret(key string) {
_, err := s.client.DeleteSecret(&secretsmanager.DeleteSecretInput{
SecretId: aws.String(key),
})
Expect(err).ToNot(HaveOccurred())
}
func (s *SMProvider) BeforeEach() {
By("creating a AWS SM credentials secret")
awsCreds := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "provider-secret",
Namespace: s.framework.Namespace.Name,
},
StringData: map[string]string{
"kid": "foobar",
"sak": "foobar",
},
}
err := s.framework.CRClient.Create(context.Background(), awsCreds)
Expect(err).ToNot(HaveOccurred())
By("creating a AWS SM secret store")
secretStore := &esv1alpha1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: s.framework.Namespace.Name,
Namespace: s.framework.Namespace.Name,
},
Spec: esv1alpha1.SecretStoreSpec{
Provider: &esv1alpha1.SecretStoreProvider{
AWS: &esv1alpha1.AWSProvider{
Service: esv1alpha1.AWSServiceSecretsManager,
Region: "us-east-1",
Auth: &esv1alpha1.AWSAuth{
SecretRef: esv1alpha1.AWSAuthSecretRef{
AccessKeyID: esmeta.SecretKeySelector{
Name: "provider-secret",
Key: "kid",
},
SecretAccessKey: esmeta.SecretKeySelector{
Name: "provider-secret",
Key: "sak",
},
},
},
},
},
},
}
err = s.framework.CRClient.Create(context.Background(), secretStore)
Expect(err).ToNot(HaveOccurred())
}

View file

@ -14,222 +14,26 @@ limitations under the License.
package aws
import (
"context"
"fmt"
// nolint
. "github.com/onsi/ginkgo"
// nolint
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
. "github.com/onsi/ginkgo/extensions/table"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
"github.com/external-secrets/external-secrets/e2e/framework"
)
const (
targetSecret = "target-secret"
"github.com/external-secrets/external-secrets/e2e/suite/common"
)
var _ = Describe("[aws] ", func() {
f := framework.New("eso-aws")
var secretStore *esv1alpha1.SecretStore
localstackURL := "http://localstack.default"
BeforeEach(func() {
By("creating an secret store for localstack")
awsCreds := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: f.Namespace.Name,
Namespace: f.Namespace.Name,
},
StringData: map[string]string{
"kid": "foobar",
"sak": "foobar",
},
}
err := f.CRClient.Create(context.Background(), awsCreds)
Expect(err).ToNot(HaveOccurred())
secretStore = &esv1alpha1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: f.Namespace.Name,
Namespace: f.Namespace.Name,
},
Spec: esv1alpha1.SecretStoreSpec{
Provider: &esv1alpha1.SecretStoreProvider{
AWS: &esv1alpha1.AWSProvider{
Service: esv1alpha1.AWSServiceSecretsManager,
Region: "us-east-1",
Auth: &esv1alpha1.AWSAuth{
SecretRef: esv1alpha1.AWSAuthSecretRef{
AccessKeyID: esmeta.SecretKeySelector{
Name: f.Namespace.Name,
Key: "kid",
},
SecretAccessKey: esmeta.SecretKeySelector{
Name: f.Namespace.Name,
Key: "sak",
},
},
},
},
},
},
}
err = f.CRClient.Create(context.Background(), secretStore)
Expect(err).ToNot(HaveOccurred())
})
It("should sync multiple secrets", func() {
By("creating a AWS SM Secret")
secretKey1 := fmt.Sprintf("%s-%s", f.Namespace.Name, "one")
secretKey2 := fmt.Sprintf("%s-%s", f.Namespace.Name, "other")
secretValue := "bar"
err := CreateAWSSecretsManagerSecret(
localstackURL,
secretKey1, secretValue)
Expect(err).ToNot(HaveOccurred())
err = CreateAWSSecretsManagerSecret(
localstackURL,
secretKey2, secretValue)
Expect(err).ToNot(HaveOccurred())
err = f.CRClient.Create(context.Background(), &esv1alpha1.ExternalSecret{
ObjectMeta: metav1.ObjectMeta{
Name: "simple-sync",
Namespace: f.Namespace.Name,
},
Spec: esv1alpha1.ExternalSecretSpec{
SecretStoreRef: esv1alpha1.SecretStoreRef{
Name: f.Namespace.Name,
},
Target: esv1alpha1.ExternalSecretTarget{
Name: targetSecret,
},
Data: []esv1alpha1.ExternalSecretData{
{
SecretKey: secretKey1,
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey1,
},
},
{
SecretKey: secretKey2,
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey2,
},
},
},
},
})
Expect(err).ToNot(HaveOccurred())
_, err = f.WaitForSecretValue(f.Namespace.Name, targetSecret, map[string][]byte{
secretKey1: []byte(secretValue),
secretKey2: []byte(secretValue),
})
Expect(err).ToNot(HaveOccurred())
})
It("should sync secrets with dataFrom", func() {
By("creating a AWS SM Secret")
secretKey1 := fmt.Sprintf("%s-%s", f.Namespace.Name, "one")
targetSecretKey1 := "name"
targetSecretValue1 := "great-name"
targetSecretKey2 := "surname"
targetSecretValue2 := "great-surname"
secretValue := fmt.Sprintf("{ \"%s\": \"%s\", \"%s\": \"%s\" }", targetSecretKey1, targetSecretValue1, targetSecretKey2, targetSecretValue2)
err := CreateAWSSecretsManagerSecret(
localstackURL,
secretKey1, secretValue)
Expect(err).ToNot(HaveOccurred())
err = f.CRClient.Create(context.Background(), &esv1alpha1.ExternalSecret{
ObjectMeta: metav1.ObjectMeta{
Name: "datafrom-sync",
Namespace: f.Namespace.Name,
},
Spec: esv1alpha1.ExternalSecretSpec{
SecretStoreRef: esv1alpha1.SecretStoreRef{
Name: f.Namespace.Name,
},
Target: esv1alpha1.ExternalSecretTarget{
Name: targetSecret,
},
DataFrom: []esv1alpha1.ExternalSecretDataRemoteRef{
{
Key: secretKey1,
},
},
},
})
Expect(err).ToNot(HaveOccurred())
_, err = f.WaitForSecretValue(f.Namespace.Name, targetSecret, map[string][]byte{
targetSecretKey1: []byte(targetSecretValue1),
targetSecretKey2: []byte(targetSecretValue2),
})
Expect(err).ToNot(HaveOccurred())
})
It("should sync secrets and get inner keys", func() {
By("creating a AWS SM Secret")
secretKey1 := fmt.Sprintf("%s-%s", f.Namespace.Name, "one")
targetSecretKey1 := "firstname"
targetSecretValue1 := "Tom"
targetSecretKey2 := "first_friend"
targetSecretValue2 := "Roger"
secretValue := fmt.Sprintf(
`{
"name": {"first": "%s", "last": "Anderson"},
"friends":
[
{"first": "Dale", "last": "Murphy"},
{"first": "%s", "last": "Craig"},
{"first": "Jane", "last": "Murphy"}
]
}`, targetSecretValue1, targetSecretValue2)
err := CreateAWSSecretsManagerSecret(
localstackURL,
secretKey1, secretValue)
Expect(err).ToNot(HaveOccurred())
err = f.CRClient.Create(context.Background(), &esv1alpha1.ExternalSecret{
ObjectMeta: metav1.ObjectMeta{
Name: "datafrom-sync",
Namespace: f.Namespace.Name,
},
Spec: esv1alpha1.ExternalSecretSpec{
SecretStoreRef: esv1alpha1.SecretStoreRef{
Name: f.Namespace.Name,
},
Target: esv1alpha1.ExternalSecretTarget{
Name: targetSecret,
},
Data: []esv1alpha1.ExternalSecretData{
{
SecretKey: targetSecretKey1,
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey1,
Property: "name.first",
},
},
{
SecretKey: targetSecretKey2,
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey1,
Property: "friends.1.first",
},
},
},
},
})
Expect(err).ToNot(HaveOccurred())
_, err = f.WaitForSecretValue(f.Namespace.Name, targetSecret, map[string][]byte{
targetSecretKey1: []byte(targetSecretValue1),
targetSecretKey2: []byte(targetSecretValue2),
})
Expect(err).ToNot(HaveOccurred())
})
DescribeTable("sync secrets",
framework.TableFunc(f,
newSMProvider(f, "http://localstack.default")),
Entry(common.SimpleDataSync(f)),
Entry(common.NestedJSONWithGJSON(f)),
Entry(common.JSONDataFromSync(f)),
Entry(common.JSONDataWithProperty(f)),
Entry(common.JSONDataWithTemplate(f)),
)
})

View file

@ -1,44 +0,0 @@
/*
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.
limitations under the License.
*/
package aws
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/secretsmanager"
prov "github.com/external-secrets/external-secrets/pkg/provider/aws"
)
// CreateAWSSecretsManagerSecret creates a sm secret with the given value.
func CreateAWSSecretsManagerSecret(endpoint, secretName, secretValue string) error {
sess, err := session.NewSessionWithOptions(session.Options{
Config: aws.Config{
Credentials: credentials.NewStaticCredentials("foobar", "foobar", "secret-manager"),
EndpointResolver: prov.ResolveEndpointWithServiceMap(map[string]string{
"secretsmanager": endpoint,
}),
Region: aws.String("eu-east-1"),
},
})
if err != nil {
return err
}
sm := secretsmanager.New(sess)
_, err = sm.CreateSecret(&secretsmanager.CreateSecretInput{
Name: aws.String(secretName),
SecretString: aws.String(secretValue),
})
return err
}

View file

@ -13,118 +13,32 @@ limitations under the License.
package azure
import (
"context"
"fmt"
"os"
// nolint
. "github.com/onsi/ginkgo"
// nolint
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
. "github.com/onsi/ginkgo/extensions/table"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
"github.com/external-secrets/external-secrets/e2e/framework"
)
const (
targetSecret = "target-secret"
"github.com/external-secrets/external-secrets/e2e/suite/common"
)
var _ = Describe("[azure] ", func() {
f := framework.New("eso-azure")
var secretStore *esv1alpha1.SecretStore
vaultURL := os.Getenv("VAULT_URL")
tenantID := os.Getenv("TENANT_ID")
clientID := os.Getenv("AZURE_CLIENT_ID")
clientSecret := os.Getenv("AZURE_CLIENT_SECRET")
BeforeEach(func() {
By("creating a secret in AzureKV")
azureCreds := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: f.Namespace.Name,
Namespace: f.Namespace.Name,
},
StringData: map[string]string{
"ClientID": clientID,
"ClientSecret": clientSecret,
},
}
err := f.CRClient.Create(context.Background(), azureCreds)
Expect(err).ToNot(HaveOccurred())
secretStore = &esv1alpha1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: f.Namespace.Name,
Namespace: f.Namespace.Name,
},
Spec: esv1alpha1.SecretStoreSpec{
Provider: &esv1alpha1.SecretStoreProvider{
AzureKV: &esv1alpha1.AzureKVProvider{
TenantID: &tenantID,
VaultURL: &vaultURL,
AuthSecretRef: &esv1alpha1.AzureKVAuth{
ClientID: &esmeta.SecretKeySelector{
Name: f.Namespace.Name,
Key: "ClientID",
},
ClientSecret: &esmeta.SecretKeySelector{
Name: f.Namespace.Name,
Key: "ClientSecret",
},
},
},
},
},
}
err = f.CRClient.Create(context.Background(), secretStore)
Expect(err).ToNot(HaveOccurred())
})
It("should sync secrets", func() {
By("creating a AzureKV Secret")
secretKey1 := fmt.Sprintf("%s-%s", f.Namespace.Name, "one")
secretValue := "great-value-test"
_, err := createAzureKVSecret(
secretKey1,
secretValue,
clientID,
clientSecret,
tenantID,
vaultURL)
Expect(err).ToNot(HaveOccurred())
err = f.CRClient.Create(context.Background(), &esv1alpha1.ExternalSecret{
ObjectMeta: metav1.ObjectMeta{
Name: "simple-sync",
Namespace: f.Namespace.Name,
},
Spec: esv1alpha1.ExternalSecretSpec{
SecretStoreRef: esv1alpha1.SecretStoreRef{
Name: f.Namespace.Name,
},
Target: esv1alpha1.ExternalSecretTarget{
Name: targetSecret,
},
Data: []esv1alpha1.ExternalSecretData{
{
SecretKey: secretKey1,
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey1,
},
},
},
},
})
Expect(err).ToNot(HaveOccurred())
_, err = f.WaitForSecretValue(f.Namespace.Name, targetSecret, map[string][]byte{
secretKey1: []byte(secretValue),
})
Expect(err).ToNot(HaveOccurred())
err = deleteAzureKVSecret(secretKey1, clientID, clientSecret, tenantID, vaultURL)
Expect(err).ToNot(HaveOccurred())
})
prov := newazureProvider(f, clientID, clientSecret, tenantID, vaultURL)
DescribeTable("sync secrets", framework.TableFunc(f, prov),
Entry(common.SimpleDataSync(f)),
Entry(common.NestedJSONWithGJSON(f)),
// TODO: dataFrom is not working as expected RN
// see: https://github.com/external-secrets/external-secrets/issues/263
// Entry(common.JSONDataFromSync(f)),
Entry(common.JSONDataWithProperty(f)),
Entry(common.JSONDataWithTemplate(f)),
)
})

127
e2e/suite/azure/provider.go Normal file
View file

@ -0,0 +1,127 @@
/*
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.
limitations under the License.
*/
package azure
import (
"context"
"github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
kvauth "github.com/Azure/go-autorest/autorest/azure/auth"
// nolint
. "github.com/onsi/ginkgo"
// nolint
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilpointer "k8s.io/utils/pointer"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
"github.com/external-secrets/external-secrets/e2e/framework"
)
type azureProvider struct {
clientID string
clientSecret string
tenantID string
vaultURL string
client *keyvault.BaseClient
framework *framework.Framework
}
func newazureProvider(f *framework.Framework, clientID, clientSecret, tenantID, vaultURL string) *azureProvider {
clientCredentialsConfig := kvauth.NewClientCredentialsConfig(clientID, clientSecret, tenantID)
clientCredentialsConfig.Resource = "https://vault.azure.net"
authorizer, err := clientCredentialsConfig.Authorizer()
Expect(err).ToNot(HaveOccurred())
basicClient := keyvault.New()
basicClient.Authorizer = authorizer
prov := &azureProvider{
framework: f,
clientID: clientID,
clientSecret: clientSecret,
tenantID: tenantID,
vaultURL: vaultURL,
client: &basicClient,
}
BeforeEach(prov.BeforeEach)
return prov
}
func (s *azureProvider) CreateSecret(key, val string) {
_, err := s.client.SetSecret(
context.Background(),
s.vaultURL,
key,
keyvault.SecretSetParameters{
Value: &val,
SecretAttributes: &keyvault.SecretAttributes{
RecoveryLevel: keyvault.Purgeable,
Enabled: utilpointer.BoolPtr(true),
},
})
Expect(err).ToNot(HaveOccurred())
}
func (s *azureProvider) DeleteSecret(key string) {
_, err := s.client.DeleteSecret(
context.Background(),
s.vaultURL,
key)
Expect(err).ToNot(HaveOccurred())
}
func (s *azureProvider) BeforeEach() {
azureCreds := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "provider-secret",
Namespace: s.framework.Namespace.Name,
},
StringData: map[string]string{
"client-id": s.clientID,
"client-secret": s.clientSecret,
},
}
err := s.framework.CRClient.Create(context.Background(), azureCreds)
Expect(err).ToNot(HaveOccurred())
secretStore := &esv1alpha1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: s.framework.Namespace.Name,
Namespace: s.framework.Namespace.Name,
},
Spec: esv1alpha1.SecretStoreSpec{
Provider: &esv1alpha1.SecretStoreProvider{
AzureKV: &esv1alpha1.AzureKVProvider{
TenantID: &s.tenantID,
VaultURL: &s.vaultURL,
AuthSecretRef: &esv1alpha1.AzureKVAuth{
ClientID: &esmeta.SecretKeySelector{
Name: "provider-secret",
Key: "client-id",
},
ClientSecret: &esmeta.SecretKeySelector{
Name: "provider-secret",
Key: "client-secret",
},
},
},
},
},
}
err = s.framework.CRClient.Create(context.Background(), secretStore)
Expect(err).ToNot(HaveOccurred())
}

View file

@ -1,79 +0,0 @@
/*
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.
limitations under the License.
*/
package azure
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
kvauth "github.com/Azure/go-autorest/autorest/azure/auth"
utilpointer "k8s.io/utils/pointer"
)
// CreateAWSSecretsManagerSecret creates a sm secret with the given value.
func createAzureKVSecret(secretName, secretValue, clientID, clientSecret, tenantID, vaultURL string) (result keyvault.SecretBundle, err error) {
ctx := context.Background()
clientCredentialsConfig := kvauth.NewClientCredentialsConfig(clientID, clientSecret, tenantID)
clientCredentialsConfig.Resource = "https://vault.azure.net"
authorizer, err := clientCredentialsConfig.Authorizer()
if err != nil {
return keyvault.SecretBundle{}, fmt.Errorf("could not configure azure authorizer: %w", err)
}
basicClient := keyvault.New()
basicClient.Authorizer = authorizer
deletionRecoveryLevel := keyvault.Purgeable
result, err = basicClient.SetSecret(
ctx,
vaultURL,
secretName,
keyvault.SecretSetParameters{
Value: &secretValue,
SecretAttributes: &keyvault.SecretAttributes{
RecoveryLevel: deletionRecoveryLevel,
Enabled: utilpointer.BoolPtr(true),
},
})
if err != nil {
return keyvault.SecretBundle{}, fmt.Errorf("could not create secret key %s: %w", secretName, err)
}
return result, err
}
// deleteSecret deletes the secret with the given name and all of its versions.
func deleteAzureKVSecret(secretName, clientID, clientSecret, tenantID, vaultURL string) error {
ctx := context.Background()
clientCredentialsConfig := kvauth.NewClientCredentialsConfig(clientID, clientSecret, tenantID)
clientCredentialsConfig.Resource = "https://vault.azure.net"
authorizer, err := clientCredentialsConfig.Authorizer()
if err != nil {
return fmt.Errorf("could not configure azure authorizer: %w", err)
}
basicClient := keyvault.New()
basicClient.Authorizer = authorizer
_, err = basicClient.DeleteSecret(
ctx,
vaultURL,
secretName)
if err != nil {
return fmt.Errorf("could not delete secret: %w", err)
}
return err
}

230
e2e/suite/common/common.go Normal file
View file

@ -0,0 +1,230 @@
/*
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.
limitations under the License.
*/
package common
import (
"fmt"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
"github.com/external-secrets/external-secrets/e2e/framework"
)
// This case creates multiple secrets with simple key/value pairs and syncs them using multiple .Spec.Data blocks.
// Not supported by: vault.
func SimpleDataSync(f *framework.Framework) (string, func(*framework.TestCase)) {
return "[common] should sync simple secrets from .Data[]", func(tc *framework.TestCase) {
secretKey1 := fmt.Sprintf("%s-%s", f.Namespace.Name, "one")
secretKey2 := fmt.Sprintf("%s-%s", f.Namespace.Name, "other")
secretValue := "bar"
tc.Secrets = map[string]string{
secretKey1: secretValue,
secretKey2: secretValue,
}
tc.ExpectedSecret = &v1.Secret{
Type: v1.SecretTypeOpaque,
Data: map[string][]byte{
secretKey1: []byte(secretValue),
secretKey2: []byte(secretValue),
},
}
tc.ExternalSecret.Spec.Data = []esv1alpha1.ExternalSecretData{
{
SecretKey: secretKey1,
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey1,
},
},
{
SecretKey: secretKey2,
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey2,
},
},
}
}
}
// This case creates multiple secrets with json values and syncs them using multiple .Spec.Data blocks.
// The data is extracted from the JSON key using ref.Property.
func JSONDataWithProperty(f *framework.Framework) (string, func(*framework.TestCase)) {
return "[common] should sync multiple secrets from .Data[]", func(tc *framework.TestCase) {
secretKey1 := fmt.Sprintf("%s-%s", f.Namespace.Name, "one")
secretKey2 := fmt.Sprintf("%s-%s", f.Namespace.Name, "two")
secretValue1 := "{\"foo1\":\"foo1-val\",\"bar1\":\"bar1-val\"}"
secretValue2 := "{\"foo2\":\"foo2-val\",\"bar2\":\"bar2-val\"}"
tc.Secrets = map[string]string{
secretKey1: secretValue1,
secretKey2: secretValue2,
}
tc.ExpectedSecret = &v1.Secret{
Type: v1.SecretTypeOpaque,
Data: map[string][]byte{
secretKey1: []byte("foo1-val"),
secretKey2: []byte("bar2-val"),
},
}
tc.ExternalSecret.Spec.Data = []esv1alpha1.ExternalSecretData{
{
SecretKey: secretKey1,
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey1,
Property: "foo1",
},
},
{
SecretKey: secretKey2,
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey2,
Property: "bar2",
},
},
}
}
}
// This case creates multiple secrets with json values and renders a template.
// The data is extracted from the JSON key using ref.Property.
func JSONDataWithTemplate(f *framework.Framework) (string, func(*framework.TestCase)) {
return "[common] should sync json secrets with template", func(tc *framework.TestCase) {
secretKey1 := fmt.Sprintf("%s-%s", f.Namespace.Name, "one")
secretKey2 := fmt.Sprintf("%s-%s", f.Namespace.Name, "other")
secretValue1 := "{\"foo1\":\"foo1-val\",\"bar1\":\"bar1-val\"}"
secretValue2 := "{\"foo2\":\"foo2-val\",\"bar2\":\"bar2-val\"}"
tc.Secrets = map[string]string{
secretKey1: secretValue1,
secretKey2: secretValue2,
}
tc.ExpectedSecret = &v1.Secret{
Type: v1.SecretTypeOpaque,
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"example": "annotation",
},
Labels: map[string]string{
"example": "label",
},
},
Data: map[string][]byte{
"my-data": []byte(`executed: foo1-val|bar2-val`),
},
}
tc.ExternalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{
Metadata: esv1alpha1.ExternalSecretTemplateMetadata{
Annotations: map[string]string{
"example": "annotation",
},
Labels: map[string]string{
"example": "label",
},
},
Data: map[string]string{
"my-data": "executed: {{ .one | toString }}|{{ .two | toString }}",
},
}
tc.ExternalSecret.Spec.Data = []esv1alpha1.ExternalSecretData{
{
SecretKey: "one",
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey1,
Property: "foo1",
},
},
{
SecretKey: "two",
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey2,
Property: "bar2",
},
},
}
}
}
// This case creates one secret with json values and syncs them using a single .Spec.DataFrom block.
func JSONDataFromSync(f *framework.Framework) (string, func(*framework.TestCase)) {
return "[common] should sync secrets with dataFrom", func(tc *framework.TestCase) {
secretKey1 := fmt.Sprintf("%s-%s", f.Namespace.Name, "one")
targetSecretKey1 := "name"
targetSecretValue1 := "great-name"
targetSecretKey2 := "surname"
targetSecretValue2 := "great-surname"
secretValue := fmt.Sprintf("{ \"%s\": \"%s\", \"%s\": \"%s\" }", targetSecretKey1, targetSecretValue1, targetSecretKey2, targetSecretValue2)
tc.Secrets = map[string]string{
secretKey1: secretValue,
}
tc.ExpectedSecret = &v1.Secret{
Type: v1.SecretTypeOpaque,
Data: map[string][]byte{
targetSecretKey1: []byte(targetSecretValue1),
targetSecretKey2: []byte(targetSecretValue2),
},
}
tc.ExternalSecret.Spec.DataFrom = []esv1alpha1.ExternalSecretDataRemoteRef{
{
Key: secretKey1,
},
}
}
}
// This case creates a secret with a nested json value. It is synced into two secrets.
// The values from the nested data are extracted using gjson.
// not supported by: vault.
func NestedJSONWithGJSON(f *framework.Framework) (string, func(*framework.TestCase)) {
return "[common] should sync nested json secrets and get inner keys", func(tc *framework.TestCase) {
secretKey1 := fmt.Sprintf("%s-%s", f.Namespace.Name, "one")
targetSecretKey1 := "firstname"
targetSecretValue1 := "Tom"
targetSecretKey2 := "first_friend"
targetSecretValue2 := "Roger"
secretValue := fmt.Sprintf(
`{
"name": {"first": "%s", "last": "Anderson"},
"friends":
[
{"first": "Dale", "last": "Murphy"},
{"first": "%s", "last": "Craig"},
{"first": "Jane", "last": "Murphy"}
]
}`, targetSecretValue1, targetSecretValue2)
tc.Secrets = map[string]string{
secretKey1: secretValue,
}
tc.ExpectedSecret = &v1.Secret{
Type: v1.SecretTypeOpaque,
Data: map[string][]byte{
targetSecretKey1: []byte(targetSecretValue1),
targetSecretKey2: []byte(targetSecretValue2),
},
}
tc.ExternalSecret.Spec.Data = []esv1alpha1.ExternalSecretData{
{
SecretKey: targetSecretKey1,
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey1,
Property: "name.first",
},
},
{
SecretKey: targetSecretKey2,
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey1,
Property: "friends.1.first",
},
},
}
}
}

View file

@ -13,215 +13,28 @@ limitations under the License.
package gcp
import (
"context"
"fmt"
"os"
// nolint
. "github.com/onsi/ginkgo"
// nolint
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
. "github.com/onsi/ginkgo/extensions/table"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
"github.com/external-secrets/external-secrets/e2e/framework"
)
const (
targetSecret = "target-secret"
"github.com/external-secrets/external-secrets/e2e/suite/common"
)
var _ = Describe("[gcp] ", func() {
f := framework.New("eso-gcp")
var secretStore *esv1alpha1.SecretStore
projectID := "external-secrets-operator"
credentials := os.Getenv("GCP_SM_SA_JSON")
projectID := os.Getenv("GCP_PROJECT_ID")
prov := newgcpProvider(f, credentials, projectID)
BeforeEach(func() {
By("creating a secret in GCP SM")
gcpCred := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: f.Namespace.Name,
Namespace: f.Namespace.Name,
},
StringData: map[string]string{
"secret-access-credentials": credentials,
},
}
err := f.CRClient.Create(context.Background(), gcpCred)
Expect(err).ToNot(HaveOccurred())
secretStore = &esv1alpha1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: f.Namespace.Name,
Namespace: f.Namespace.Name,
},
Spec: esv1alpha1.SecretStoreSpec{
Provider: &esv1alpha1.SecretStoreProvider{
GCPSM: &esv1alpha1.GCPSMProvider{
ProjectID: projectID,
Auth: esv1alpha1.GCPSMAuth{
SecretRef: esv1alpha1.GCPSMAuthSecretRef{
SecretAccessKey: esmeta.SecretKeySelector{
Name: f.Namespace.Name,
Key: "secret-access-credentials",
},
},
},
},
},
},
}
err = f.CRClient.Create(context.Background(), secretStore)
Expect(err).ToNot(HaveOccurred())
})
It("should sync secrets", func() {
By("creating a GCP SM Secret")
secretKey1 := fmt.Sprintf("%s-%s", f.Namespace.Name, "one")
secretValue := "great-value-test"
secret, err := createGCPSecretsManagerSecret(
projectID,
secretKey1, secretValue, []byte(credentials))
Expect(err).ToNot(HaveOccurred())
err = f.CRClient.Create(context.Background(), &esv1alpha1.ExternalSecret{
ObjectMeta: metav1.ObjectMeta{
Name: "simple-sync",
Namespace: f.Namespace.Name,
},
Spec: esv1alpha1.ExternalSecretSpec{
SecretStoreRef: esv1alpha1.SecretStoreRef{
Name: f.Namespace.Name,
},
Target: esv1alpha1.ExternalSecretTarget{
Name: targetSecret,
},
Data: []esv1alpha1.ExternalSecretData{
{
SecretKey: secretKey1,
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey1,
},
},
},
},
})
Expect(err).ToNot(HaveOccurred())
_, err = f.WaitForSecretValue(f.Namespace.Name, targetSecret, map[string][]byte{
secretKey1: []byte(secretValue),
})
Expect(err).ToNot(HaveOccurred())
err = deleteGCPSecretsManagerSecret(secret.Name, []byte(credentials))
Expect(err).ToNot(HaveOccurred())
})
It("should sync secrets with dataFrom", func() {
By("creating a GCP SM Secret with JSON string")
secretKey1 := fmt.Sprintf("%s-%s", f.Namespace.Name, "one")
targetSecretKey1 := "name"
targetSecretValue1 := "great-name"
targetSecretKey2 := "surname"
targetSecretValue2 := "great-surname"
secretValue := fmt.Sprintf("{ \"%s\": \"%s\", \"%s\": \"%s\" }", targetSecretKey1, targetSecretValue1, targetSecretKey2, targetSecretValue2)
secret, err := createGCPSecretsManagerSecret(
projectID,
secretKey1, secretValue, []byte(credentials))
Expect(err).ToNot(HaveOccurred())
err = f.CRClient.Create(context.Background(), &esv1alpha1.ExternalSecret{
ObjectMeta: metav1.ObjectMeta{
Name: "datafrom-sync",
Namespace: f.Namespace.Name,
},
Spec: esv1alpha1.ExternalSecretSpec{
SecretStoreRef: esv1alpha1.SecretStoreRef{
Name: f.Namespace.Name,
},
Target: esv1alpha1.ExternalSecretTarget{
Name: targetSecret,
},
DataFrom: []esv1alpha1.ExternalSecretDataRemoteRef{
{
Key: secretKey1,
},
},
},
})
Expect(err).ToNot(HaveOccurred())
_, err = f.WaitForSecretValue(f.Namespace.Name, targetSecret, map[string][]byte{
targetSecretKey1: []byte(targetSecretValue1),
targetSecretKey2: []byte(targetSecretValue2),
})
Expect(err).ToNot(HaveOccurred())
err = deleteGCPSecretsManagerSecret(secret.Name, []byte(credentials))
Expect(err).ToNot(HaveOccurred())
})
It("should sync secrets and get inner keys", func() {
By("creating a GCP SM Secret")
secretKey1 := fmt.Sprintf("%s-%s", f.Namespace.Name, "one")
targetSecretKey1 := "firstname"
targetSecretValue1 := "Tom"
targetSecretKey2 := "first_friend"
targetSecretValue2 := "Roger"
secretValue := fmt.Sprintf(
`{
"name": {"first": "%s", "last": "Anderson"},
"friends":
[
{"first": "Dale", "last": "Murphy"},
{"first": "%s", "last": "Craig"},
{"first": "Jane", "last": "Murphy"}
]
}`, targetSecretValue1, targetSecretValue2)
secret, err := createGCPSecretsManagerSecret(
projectID,
secretKey1, secretValue, []byte(credentials))
Expect(err).ToNot(HaveOccurred())
err = f.CRClient.Create(context.Background(), &esv1alpha1.ExternalSecret{
ObjectMeta: metav1.ObjectMeta{
Name: "datafrom-sync",
Namespace: f.Namespace.Name,
},
Spec: esv1alpha1.ExternalSecretSpec{
SecretStoreRef: esv1alpha1.SecretStoreRef{
Name: f.Namespace.Name,
},
Target: esv1alpha1.ExternalSecretTarget{
Name: targetSecret,
},
Data: []esv1alpha1.ExternalSecretData{
{
SecretKey: targetSecretKey1,
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey1,
Property: "name.first",
},
},
{
SecretKey: targetSecretKey2,
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey1,
Property: "friends.1.first",
},
},
},
},
})
Expect(err).ToNot(HaveOccurred())
_, err = f.WaitForSecretValue(f.Namespace.Name, targetSecret, map[string][]byte{
targetSecretKey1: []byte(targetSecretValue1),
targetSecretKey2: []byte(targetSecretValue2),
})
Expect(err).ToNot(HaveOccurred())
err = deleteGCPSecretsManagerSecret(secret.Name, []byte(credentials))
Expect(err).ToNot(HaveOccurred())
})
DescribeTable("sync secrets", framework.TableFunc(f, prov),
Entry(common.SimpleDataSync(f)),
Entry(common.JSONDataWithProperty(f)),
Entry(common.JSONDataFromSync(f)),
Entry(common.NestedJSONWithGJSON(f)),
Entry(common.JSONDataWithTemplate(f)),
)
})

139
e2e/suite/gcp/provider.go Normal file
View file

@ -0,0 +1,139 @@
/*
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.
limitations under the License.
*/
package gcp
import (
"context"
"fmt"
secretmanager "cloud.google.com/go/secretmanager/apiv1"
// nolint
. "github.com/onsi/ginkgo"
// nolint
. "github.com/onsi/gomega"
"golang.org/x/oauth2/google"
"google.golang.org/api/option"
secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
"github.com/external-secrets/external-secrets/e2e/framework"
gcpsm "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager"
)
type gcpProvider struct {
credentials string
projectID string
framework *framework.Framework
}
func newgcpProvider(f *framework.Framework, credentials, projectID string) *gcpProvider {
prov := &gcpProvider{
credentials: credentials,
projectID: projectID,
framework: f,
}
BeforeEach(prov.BeforeEach)
return prov
}
func (s *gcpProvider) CreateSecret(key, val string) {
ctx := context.Background()
config, err := google.JWTConfigFromJSON([]byte(s.credentials), gcpsm.CloudPlatformRole)
Expect(err).ToNot(HaveOccurred())
ts := config.TokenSource(ctx)
client, err := secretmanager.NewClient(ctx, option.WithTokenSource(ts))
Expect(err).ToNot(HaveOccurred())
defer client.Close()
// Create the request to create the secret.
createSecretReq := &secretmanagerpb.CreateSecretRequest{
Parent: fmt.Sprintf("projects/%s", s.projectID),
SecretId: key,
Secret: &secretmanagerpb.Secret{
Replication: &secretmanagerpb.Replication{
Replication: &secretmanagerpb.Replication_Automatic_{
Automatic: &secretmanagerpb.Replication_Automatic{},
},
},
},
}
secret, err := client.CreateSecret(ctx, createSecretReq)
Expect(err).ToNot(HaveOccurred())
addSecretVersionReq := &secretmanagerpb.AddSecretVersionRequest{
Parent: secret.Name,
Payload: &secretmanagerpb.SecretPayload{
Data: []byte(val),
},
}
_, err = client.AddSecretVersion(ctx, addSecretVersionReq)
Expect(err).ToNot(HaveOccurred())
}
func (s *gcpProvider) DeleteSecret(key string) {
ctx := context.Background()
config, err := google.JWTConfigFromJSON([]byte(s.credentials), gcpsm.CloudPlatformRole)
Expect(err).ToNot(HaveOccurred())
ts := config.TokenSource(ctx)
client, err := secretmanager.NewClient(ctx, option.WithTokenSource(ts))
Expect(err).ToNot(HaveOccurred())
defer client.Close()
req := &secretmanagerpb.DeleteSecretRequest{
Name: fmt.Sprintf("projects/%s/secrets/%s", s.projectID, key),
}
err = client.DeleteSecret(ctx, req)
Expect(err).ToNot(HaveOccurred())
}
func (s *gcpProvider) BeforeEach() {
By("creating a gcp secret")
gcpCreds := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "provider-secret",
Namespace: s.framework.Namespace.Name,
},
StringData: map[string]string{
"secret-access-credentials": s.credentials,
},
}
err := s.framework.CRClient.Create(context.Background(), gcpCreds)
Expect(err).ToNot(HaveOccurred())
By("creating an secret store for vault")
secretStore := &esv1alpha1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: s.framework.Namespace.Name,
Namespace: s.framework.Namespace.Name,
},
Spec: esv1alpha1.SecretStoreSpec{
Provider: &esv1alpha1.SecretStoreProvider{
GCPSM: &esv1alpha1.GCPSMProvider{
ProjectID: s.projectID,
Auth: esv1alpha1.GCPSMAuth{
SecretRef: esv1alpha1.GCPSMAuthSecretRef{
SecretAccessKey: esmeta.SecretKeySelector{
Name: "provider-secret",
Key: "secret-access-credentials",
},
},
},
},
},
},
}
err = s.framework.CRClient.Create(context.Background(), secretStore)
Expect(err).ToNot(HaveOccurred())
}

View file

@ -1,101 +0,0 @@
/*
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.
limitations under the License.
*/
package gcp
import (
"context"
"fmt"
secretmanager "cloud.google.com/go/secretmanager/apiv1"
"golang.org/x/oauth2/google"
"google.golang.org/api/option"
secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
gcpsm "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager"
)
// CreateAWSSecretsManagerSecret creates a sm secret with the given value.
func createGCPSecretsManagerSecret(projectID, secretName, secretValue string, credentials []byte) (*secretmanagerpb.Secret, error) {
ctx := context.Background()
config, err := google.JWTConfigFromJSON(credentials, gcpsm.CloudPlatformRole)
if err != nil {
return nil, fmt.Errorf("unable to procces JSON credentials: %w", err)
}
ts := config.TokenSource(ctx)
client, err := secretmanager.NewClient(ctx, option.WithTokenSource(ts))
if err != nil {
return nil, fmt.Errorf("failed to setup client: %w", err)
}
defer client.Close()
// Create the request to create the secret.
createSecretReq := &secretmanagerpb.CreateSecretRequest{
Parent: fmt.Sprintf("projects/%s", projectID),
SecretId: secretName,
Secret: &secretmanagerpb.Secret{
Replication: &secretmanagerpb.Replication{
Replication: &secretmanagerpb.Replication_Automatic_{
Automatic: &secretmanagerpb.Replication_Automatic{},
},
},
},
}
secret, err := client.CreateSecret(ctx, createSecretReq)
if err != nil {
return nil, fmt.Errorf("failed to create secret: %w", err)
}
// Declare the payload to store.
payload := []byte(secretValue)
// Build the request.
addSecretVersionReq := &secretmanagerpb.AddSecretVersionRequest{
Parent: secret.Name,
Payload: &secretmanagerpb.SecretPayload{
Data: payload,
},
}
// Call the API.
_, err = client.AddSecretVersion(ctx, addSecretVersionReq)
if err != nil {
return nil, fmt.Errorf("failed to add secret version: %w", err)
}
return secret, err
}
// deleteSecret deletes the secret with the given name and all of its versions.
func deleteGCPSecretsManagerSecret(secretName string, credentials []byte) error {
ctx := context.Background()
config, err := google.JWTConfigFromJSON(credentials, gcpsm.CloudPlatformRole)
if err != nil {
return fmt.Errorf("unable to procces JSON credentials: %w", err)
}
ts := config.TokenSource(ctx)
client, err := secretmanager.NewClient(ctx, option.WithTokenSource(ts))
if err != nil {
return fmt.Errorf("failed to setup client: %w", err)
}
defer client.Close()
// Build the request.
req := &secretmanagerpb.DeleteSecretRequest{
Name: secretName,
}
// Call the API.
if err := client.DeleteSecret(ctx, req); err != nil {
return fmt.Errorf("failed to delete secret: %w", err)
}
return nil
}

111
e2e/suite/vault/provider.go Normal file
View file

@ -0,0 +1,111 @@
/*
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 vault
import (
"context"
"fmt"
"net/http"
vault "github.com/hashicorp/vault/api"
//nolint
. "github.com/onsi/ginkgo"
//nolint
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
"github.com/external-secrets/external-secrets/e2e/framework"
)
type vaultProvider struct {
url string
token string
client *vault.Client
framework *framework.Framework
}
func newVaultProvider(f *framework.Framework, url, token string) *vaultProvider {
vc, err := vault.NewClient(&vault.Config{
Address: url,
})
Expect(err).ToNot(HaveOccurred())
vc.SetToken(token)
prov := &vaultProvider{
framework: f,
url: url,
token: token,
client: vc,
}
BeforeEach(prov.BeforeEach)
return prov
}
func (s *vaultProvider) CreateSecret(key, val string) {
req := s.client.NewRequest(http.MethodPost, fmt.Sprintf("/v1/secret/data/%s", key))
req.BodyBytes = []byte(fmt.Sprintf(`{"data": %s}`, val))
_, err := s.client.RawRequestWithContext(context.Background(), req)
Expect(err).ToNot(HaveOccurred())
}
func (s *vaultProvider) DeleteSecret(key string) {
req := s.client.NewRequest(http.MethodDelete, fmt.Sprintf("/v1/secret/data/%s", key))
_, err := s.client.RawRequestWithContext(context.Background(), req)
Expect(err).ToNot(HaveOccurred())
}
func (s *vaultProvider) BeforeEach() {
By("creating a vault secret")
vaultCreds := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "provider-secret",
Namespace: s.framework.Namespace.Name,
},
StringData: map[string]string{
"token": s.token, // vault dev-mode default token
},
}
err := s.framework.CRClient.Create(context.Background(), vaultCreds)
Expect(err).ToNot(HaveOccurred())
By("creating an secret store for vault")
secretStore := &esv1alpha1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: s.framework.Namespace.Name,
Namespace: s.framework.Namespace.Name,
},
Spec: esv1alpha1.SecretStoreSpec{
Provider: &esv1alpha1.SecretStoreProvider{
Vault: &esv1alpha1.VaultProvider{
Version: esv1alpha1.VaultKVStoreV2,
Path: "secret",
Server: s.url,
Auth: esv1alpha1.VaultAuth{
TokenSecretRef: &esmeta.SecretKeySelector{
Name: "provider-secret",
Key: "token",
},
},
},
},
},
}
err = s.framework.CRClient.Create(context.Background(), secretStore)
Expect(err).ToNot(HaveOccurred())
}

View file

@ -13,116 +13,24 @@ limitations under the License.
package vault
import (
"context"
"fmt"
"net/http"
vault "github.com/hashicorp/vault/api"
// nolint
. "github.com/onsi/ginkgo"
// nolint
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
. "github.com/onsi/ginkgo/extensions/table"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
"github.com/external-secrets/external-secrets/e2e/framework"
"github.com/external-secrets/external-secrets/e2e/suite/common"
)
var _ = Describe("[vault] ", func() {
f := framework.New("eso-vault")
var secretStore *esv1alpha1.SecretStore
BeforeEach(func() {
By("creating an secret store for vault")
vaultCreds := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: f.Namespace.Name,
Namespace: f.Namespace.Name,
},
StringData: map[string]string{
"token": "root", // vault dev-mode default token
},
}
err := f.CRClient.Create(context.Background(), vaultCreds)
Expect(err).ToNot(HaveOccurred())
secretStore = &esv1alpha1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: f.Namespace.Name,
Namespace: f.Namespace.Name,
},
Spec: esv1alpha1.SecretStoreSpec{
Provider: &esv1alpha1.SecretStoreProvider{
Vault: &esv1alpha1.VaultProvider{
Version: esv1alpha1.VaultKVStoreV2,
Path: "secret",
Server: "http://vault.default:8200",
Auth: esv1alpha1.VaultAuth{
TokenSecretRef: &esmeta.SecretKeySelector{
Name: f.Namespace.Name,
Key: "token",
},
},
},
},
},
}
err = f.CRClient.Create(context.Background(), secretStore)
Expect(err).ToNot(HaveOccurred())
})
It("should sync secrets", func() {
secretKey := fmt.Sprintf("%s-%s", f.Namespace.Name, "one")
secretProp := "example"
secretValue := "bar"
targetSecret := "target-secret"
By("creating a vault secret")
vc, err := vault.NewClient(&vault.Config{
Address: "http://vault.default:8200",
})
Expect(err).ToNot(HaveOccurred())
vc.SetToken("root") // dev-mode default token
req := vc.NewRequest(http.MethodPost, fmt.Sprintf("/v1/secret/data/%s", secretKey))
err = req.SetJSONBody(map[string]interface{}{
"data": map[string]string{
secretProp: secretValue,
},
})
Expect(err).ToNot(HaveOccurred())
_, err = vc.RawRequestWithContext(context.Background(), req)
Expect(err).ToNot(HaveOccurred())
By("creating ExternalSecret")
err = f.CRClient.Create(context.Background(), &esv1alpha1.ExternalSecret{
ObjectMeta: metav1.ObjectMeta{
Name: "simple-sync",
Namespace: f.Namespace.Name,
},
Spec: esv1alpha1.ExternalSecretSpec{
SecretStoreRef: esv1alpha1.SecretStoreRef{
Name: secretStore.Name,
},
Target: esv1alpha1.ExternalSecretTarget{
Name: targetSecret,
},
Data: []esv1alpha1.ExternalSecretData{
{
SecretKey: secretKey,
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: secretKey,
Property: secretProp,
},
},
},
},
})
Expect(err).ToNot(HaveOccurred())
_, err = f.WaitForSecretValue(f.Namespace.Name, targetSecret, map[string][]byte{
secretKey: []byte(secretValue),
})
Expect(err).ToNot(HaveOccurred())
})
DescribeTable("sync secrets",
framework.TableFunc(f,
newVaultProvider(f, "http://vault.default:8200", "root")),
Entry(common.JSONDataFromSync(f)),
Entry(common.JSONDataWithProperty(f)),
Entry(common.JSONDataWithTemplate(f)),
)
})

View file

@ -23,6 +23,7 @@ import (
"github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
kvauth "github.com/Azure/go-autorest/autorest/azure/auth"
"github.com/tidwall/gjson"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
@ -110,7 +111,14 @@ func (a *Azure) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretData
if err != nil {
return nil, err
}
return []byte(*secretResp.Value), nil
if ref.Property == "" {
return []byte(*secretResp.Value), nil
}
res := gjson.Get(*secretResp.Value, ref.Property)
if !res.Exists() {
return nil, fmt.Errorf("property %s does not exist in key %s", ref.Property, ref.Key)
}
return []byte(res.String()), err
case "cert":
// returns a CertBundle. We return CER contents of x509 certificate
// see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#CertificateBundle

View file

@ -167,7 +167,9 @@ func (sm *ProviderGCP) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSec
}
val := gjson.Get(payload, ref.Property)
if !val.Exists() {
return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
}
return []byte(val.String()), nil
}