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

aws secretsmanager/parameterstore referent auth (#1884)

* feat: implement referentAuth for aws

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>

* feat: e2e tests

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>

* Update pkg/provider/aws/provider.go

Co-authored-by: Gustavo Fernandes de Carvalho <gusfcarvalho@gmail.com>
Signed-off-by: Moritz Johner <moolen@users.noreply.github.com>

* Update pkg/provider/aws/provider.go

Co-authored-by: Gustavo Fernandes de Carvalho <gusfcarvalho@gmail.com>
Signed-off-by: Moritz Johner <moolen@users.noreply.github.com>

* feat: allow each credential to be referent

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
Signed-off-by: Moritz Johner <moolen@users.noreply.github.com>
Co-authored-by: Gustavo Fernandes de Carvalho <gusfcarvalho@gmail.com>
This commit is contained in:
Moritz Johner 2023-01-13 10:19:25 +01:00 committed by GitHub
parent f4e70ddfed
commit 5384954f46
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 320 additions and 145 deletions

View file

@ -16,8 +16,8 @@ env:
GO_VERSION: '1.19' GO_VERSION: '1.19'
GINKGO_VERSION: 'v2.1.6' GINKGO_VERSION: 'v2.1.6'
DOCKER_BUILDX_VERSION: 'v0.4.2' DOCKER_BUILDX_VERSION: 'v0.4.2'
KIND_VERSION: 'v0.14.0' KIND_VERSION: 'v0.17.0'
KIND_IMAGE: 'kindest/node:v1.24.2' KIND_IMAGE: 'kindest/node:v1.26.0'
# Common users. We can't run a step 'if secrets.GHCR_USERNAME != ""' but we can run # Common users. We can't run a step 'if secrets.GHCR_USERNAME != ""' but we can run
# a step 'if env.GHCR_USERNAME' != ""', so we copy these to succinctly test whether # a step 'if env.GHCR_USERNAME' != ""', so we copy these to succinctly test whether

View file

@ -46,8 +46,8 @@ The following table show the support for features across different providers.
| Provider | find by name | find by tags | metadataPolicy Fetch | referent authentication | store validation | push secret | | Provider | find by name | find by tags | metadataPolicy Fetch | referent authentication | store validation | push secret |
|---------------------------|:------------:|:------------:| :------------------: | :---------------------: | :--------------: | :---------: | |---------------------------|:------------:|:------------:| :------------------: | :---------------------: | :--------------: | :---------: |
| AWS Secrets Manager | x | x | | | x | | | AWS Secrets Manager | x | x | | x | x | |
| AWS Parameter Store | x | x | | | x | | | AWS Parameter Store | x | x | | x | x | |
| Hashicorp Vault | x | x | | | x | | | Hashicorp Vault | x | x | | | x | |
| GCP Secret Manager | x | x | | x | x | | | GCP Secret Manager | x | x | | x | x | |
| Azure Keyvault | x | x | x | x | x | | | Azure Keyvault | x | x | x | x | x | |

View file

@ -33,9 +33,7 @@ ginkgo_args=(
"--randomize-all" "--randomize-all"
"--flake-attempts=2" "--flake-attempts=2"
"-p" "-p"
"-progress"
"-trace" "-trace"
"--slow-spec-threshold=5m"
"-r" "-r"
"-v" "-v"
"-timeout=45m" "-timeout=45m"

View file

@ -27,9 +27,10 @@ import (
) )
const ( const (
WithReferencedIRSA = "with referenced IRSA" WithReferencedIRSA = "with referenced IRSA"
WithMountedIRSA = "with mounted IRSA" WithMountedIRSA = "with mounted IRSA"
StaticCredentialsSecretName = "provider-secret" StaticCredentialsSecretName = "provider-secret"
StaticReferentCredentialsSecretName = "referent-provider-secret"
) )
func ReferencedIRSAStoreName(f *framework.Framework) string { func ReferencedIRSAStoreName(f *framework.Framework) string {
@ -50,6 +51,38 @@ func UseMountedIRSAStore(tc *framework.TestCase) {
tc.ExternalSecret.Spec.SecretStoreRef.Name = MountedIRSAStoreName(tc.Framework) tc.ExternalSecret.Spec.SecretStoreRef.Name = MountedIRSAStoreName(tc.Framework)
} }
const (
StaticStoreName = "aws-static-creds"
staticKeyID = "kid"
staticSecretAccessKey = "sak"
staticySessionToken = "st"
)
func newStaticStoreProvider(serviceType esv1beta1.AWSServiceType, region, secretName string) *esv1beta1.SecretStoreProvider {
return &esv1beta1.SecretStoreProvider{
AWS: &esv1beta1.AWSProvider{
Service: serviceType,
Region: region,
Auth: esv1beta1.AWSAuth{
SecretRef: &esv1beta1.AWSAuthSecretRef{
AccessKeyID: esmetav1.SecretKeySelector{
Name: StaticReferentCredentialsSecretName,
Key: staticKeyID,
},
SecretAccessKey: esmetav1.SecretKeySelector{
Name: StaticReferentCredentialsSecretName,
Key: staticSecretAccessKey,
},
SessionToken: &esmetav1.SecretKeySelector{
Name: StaticReferentCredentialsSecretName,
Key: staticySessionToken,
},
},
},
},
}
}
// StaticStore is namespaced and references // StaticStore is namespaced and references
// static credentials from a secret. // static credentials from a secret.
func SetupStaticStore(f *framework.Framework, kid, sak, st, region string, serviceType esv1beta1.AWSServiceType) { func SetupStaticStore(f *framework.Framework, kid, sak, st, region string, serviceType esv1beta1.AWSServiceType) {
@ -59,9 +92,9 @@ func SetupStaticStore(f *framework.Framework, kid, sak, st, region string, servi
Namespace: f.Namespace.Name, Namespace: f.Namespace.Name,
}, },
StringData: map[string]string{ StringData: map[string]string{
"kid": kid, staticKeyID: kid,
"sak": sak, staticSecretAccessKey: sak,
"st": st, staticySessionToken: st,
}, },
} }
err := f.CRClient.Create(context.Background(), awsCreds) err := f.CRClient.Create(context.Background(), awsCreds)
@ -69,34 +102,48 @@ func SetupStaticStore(f *framework.Framework, kid, sak, st, region string, servi
secretStore := &esv1beta1.SecretStore{ secretStore := &esv1beta1.SecretStore{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: f.Namespace.Name, Name: StaticStoreName,
Namespace: f.Namespace.Name, Namespace: f.Namespace.Name,
}, },
Spec: esv1beta1.SecretStoreSpec{ Spec: esv1beta1.SecretStoreSpec{
Provider: &esv1beta1.SecretStoreProvider{ Provider: newStaticStoreProvider(serviceType, region, StaticCredentialsSecretName),
AWS: &esv1beta1.AWSProvider{
Service: serviceType,
Region: region,
Auth: esv1beta1.AWSAuth{
SecretRef: &esv1beta1.AWSAuthSecretRef{
AccessKeyID: esmetav1.SecretKeySelector{
Name: StaticCredentialsSecretName,
Key: "kid",
},
SecretAccessKey: esmetav1.SecretKeySelector{
Name: StaticCredentialsSecretName,
Key: "sak",
},
SessionToken: &esmetav1.SecretKeySelector{
Name: StaticCredentialsSecretName,
Key: "st",
},
},
},
},
},
}, },
} }
err = f.CRClient.Create(context.Background(), secretStore) err = f.CRClient.Create(context.Background(), secretStore)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
} }
// CreateReferentStaticStore creates a CSS with referent auth and
// creates a secret with static authentication credentials in the ExternalSecret namespace.
func CreateReferentStaticStore(f *framework.Framework, kid, sak, st, region string, serviceType esv1beta1.AWSServiceType) {
ns := f.Namespace.Name
awsCreds := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: StaticReferentCredentialsSecretName,
Namespace: ns,
},
StringData: map[string]string{
staticKeyID: kid,
staticSecretAccessKey: sak,
staticySessionToken: st,
},
}
err := f.CRClient.Create(context.Background(), awsCreds)
Expect(err).ToNot(HaveOccurred())
secretStore := &esv1beta1.ClusterSecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: ReferentSecretStoreName(f),
},
Spec: esv1beta1.SecretStoreSpec{
Provider: newStaticStoreProvider(serviceType, region, StaticReferentCredentialsSecretName),
},
}
err = f.CRClient.Create(context.Background(), secretStore)
Expect(err).ToNot(HaveOccurred())
}
func ReferentSecretStoreName(f *framework.Framework) string {
return "referent-auth" + f.Namespace.Name
}

View file

@ -20,7 +20,14 @@ import (
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
"github.com/external-secrets/external-secrets-e2e/framework" "github.com/external-secrets/external-secrets-e2e/framework"
awscommon "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/aws"
"github.com/external-secrets/external-secrets-e2e/suites/provider/cases/common" "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/common"
esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
)
const (
withStaticAuth = "with static auth"
withReferentStaticAuth = "with static referent auth"
) )
var _ = Describe("[aws] ", Label("aws", "parameterstore"), func() { var _ = Describe("[aws] ", Label("aws", "parameterstore"), func() {
@ -30,25 +37,41 @@ var _ = Describe("[aws] ", Label("aws", "parameterstore"), func() {
DescribeTable("sync secrets", DescribeTable("sync secrets",
framework.TableFunc(f, framework.TableFunc(f,
prov), prov),
Entry(common.SimpleDataSync(f)), framework.Compose(withStaticAuth, f, common.SimpleDataSync, useStaticAuth),
Entry(common.NestedJSONWithGJSON(f)), framework.Compose(withStaticAuth, f, common.NestedJSONWithGJSON, useStaticAuth),
Entry(common.JSONDataFromSync(f)), framework.Compose(withStaticAuth, f, common.JSONDataFromSync, useStaticAuth),
Entry(common.JSONDataFromRewrite(f)), framework.Compose(withStaticAuth, f, common.JSONDataFromRewrite, useStaticAuth),
Entry(common.JSONDataWithProperty(f)), framework.Compose(withStaticAuth, f, common.JSONDataWithProperty, useStaticAuth),
Entry(common.JSONDataWithTemplate(f)), framework.Compose(withStaticAuth, f, common.JSONDataWithTemplate, useStaticAuth),
Entry(common.DockerJSONConfig(f)), framework.Compose(withStaticAuth, f, common.DockerJSONConfig, useStaticAuth),
Entry(common.DataPropertyDockerconfigJSON(f)), framework.Compose(withStaticAuth, f, common.DataPropertyDockerconfigJSON, useStaticAuth),
Entry(common.SSHKeySync(f)), framework.Compose(withStaticAuth, f, common.SSHKeySync, useStaticAuth),
Entry(common.SSHKeySyncDataProperty(f)), framework.Compose(withStaticAuth, f, common.SSHKeySyncDataProperty, useStaticAuth),
Entry(common.SyncWithoutTargetName(f)), framework.Compose(withStaticAuth, f, common.SyncWithoutTargetName, useStaticAuth),
Entry(common.JSONDataWithoutTargetName(f)), framework.Compose(withStaticAuth, f, common.JSONDataWithoutTargetName, useStaticAuth),
Entry(common.SyncV1Alpha1(f)),
Entry(common.DeletionPolicyDelete(f)), framework.Compose(withStaticAuth, f, common.SyncV1Alpha1, useStaticAuth),
framework.Compose(withStaticAuth, f, common.DeletionPolicyDelete, useStaticAuth),
// referent auth
framework.Compose(withReferentStaticAuth, f, common.SimpleDataSync, useReferentStaticAuth),
// These are specific to parameterstore // These are specific to parameterstore
Entry(FindByName(f)), framework.Compose(withStaticAuth, f, FindByName, useStaticAuth),
Entry(FindByNameWithPath(f)), framework.Compose(withStaticAuth, f, FindByNameWithPath, useStaticAuth),
Entry(FindByTag(f)), framework.Compose(withStaticAuth, f, FindByTag, useStaticAuth),
Entry(FindByTagWithPath(f)), framework.Compose(withStaticAuth, f, FindByTagWithPath, useStaticAuth),
) )
}) })
func useStaticAuth(tc *framework.TestCase) {
tc.ExternalSecret.Spec.SecretStoreRef.Name = awscommon.StaticStoreName
if tc.ExternalSecretV1Alpha1 != nil {
tc.ExternalSecretV1Alpha1.Spec.SecretStoreRef.Name = awscommon.StaticStoreName
}
}
func useReferentStaticAuth(tc *framework.TestCase) {
tc.ExternalSecret.Spec.SecretStoreRef.Name = awscommon.ReferentSecretStoreName(tc.Framework)
tc.ExternalSecret.Spec.SecretStoreRef.Kind = esapi.ClusterSecretStoreKind
}

View file

@ -34,7 +34,7 @@ import (
"github.com/external-secrets/external-secrets-e2e/framework" "github.com/external-secrets/external-secrets-e2e/framework"
"github.com/external-secrets/external-secrets-e2e/framework/log" "github.com/external-secrets/external-secrets-e2e/framework/log"
common "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/aws" awscommon "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/aws"
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1" esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
esmetav1 "github.com/external-secrets/external-secrets/apis/meta/v1" esmetav1 "github.com/external-secrets/external-secrets/apis/meta/v1"
) )
@ -68,19 +68,15 @@ func NewProvider(f *framework.Framework, kid, sak, st, region, saName, saNamespa
} }
BeforeEach(func() { BeforeEach(func() {
common.SetupStaticStore(f, kid, sak, st, region, esv1beta1.AWSServiceParameterStore) awscommon.SetupStaticStore(f, kid, sak, st, region, esv1beta1.AWSServiceParameterStore)
awscommon.CreateReferentStaticStore(f, kid, sak, st, region, esv1beta1.AWSServiceParameterStore)
prov.SetupReferencedIRSAStore() prov.SetupReferencedIRSAStore()
prov.SetupMountedIRSAStore() prov.SetupMountedIRSAStore()
}) })
AfterEach(func() { AfterEach(func() {
// Cleanup ClusterSecretStore prov.TeardownReferencedIRSAStore()
err := prov.framework.CRClient.Delete(context.Background(), &esv1beta1.ClusterSecretStore{ prov.TeardownMountedIRSAStore()
ObjectMeta: metav1.ObjectMeta{
Name: common.ReferencedIRSAStoreName(f),
},
})
Expect(err).ToNot(HaveOccurred())
}) })
return prov return prov
@ -132,7 +128,7 @@ func (s *Provider) DeleteSecret(key string) {
func (s *Provider) SetupMountedIRSAStore() { func (s *Provider) SetupMountedIRSAStore() {
secretStore := &esv1beta1.SecretStore{ secretStore := &esv1beta1.SecretStore{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: common.MountedIRSAStoreName(s.framework), Name: awscommon.MountedIRSAStoreName(s.framework),
Namespace: s.framework.Namespace.Name, Namespace: s.framework.Namespace.Name,
}, },
Spec: esv1beta1.SecretStoreSpec{ Spec: esv1beta1.SecretStoreSpec{
@ -149,13 +145,21 @@ func (s *Provider) SetupMountedIRSAStore() {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
} }
func (s *Provider) TeardownMountedIRSAStore() {
s.framework.CRClient.Delete(context.Background(), &esv1beta1.ClusterSecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: awscommon.MountedIRSAStoreName(s.framework),
},
})
}
// ReferncedIRSAStore is a ClusterStore // ReferncedIRSAStore is a ClusterStore
// that references a (IRSA-) ServiceAccount in the default namespace. // that references a (IRSA-) ServiceAccount in the default namespace.
func (s *Provider) SetupReferencedIRSAStore() { func (s *Provider) SetupReferencedIRSAStore() {
log.Logf("creating IRSA ClusterSecretStore %s", s.framework.Namespace.Name) log.Logf("creating IRSA ClusterSecretStore %s", s.framework.Namespace.Name)
secretStore := &esv1beta1.ClusterSecretStore{ secretStore := &esv1beta1.ClusterSecretStore{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: common.ReferencedIRSAStoreName(s.framework), Name: awscommon.ReferencedIRSAStoreName(s.framework),
}, },
} }
_, err := controllerutil.CreateOrUpdate(context.Background(), s.framework.CRClient, secretStore, func() error { _, err := controllerutil.CreateOrUpdate(context.Background(), s.framework.CRClient, secretStore, func() error {
@ -177,3 +181,11 @@ func (s *Provider) SetupReferencedIRSAStore() {
}) })
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
} }
func (s *Provider) TeardownReferencedIRSAStore() {
s.framework.CRClient.Delete(context.Background(), &esv1beta1.ClusterSecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: awscommon.ReferencedIRSAStoreName(s.framework),
},
})
}

View file

@ -35,7 +35,7 @@ import (
"github.com/external-secrets/external-secrets-e2e/framework" "github.com/external-secrets/external-secrets-e2e/framework"
"github.com/external-secrets/external-secrets-e2e/framework/log" "github.com/external-secrets/external-secrets-e2e/framework/log"
common "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/aws" awscommon "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/aws"
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1" esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
esmetav1 "github.com/external-secrets/external-secrets/apis/meta/v1" esmetav1 "github.com/external-secrets/external-secrets/apis/meta/v1"
) )
@ -69,19 +69,16 @@ func NewProvider(f *framework.Framework, kid, sak, st, region, saName, saNamespa
} }
BeforeEach(func() { BeforeEach(func() {
common.SetupStaticStore(f, kid, sak, st, region, esv1beta1.AWSServiceSecretsManager) awscommon.SetupStaticStore(f, kid, sak, st, region, esv1beta1.AWSServiceSecretsManager)
awscommon.CreateReferentStaticStore(f, kid, sak, st, region, esv1beta1.AWSServiceSecretsManager)
prov.SetupReferencedIRSAStore() prov.SetupReferencedIRSAStore()
prov.SetupMountedIRSAStore() prov.SetupMountedIRSAStore()
}) })
AfterEach(func() { AfterEach(func() {
// Cleanup ClusterSecretStore prov.TeardownReferencedIRSAStore()
err := prov.framework.CRClient.Delete(context.Background(), &esv1beta1.ClusterSecretStore{ prov.TeardownMountedIRSAStore()
ObjectMeta: metav1.ObjectMeta{
Name: common.ReferencedIRSAStoreName(f),
},
})
Expect(err).ToNot(HaveOccurred())
}) })
return prov return prov
@ -150,7 +147,7 @@ func (s *Provider) DeleteSecret(key string) {
func (s *Provider) SetupMountedIRSAStore() { func (s *Provider) SetupMountedIRSAStore() {
secretStore := &esv1beta1.SecretStore{ secretStore := &esv1beta1.SecretStore{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: common.MountedIRSAStoreName(s.framework), Name: awscommon.MountedIRSAStoreName(s.framework),
Namespace: s.framework.Namespace.Name, Namespace: s.framework.Namespace.Name,
}, },
Spec: esv1beta1.SecretStoreSpec{ Spec: esv1beta1.SecretStoreSpec{
@ -167,13 +164,21 @@ func (s *Provider) SetupMountedIRSAStore() {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
} }
func (s *Provider) TeardownMountedIRSAStore() {
s.framework.CRClient.Delete(context.Background(), &esv1beta1.ClusterSecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: awscommon.MountedIRSAStoreName(s.framework),
},
})
}
// ReferncedIRSAStore is a ClusterStore // ReferncedIRSAStore is a ClusterStore
// that references a (IRSA-) ServiceAccount in the default namespace. // that references a (IRSA-) ServiceAccount in the default namespace.
func (s *Provider) SetupReferencedIRSAStore() { func (s *Provider) SetupReferencedIRSAStore() {
log.Logf("creating IRSA ClusterSecretStore %s", s.framework.Namespace.Name) log.Logf("creating IRSA ClusterSecretStore %s", s.framework.Namespace.Name)
secretStore := &esv1beta1.ClusterSecretStore{ secretStore := &esv1beta1.ClusterSecretStore{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: common.ReferencedIRSAStoreName(s.framework), Name: awscommon.ReferencedIRSAStoreName(s.framework),
}, },
} }
_, err := controllerutil.CreateOrUpdate(context.Background(), s.framework.CRClient, secretStore, func() error { _, err := controllerutil.CreateOrUpdate(context.Background(), s.framework.CRClient, secretStore, func() error {
@ -195,3 +200,11 @@ func (s *Provider) SetupReferencedIRSAStore() {
}) })
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
} }
func (s *Provider) TeardownReferencedIRSAStore() {
s.framework.CRClient.Delete(context.Background(), &esv1beta1.ClusterSecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: awscommon.ReferencedIRSAStoreName(s.framework),
},
})
}

View file

@ -20,7 +20,14 @@ import (
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
"github.com/external-secrets/external-secrets-e2e/framework" "github.com/external-secrets/external-secrets-e2e/framework"
awscommon "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/aws"
"github.com/external-secrets/external-secrets-e2e/suites/provider/cases/common" "github.com/external-secrets/external-secrets-e2e/suites/provider/cases/common"
esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
)
const (
withStaticAuth = "with static auth"
withReferentStaticAuth = "with static referent auth"
) )
var _ = Describe("[aws] ", Label("aws", "secretsmanager"), func() { var _ = Describe("[aws] ", Label("aws", "secretsmanager"), func() {
@ -30,23 +37,38 @@ var _ = Describe("[aws] ", Label("aws", "secretsmanager"), func() {
DescribeTable("sync secrets", DescribeTable("sync secrets",
framework.TableFunc(f, framework.TableFunc(f,
prov), prov),
Entry(common.SimpleDataSync(f)), framework.Compose(withStaticAuth, f, common.SimpleDataSync, useStaticAuth),
Entry(common.NestedJSONWithGJSON(f)), framework.Compose(withStaticAuth, f, common.NestedJSONWithGJSON, useStaticAuth),
Entry(common.JSONDataFromSync(f)), framework.Compose(withStaticAuth, f, common.JSONDataFromSync, useStaticAuth),
Entry(common.JSONDataFromRewrite(f)), framework.Compose(withStaticAuth, f, common.JSONDataFromRewrite, useStaticAuth),
Entry(common.JSONDataWithProperty(f)), framework.Compose(withStaticAuth, f, common.JSONDataWithProperty, useStaticAuth),
Entry(common.JSONDataWithTemplate(f)), framework.Compose(withStaticAuth, f, common.JSONDataWithTemplate, useStaticAuth),
Entry(common.DockerJSONConfig(f)), framework.Compose(withStaticAuth, f, common.DockerJSONConfig, useStaticAuth),
Entry(common.DataPropertyDockerconfigJSON(f)), framework.Compose(withStaticAuth, f, common.DataPropertyDockerconfigJSON, useStaticAuth),
Entry(common.SSHKeySync(f)), framework.Compose(withStaticAuth, f, common.SSHKeySync, useStaticAuth),
Entry(common.SSHKeySyncDataProperty(f)), framework.Compose(withStaticAuth, f, common.SSHKeySyncDataProperty, useStaticAuth),
Entry(common.SyncWithoutTargetName(f)), framework.Compose(withStaticAuth, f, common.SyncWithoutTargetName, useStaticAuth),
Entry(common.JSONDataWithoutTargetName(f)), framework.Compose(withStaticAuth, f, common.JSONDataWithoutTargetName, useStaticAuth),
Entry(common.FindByName(f)), framework.Compose(withStaticAuth, f, common.FindByName, useStaticAuth),
Entry(common.FindByNameWithPath(f)), framework.Compose(withStaticAuth, f, common.FindByNameWithPath, useStaticAuth),
Entry(common.FindByTag(f)), framework.Compose(withStaticAuth, f, common.FindByTag, useStaticAuth),
Entry(common.FindByTagWithPath(f)), framework.Compose(withStaticAuth, f, common.FindByTagWithPath, useStaticAuth),
Entry(common.SyncV1Alpha1(f)), framework.Compose(withStaticAuth, f, common.SyncV1Alpha1, useStaticAuth),
Entry(common.DeletionPolicyDelete(f)), framework.Compose(withStaticAuth, f, common.DeletionPolicyDelete, useStaticAuth),
// referent auth
framework.Compose(withStaticAuth, f, common.SimpleDataSync, useReferentStaticAuth),
) )
}) })
func useStaticAuth(tc *framework.TestCase) {
tc.ExternalSecret.Spec.SecretStoreRef.Name = awscommon.StaticStoreName
if tc.ExternalSecretV1Alpha1 != nil {
tc.ExternalSecretV1Alpha1.Spec.SecretStoreRef.Name = awscommon.StaticStoreName
}
}
func useReferentStaticAuth(tc *framework.TestCase) {
tc.ExternalSecret.Spec.SecretStoreRef.Name = awscommon.ReferentSecretStoreName(tc.Framework)
tc.ExternalSecret.Spec.SecretStoreRef.Kind = esapi.ClusterSecretStoreKind
}

View file

@ -87,7 +87,7 @@ func New(ctx context.Context, store esv1beta1.GenericStore, kube client.Client,
// use credentials via service account token // use credentials via service account token
jwtAuth := prov.Auth.JWTAuth jwtAuth := prov.Auth.JWTAuth
if jwtAuth != nil { if jwtAuth != nil {
creds, err = sessionFromServiceAccount(ctx, prov.Auth, prov.Region, isClusterKind, kube, namespace, jwtProvider) creds, err = credsFromServiceAccount(ctx, prov.Auth, prov.Region, isClusterKind, kube, namespace, jwtProvider)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -97,7 +97,7 @@ func New(ctx context.Context, store esv1beta1.GenericStore, kube client.Client,
secretRef := prov.Auth.SecretRef secretRef := prov.Auth.SecretRef
if secretRef != nil { if secretRef != nil {
log.V(1).Info("using credentials from secretRef") log.V(1).Info("using credentials from secretRef")
creds, err = sessionFromSecretRef(ctx, prov.Auth, isClusterKind, kube, namespace) creds, err = credsFromSecretRef(ctx, prov.Auth, isClusterKind, kube, namespace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -141,7 +141,7 @@ func NewGeneratorSession(ctx context.Context, auth esv1beta1.AWSAuth, role, regi
// use credentials via service account token // use credentials via service account token
jwtAuth := auth.JWTAuth jwtAuth := auth.JWTAuth
if jwtAuth != nil { if jwtAuth != nil {
creds, err = sessionFromServiceAccount(ctx, auth, region, false, kube, namespace, jwtProvider) creds, err = credsFromServiceAccount(ctx, auth, region, false, kube, namespace, jwtProvider)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -151,7 +151,7 @@ func NewGeneratorSession(ctx context.Context, auth esv1beta1.AWSAuth, role, regi
secretRef := auth.SecretRef secretRef := auth.SecretRef
if secretRef != nil { if secretRef != nil {
log.V(1).Info("using credentials from secretRef") log.V(1).Info("using credentials from secretRef")
creds, err = sessionFromSecretRef(ctx, auth, false, kube, namespace) creds, err = credsFromSecretRef(ctx, auth, false, kube, namespace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -178,16 +178,16 @@ func NewGeneratorSession(ctx context.Context, auth esv1beta1.AWSAuth, role, regi
return sess, nil return sess, nil
} }
func sessionFromSecretRef(ctx context.Context, auth esv1beta1.AWSAuth, isClusterKind bool, kube client.Client, namespace string) (*credentials.Credentials, error) { // credsFromSecretRef pulls access-key / secret-access-key from a secretRef to
// construct a aws.Credentials object
// The namespace of the external secret is used if the ClusterSecretStore does not specify a namespace (referentAuth)
// If the ClusterSecretStore defines a namespace it will take precedence.
func credsFromSecretRef(ctx context.Context, auth esv1beta1.AWSAuth, isClusterKind bool, kube client.Client, namespace string) (*credentials.Credentials, error) {
ke := client.ObjectKey{ ke := client.ObjectKey{
Name: auth.SecretRef.AccessKeyID.Name, Name: auth.SecretRef.AccessKeyID.Name,
Namespace: namespace, // default to ExternalSecret namespace Namespace: namespace,
} }
// only ClusterStore is allowed to set namespace (and then it's required) if isClusterKind && auth.SecretRef.AccessKeyID.Namespace != nil {
if isClusterKind {
if auth.SecretRef.AccessKeyID.Namespace == nil {
return nil, fmt.Errorf(errInvalidClusterStoreMissingAKIDNamespace)
}
ke.Namespace = *auth.SecretRef.AccessKeyID.Namespace ke.Namespace = *auth.SecretRef.AccessKeyID.Namespace
} }
akSecret := v1.Secret{} akSecret := v1.Secret{}
@ -197,13 +197,9 @@ func sessionFromSecretRef(ctx context.Context, auth esv1beta1.AWSAuth, isCluster
} }
ke = client.ObjectKey{ ke = client.ObjectKey{
Name: auth.SecretRef.SecretAccessKey.Name, Name: auth.SecretRef.SecretAccessKey.Name,
Namespace: namespace, // default to ExternalSecret namespace Namespace: namespace,
} }
// only ClusterStore is allowed to set namespace (and then it's required) if isClusterKind && auth.SecretRef.SecretAccessKey.Namespace != nil {
if isClusterKind {
if auth.SecretRef.SecretAccessKey.Namespace == nil {
return nil, fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
}
ke.Namespace = *auth.SecretRef.SecretAccessKey.Namespace ke.Namespace = *auth.SecretRef.SecretAccessKey.Namespace
} }
sakSecret := v1.Secret{} sakSecret := v1.Secret{}
@ -224,13 +220,9 @@ func sessionFromSecretRef(ctx context.Context, auth esv1beta1.AWSAuth, isCluster
if auth.SecretRef.SessionToken != nil { if auth.SecretRef.SessionToken != nil {
ke = client.ObjectKey{ ke = client.ObjectKey{
Name: auth.SecretRef.SessionToken.Name, Name: auth.SecretRef.SessionToken.Name,
Namespace: namespace, // default to ExternalSecret namespace Namespace: namespace,
} }
// only ClusterStore is allowed to set namespace (and then it's required) if isClusterKind && auth.SecretRef.SessionToken.Namespace != nil {
if isClusterKind {
if auth.SecretRef.SessionToken.Namespace == nil {
return nil, fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
}
ke.Namespace = *auth.SecretRef.SessionToken.Namespace ke.Namespace = *auth.SecretRef.SessionToken.Namespace
} }
stSecret := v1.Secret{} stSecret := v1.Secret{}
@ -244,9 +236,14 @@ func sessionFromSecretRef(ctx context.Context, auth esv1beta1.AWSAuth, isCluster
return credentials.NewStaticCredentials(aks, sak, sessionToken), err return credentials.NewStaticCredentials(aks, sak, sessionToken), err
} }
func sessionFromServiceAccount(ctx context.Context, auth esv1beta1.AWSAuth, region string, isClusterKind bool, kube client.Client, namespace string, jwtProvider jwtProviderFactory) (*credentials.Credentials, error) { // credsFromServiceAccount uses a Kubernetes Service Account to acquire temporary
// credentials using aws.AssumeRoleWithWebIdentity. It will assume the role defined
// in the ServiceAccount annotation.
// If the ClusterSecretStore does not define a namespace it will use the namespace from the ExternalSecret (referentAuth).
// If the ClusterSecretStore defines the namespace it will take precedence.
func credsFromServiceAccount(ctx context.Context, auth esv1beta1.AWSAuth, region string, isClusterKind bool, kube client.Client, namespace string, jwtProvider jwtProviderFactory) (*credentials.Credentials, error) {
name := auth.JWTAuth.ServiceAccountRef.Name name := auth.JWTAuth.ServiceAccountRef.Name
if isClusterKind { if isClusterKind && auth.JWTAuth.ServiceAccountRef.Namespace != nil {
namespace = *auth.JWTAuth.ServiceAccountRef.Namespace namespace = *auth.JWTAuth.ServiceAccountRef.Namespace
} }
sa := v1.ServiceAccount{} sa := v1.ServiceAccount{}
@ -327,7 +324,7 @@ func DefaultSTSProvider(sess *session.Session) stsiface.STSAPI {
return sts.New(sess) return sts.New(sess)
} }
// getAWSSession check if an AWS session should be reused // getAWSSession checks if an AWS session should be reused
// it returns the aws session or an error. // it returns the aws session or an error.
func getAWSSession(config *aws.Config, enableCache bool, name, kind, namespace, resourceVersion string) (*session.Session, error) { func getAWSSession(config *aws.Config, enableCache bool, name, kind, namespace, resourceVersion string) (*session.Session, error) {
tmpSession := SessionCache{ tmpSession := SessionCache{

View file

@ -311,7 +311,7 @@ func TestNewSession(t *testing.T) {
expectedSecretKey: "2222", expectedSecretKey: "2222",
}, },
{ {
name: "namespace is mandatory when using ClusterStore with SecretKeySelector", name: "ClusterStore should use credentials from a ExternalSecret namespace (referentAuth)",
namespace: esNamespaceKey, namespace: esNamespaceKey,
store: &esv1beta1.ClusterSecretStore{ store: &esv1beta1.ClusterSecretStore{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
@ -337,7 +337,21 @@ func TestNewSession(t *testing.T) {
}, },
}, },
}, },
expectErr: "invalid ClusterSecretStore: missing AWS AccessKeyID Namespace", secrets: []v1.Secret{
{
ObjectMeta: metav1.ObjectMeta{
Name: "onesecret",
Namespace: esNamespaceKey,
},
Data: map[string][]byte{
"one": []byte("7777"),
"two": []byte("4444"),
},
},
},
expectProvider: true,
expectedKeyID: "7777",
expectedSecretKey: "4444",
}, },
{ {
name: "jwt auth via cluster secret store", name: "jwt auth via cluster secret store",

View file

@ -42,8 +42,9 @@ var (
// ParameterStore is a provider for AWS ParameterStore. // ParameterStore is a provider for AWS ParameterStore.
type ParameterStore struct { type ParameterStore struct {
sess *session.Session sess *session.Session
client PMInterface client PMInterface
referentAuth bool
} }
// PMInterface is a subset of the parameterstore api. // PMInterface is a subset of the parameterstore api.
@ -61,10 +62,11 @@ const (
) )
// New constructs a ParameterStore Provider that is specific to a store. // New constructs a ParameterStore Provider that is specific to a store.
func New(sess *session.Session, cfg *aws.Config) (*ParameterStore, error) { func New(sess *session.Session, cfg *aws.Config, referentAuth bool) (*ParameterStore, error) {
return &ParameterStore{ return &ParameterStore{
sess: sess, sess: sess,
client: ssm.New(sess, cfg), referentAuth: referentAuth,
client: ssm.New(sess, cfg),
}, nil }, nil
} }
@ -377,6 +379,11 @@ func (pm *ParameterStore) Close(ctx context.Context) error {
} }
func (pm *ParameterStore) Validate() (esv1beta1.ValidationResult, error) { func (pm *ParameterStore) Validate() (esv1beta1.ValidationResult, error) {
// skip validation stack because it depends on the namespace
// of the ExternalSecret
if pm.referentAuth {
return esv1beta1.ValidationResultUnknown, nil
}
_, err := pm.sess.Config.Credentials.Get() _, err := pm.sess.Config.Credentials.Get()
if err != nil { if err != nil {
return esv1beta1.ValidationResultError, err return esv1beta1.ValidationResultError, err

View file

@ -23,6 +23,7 @@ import (
awsclient "github.com/aws/aws-sdk-go/aws/client" awsclient "github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1" esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
@ -68,17 +69,22 @@ func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
// case: static credentials // case: static credentials
if prov.Auth.SecretRef != nil { if prov.Auth.SecretRef != nil {
if err := utils.ValidateSecretSelector(store, prov.Auth.SecretRef.AccessKeyID); err != nil { if err := utils.ValidateReferentSecretSelector(store, prov.Auth.SecretRef.AccessKeyID); err != nil {
return fmt.Errorf("invalid Auth.SecretRef.AccessKeyID: %w", err) return fmt.Errorf("invalid Auth.SecretRef.AccessKeyID: %w", err)
} }
if err := utils.ValidateSecretSelector(store, prov.Auth.SecretRef.SecretAccessKey); err != nil { if err := utils.ValidateReferentSecretSelector(store, prov.Auth.SecretRef.SecretAccessKey); err != nil {
return fmt.Errorf("invalid Auth.SecretRef.SecretAccessKey: %w", err) return fmt.Errorf("invalid Auth.SecretRef.SecretAccessKey: %w", err)
} }
if prov.Auth.SecretRef.SessionToken != nil {
if err := utils.ValidateReferentSecretSelector(store, *prov.Auth.SecretRef.SessionToken); err != nil {
return fmt.Errorf("invalid Auth.SecretRef.SessionToken: %w", err)
}
}
} }
// case: jwt credentials // case: jwt credentials
if prov.Auth.JWTAuth != nil && prov.Auth.JWTAuth.ServiceAccountRef != nil { if prov.Auth.JWTAuth != nil && prov.Auth.JWTAuth.ServiceAccountRef != nil {
if err := utils.ValidateServiceAccountSelector(store, *prov.Auth.JWTAuth.ServiceAccountRef); err != nil { if err := utils.ValidateReferentServiceAccountSelector(store, *prov.Auth.JWTAuth.ServiceAccountRef); err != nil {
return fmt.Errorf("invalid Auth.JWT.ServiceAccountRef: %w", err) return fmt.Errorf("invalid Auth.JWT.ServiceAccountRef: %w", err)
} }
} }
@ -114,6 +120,21 @@ func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Cl
storeSpec := store.GetSpec() storeSpec := store.GetSpec()
var cfg *aws.Config var cfg *aws.Config
// allow SecretStore controller validation to pass
// when using referent namespace.
if util.IsReferentSpec(prov.Auth) && namespace == "" &&
store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind {
cfg = aws.NewConfig().WithRegion("eu-west-1").WithEndpointResolver(awsauth.ResolveEndpoint())
sess := &session.Session{Config: cfg}
switch prov.Service {
case esv1beta1.AWSServiceSecretsManager:
return secretsmanager.New(sess, cfg, true)
case esv1beta1.AWSServiceParameterStore:
return parameterstore.New(sess, cfg, true)
}
return nil, fmt.Errorf(errUnknownProviderService, prov.Service)
}
sess, err := awsauth.New(ctx, store, kube, namespace, assumeRoler, awsauth.DefaultJWTProvider) sess, err := awsauth.New(ctx, store, kube, namespace, assumeRoler, awsauth.DefaultJWTProvider)
if err != nil { if err != nil {
return nil, fmt.Errorf(errUnableCreateSession, err) return nil, fmt.Errorf(errUnableCreateSession, err)
@ -146,9 +167,9 @@ func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Cl
switch prov.Service { switch prov.Service {
case esv1beta1.AWSServiceSecretsManager: case esv1beta1.AWSServiceSecretsManager:
return secretsmanager.New(sess, cfg) return secretsmanager.New(sess, cfg, false)
case esv1beta1.AWSServiceParameterStore: case esv1beta1.AWSServiceParameterStore:
return parameterstore.New(sess, cfg) return parameterstore.New(sess, cfg, false)
} }
return nil, fmt.Errorf(errUnknownProviderService, prov.Service) return nil, fmt.Errorf(errUnknownProviderService, prov.Service)
} }

View file

@ -238,8 +238,8 @@ func TestValidateStore(t *testing.T) {
}, },
}, },
{ {
name: "invalid static creds auth / SecretAccessKey missing namespace", name: "referentAuth static creds / SecretAccessKey without namespace",
wantErr: true, wantErr: false,
args: args{ args: args{
store: &esv1beta1.ClusterSecretStore{ store: &esv1beta1.ClusterSecretStore{
TypeMeta: v1.TypeMeta{ TypeMeta: v1.TypeMeta{
@ -263,8 +263,8 @@ func TestValidateStore(t *testing.T) {
}, },
}, },
{ {
name: "invalid static creds auth / AccessKeyID missing namespace", name: "referentAuth static creds / AccessKeyID without namespace",
wantErr: true, wantErr: false,
args: args{ args: args{
store: &esv1beta1.ClusterSecretStore{ store: &esv1beta1.ClusterSecretStore{
TypeMeta: v1.TypeMeta{ TypeMeta: v1.TypeMeta{
@ -288,8 +288,8 @@ func TestValidateStore(t *testing.T) {
}, },
}, },
{ {
name: "invalid jwt auth: missing sa selector namespace", name: "referentAuth jwt: sa selector without namespace",
wantErr: true, wantErr: false,
args: args{ args: args{
store: &esv1beta1.ClusterSecretStore{ store: &esv1beta1.ClusterSecretStore{
TypeMeta: v1.TypeMeta{ TypeMeta: v1.TypeMeta{

View file

@ -41,9 +41,10 @@ var _ esv1beta1.SecretsClient = &SecretsManager{}
// SecretsManager is a provider for AWS SecretsManager. // SecretsManager is a provider for AWS SecretsManager.
type SecretsManager struct { type SecretsManager struct {
sess *session.Session sess *session.Session
client SMInterface client SMInterface
cache map[string]*awssm.GetSecretValueOutput referentAuth bool
cache map[string]*awssm.GetSecretValueOutput
} }
// SMInterface is a subset of the smiface api. // SMInterface is a subset of the smiface api.
@ -67,11 +68,12 @@ const (
var log = ctrl.Log.WithName("provider").WithName("aws").WithName("secretsmanager") var log = ctrl.Log.WithName("provider").WithName("aws").WithName("secretsmanager")
// New creates a new SecretsManager client. // New creates a new SecretsManager client.
func New(sess *session.Session, cfg *aws.Config) (*SecretsManager, error) { func New(sess *session.Session, cfg *aws.Config, referentAuth bool) (*SecretsManager, error) {
return &SecretsManager{ return &SecretsManager{
sess: sess, sess: sess,
client: awssm.New(sess, cfg), client: awssm.New(sess, cfg),
cache: make(map[string]*awssm.GetSecretValueOutput), referentAuth: referentAuth,
cache: make(map[string]*awssm.GetSecretValueOutput),
}, nil }, nil
} }
@ -407,6 +409,11 @@ func (sm *SecretsManager) Close(ctx context.Context) error {
} }
func (sm *SecretsManager) Validate() (esv1beta1.ValidationResult, error) { func (sm *SecretsManager) Validate() (esv1beta1.ValidationResult, error) {
// skip validation stack because it depends on the namespace
// of the ExternalSecret
if sm.referentAuth {
return esv1beta1.ValidationResultUnknown, nil
}
_, err := sm.sess.Config.Credentials.Get() _, err := sm.sess.Config.Credentials.Get()
if err != nil { if err != nil {
return esv1beta1.ValidationResultError, err return esv1beta1.ValidationResultError, err

View file

@ -45,3 +45,17 @@ func GetAWSProvider(store esv1beta1.GenericStore) (*esv1beta1.AWSProvider, error
} }
return prov, nil return prov, nil
} }
func IsReferentSpec(prov esv1beta1.AWSAuth) bool {
if prov.JWTAuth != nil && prov.JWTAuth.ServiceAccountRef != nil && prov.JWTAuth.ServiceAccountRef.Namespace == nil {
return true
}
if prov.SecretRef != nil &&
(prov.SecretRef.AccessKeyID.Namespace == nil ||
prov.SecretRef.SecretAccessKey.Namespace == nil ||
(prov.SecretRef.SessionToken != nil && prov.SecretRef.SessionToken.Namespace == nil)) {
return true
}
return false
}

View file

@ -4,7 +4,7 @@ provider "aws" {
locals { locals {
name = var.cluster_name name = var.cluster_name
cluster_version = "1.22" cluster_version = "1.24"
region = var.cluster_region region = var.cluster_region
serviceaccount_name = var.irsa_sa_name serviceaccount_name = var.irsa_sa_name