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'
GINKGO_VERSION: 'v2.1.6'
DOCKER_BUILDX_VERSION: 'v0.4.2'
KIND_VERSION: 'v0.14.0'
KIND_IMAGE: 'kindest/node:v1.24.2'
KIND_VERSION: 'v0.17.0'
KIND_IMAGE: 'kindest/node:v1.26.0'
# 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

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 |
|---------------------------|:------------:|:------------:| :------------------: | :---------------------: | :--------------: | :---------: |
| AWS Secrets Manager | x | x | | | x | |
| AWS Parameter Store | x | x | | | x | |
| AWS Secrets Manager | x | x | | x | x | |
| AWS Parameter Store | x | x | | x | x | |
| Hashicorp Vault | x | x | | | x | |
| GCP Secret Manager | x | x | | x | x | |
| Azure Keyvault | x | x | x | x | x | |

View file

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

View file

@ -27,9 +27,10 @@ import (
)
const (
WithReferencedIRSA = "with referenced IRSA"
WithMountedIRSA = "with mounted IRSA"
StaticCredentialsSecretName = "provider-secret"
WithReferencedIRSA = "with referenced IRSA"
WithMountedIRSA = "with mounted IRSA"
StaticCredentialsSecretName = "provider-secret"
StaticReferentCredentialsSecretName = "referent-provider-secret"
)
func ReferencedIRSAStoreName(f *framework.Framework) string {
@ -50,6 +51,38 @@ func UseMountedIRSAStore(tc *framework.TestCase) {
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
// static credentials from a secret.
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,
},
StringData: map[string]string{
"kid": kid,
"sak": sak,
"st": st,
staticKeyID: kid,
staticSecretAccessKey: sak,
staticySessionToken: st,
},
}
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{
ObjectMeta: metav1.ObjectMeta{
Name: f.Namespace.Name,
Name: StaticStoreName,
Namespace: f.Namespace.Name,
},
Spec: esv1beta1.SecretStoreSpec{
Provider: &esv1beta1.SecretStoreProvider{
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",
},
},
},
},
},
Provider: newStaticStoreProvider(serviceType, region, StaticCredentialsSecretName),
},
}
err = f.CRClient.Create(context.Background(), secretStore)
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/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"
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() {
@ -30,25 +37,41 @@ var _ = Describe("[aws] ", Label("aws", "parameterstore"), func() {
DescribeTable("sync secrets",
framework.TableFunc(f,
prov),
Entry(common.SimpleDataSync(f)),
Entry(common.NestedJSONWithGJSON(f)),
Entry(common.JSONDataFromSync(f)),
Entry(common.JSONDataFromRewrite(f)),
Entry(common.JSONDataWithProperty(f)),
Entry(common.JSONDataWithTemplate(f)),
Entry(common.DockerJSONConfig(f)),
Entry(common.DataPropertyDockerconfigJSON(f)),
Entry(common.SSHKeySync(f)),
Entry(common.SSHKeySyncDataProperty(f)),
Entry(common.SyncWithoutTargetName(f)),
Entry(common.JSONDataWithoutTargetName(f)),
Entry(common.SyncV1Alpha1(f)),
Entry(common.DeletionPolicyDelete(f)),
framework.Compose(withStaticAuth, f, common.SimpleDataSync, useStaticAuth),
framework.Compose(withStaticAuth, f, common.NestedJSONWithGJSON, useStaticAuth),
framework.Compose(withStaticAuth, f, common.JSONDataFromSync, useStaticAuth),
framework.Compose(withStaticAuth, f, common.JSONDataFromRewrite, useStaticAuth),
framework.Compose(withStaticAuth, f, common.JSONDataWithProperty, useStaticAuth),
framework.Compose(withStaticAuth, f, common.JSONDataWithTemplate, useStaticAuth),
framework.Compose(withStaticAuth, f, common.DockerJSONConfig, useStaticAuth),
framework.Compose(withStaticAuth, f, common.DataPropertyDockerconfigJSON, useStaticAuth),
framework.Compose(withStaticAuth, f, common.SSHKeySync, useStaticAuth),
framework.Compose(withStaticAuth, f, common.SSHKeySyncDataProperty, useStaticAuth),
framework.Compose(withStaticAuth, f, common.SyncWithoutTargetName, useStaticAuth),
framework.Compose(withStaticAuth, f, common.JSONDataWithoutTargetName, useStaticAuth),
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
Entry(FindByName(f)),
Entry(FindByNameWithPath(f)),
Entry(FindByTag(f)),
Entry(FindByTagWithPath(f)),
framework.Compose(withStaticAuth, f, FindByName, useStaticAuth),
framework.Compose(withStaticAuth, f, FindByNameWithPath, useStaticAuth),
framework.Compose(withStaticAuth, f, FindByTag, useStaticAuth),
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/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"
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() {
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.SetupMountedIRSAStore()
})
AfterEach(func() {
// Cleanup ClusterSecretStore
err := prov.framework.CRClient.Delete(context.Background(), &esv1beta1.ClusterSecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: common.ReferencedIRSAStoreName(f),
},
})
Expect(err).ToNot(HaveOccurred())
prov.TeardownReferencedIRSAStore()
prov.TeardownMountedIRSAStore()
})
return prov
@ -132,7 +128,7 @@ func (s *Provider) DeleteSecret(key string) {
func (s *Provider) SetupMountedIRSAStore() {
secretStore := &esv1beta1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: common.MountedIRSAStoreName(s.framework),
Name: awscommon.MountedIRSAStoreName(s.framework),
Namespace: s.framework.Namespace.Name,
},
Spec: esv1beta1.SecretStoreSpec{
@ -149,13 +145,21 @@ func (s *Provider) SetupMountedIRSAStore() {
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
// that references a (IRSA-) ServiceAccount in the default namespace.
func (s *Provider) SetupReferencedIRSAStore() {
log.Logf("creating IRSA ClusterSecretStore %s", s.framework.Namespace.Name)
secretStore := &esv1beta1.ClusterSecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: common.ReferencedIRSAStoreName(s.framework),
Name: awscommon.ReferencedIRSAStoreName(s.framework),
},
}
_, err := controllerutil.CreateOrUpdate(context.Background(), s.framework.CRClient, secretStore, func() error {
@ -177,3 +181,11 @@ func (s *Provider) SetupReferencedIRSAStore() {
})
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/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"
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() {
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.SetupMountedIRSAStore()
})
AfterEach(func() {
// Cleanup ClusterSecretStore
err := prov.framework.CRClient.Delete(context.Background(), &esv1beta1.ClusterSecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: common.ReferencedIRSAStoreName(f),
},
})
Expect(err).ToNot(HaveOccurred())
prov.TeardownReferencedIRSAStore()
prov.TeardownMountedIRSAStore()
})
return prov
@ -150,7 +147,7 @@ func (s *Provider) DeleteSecret(key string) {
func (s *Provider) SetupMountedIRSAStore() {
secretStore := &esv1beta1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: common.MountedIRSAStoreName(s.framework),
Name: awscommon.MountedIRSAStoreName(s.framework),
Namespace: s.framework.Namespace.Name,
},
Spec: esv1beta1.SecretStoreSpec{
@ -167,13 +164,21 @@ func (s *Provider) SetupMountedIRSAStore() {
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
// that references a (IRSA-) ServiceAccount in the default namespace.
func (s *Provider) SetupReferencedIRSAStore() {
log.Logf("creating IRSA ClusterSecretStore %s", s.framework.Namespace.Name)
secretStore := &esv1beta1.ClusterSecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: common.ReferencedIRSAStoreName(s.framework),
Name: awscommon.ReferencedIRSAStoreName(s.framework),
},
}
_, err := controllerutil.CreateOrUpdate(context.Background(), s.framework.CRClient, secretStore, func() error {
@ -195,3 +200,11 @@ func (s *Provider) SetupReferencedIRSAStore() {
})
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/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"
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() {
@ -30,23 +37,38 @@ var _ = Describe("[aws] ", Label("aws", "secretsmanager"), func() {
DescribeTable("sync secrets",
framework.TableFunc(f,
prov),
Entry(common.SimpleDataSync(f)),
Entry(common.NestedJSONWithGJSON(f)),
Entry(common.JSONDataFromSync(f)),
Entry(common.JSONDataFromRewrite(f)),
Entry(common.JSONDataWithProperty(f)),
Entry(common.JSONDataWithTemplate(f)),
Entry(common.DockerJSONConfig(f)),
Entry(common.DataPropertyDockerconfigJSON(f)),
Entry(common.SSHKeySync(f)),
Entry(common.SSHKeySyncDataProperty(f)),
Entry(common.SyncWithoutTargetName(f)),
Entry(common.JSONDataWithoutTargetName(f)),
Entry(common.FindByName(f)),
Entry(common.FindByNameWithPath(f)),
Entry(common.FindByTag(f)),
Entry(common.FindByTagWithPath(f)),
Entry(common.SyncV1Alpha1(f)),
Entry(common.DeletionPolicyDelete(f)),
framework.Compose(withStaticAuth, f, common.SimpleDataSync, useStaticAuth),
framework.Compose(withStaticAuth, f, common.NestedJSONWithGJSON, useStaticAuth),
framework.Compose(withStaticAuth, f, common.JSONDataFromSync, useStaticAuth),
framework.Compose(withStaticAuth, f, common.JSONDataFromRewrite, useStaticAuth),
framework.Compose(withStaticAuth, f, common.JSONDataWithProperty, useStaticAuth),
framework.Compose(withStaticAuth, f, common.JSONDataWithTemplate, useStaticAuth),
framework.Compose(withStaticAuth, f, common.DockerJSONConfig, useStaticAuth),
framework.Compose(withStaticAuth, f, common.DataPropertyDockerconfigJSON, useStaticAuth),
framework.Compose(withStaticAuth, f, common.SSHKeySync, useStaticAuth),
framework.Compose(withStaticAuth, f, common.SSHKeySyncDataProperty, useStaticAuth),
framework.Compose(withStaticAuth, f, common.SyncWithoutTargetName, useStaticAuth),
framework.Compose(withStaticAuth, f, common.JSONDataWithoutTargetName, useStaticAuth),
framework.Compose(withStaticAuth, f, common.FindByName, useStaticAuth),
framework.Compose(withStaticAuth, f, common.FindByNameWithPath, useStaticAuth),
framework.Compose(withStaticAuth, f, common.FindByTag, useStaticAuth),
framework.Compose(withStaticAuth, f, common.FindByTagWithPath, useStaticAuth),
framework.Compose(withStaticAuth, f, common.SyncV1Alpha1, useStaticAuth),
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
jwtAuth := prov.Auth.JWTAuth
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 {
return nil, err
}
@ -97,7 +97,7 @@ func New(ctx context.Context, store esv1beta1.GenericStore, kube client.Client,
secretRef := prov.Auth.SecretRef
if secretRef != nil {
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 {
return nil, err
}
@ -141,7 +141,7 @@ func NewGeneratorSession(ctx context.Context, auth esv1beta1.AWSAuth, role, regi
// use credentials via service account token
jwtAuth := auth.JWTAuth
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 {
return nil, err
}
@ -151,7 +151,7 @@ func NewGeneratorSession(ctx context.Context, auth esv1beta1.AWSAuth, role, regi
secretRef := auth.SecretRef
if secretRef != nil {
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 {
return nil, err
}
@ -178,16 +178,16 @@ func NewGeneratorSession(ctx context.Context, auth esv1beta1.AWSAuth, role, regi
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{
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 {
if auth.SecretRef.AccessKeyID.Namespace == nil {
return nil, fmt.Errorf(errInvalidClusterStoreMissingAKIDNamespace)
}
if isClusterKind && auth.SecretRef.AccessKeyID.Namespace != nil {
ke.Namespace = *auth.SecretRef.AccessKeyID.Namespace
}
akSecret := v1.Secret{}
@ -197,13 +197,9 @@ func sessionFromSecretRef(ctx context.Context, auth esv1beta1.AWSAuth, isCluster
}
ke = client.ObjectKey{
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 {
if auth.SecretRef.SecretAccessKey.Namespace == nil {
return nil, fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
}
if isClusterKind && auth.SecretRef.SecretAccessKey.Namespace != nil {
ke.Namespace = *auth.SecretRef.SecretAccessKey.Namespace
}
sakSecret := v1.Secret{}
@ -224,13 +220,9 @@ func sessionFromSecretRef(ctx context.Context, auth esv1beta1.AWSAuth, isCluster
if auth.SecretRef.SessionToken != nil {
ke = client.ObjectKey{
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 {
if auth.SecretRef.SessionToken.Namespace == nil {
return nil, fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
}
if isClusterKind && auth.SecretRef.SessionToken.Namespace != nil {
ke.Namespace = *auth.SecretRef.SessionToken.Namespace
}
stSecret := v1.Secret{}
@ -244,9 +236,14 @@ func sessionFromSecretRef(ctx context.Context, auth esv1beta1.AWSAuth, isCluster
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
if isClusterKind {
if isClusterKind && auth.JWTAuth.ServiceAccountRef.Namespace != nil {
namespace = *auth.JWTAuth.ServiceAccountRef.Namespace
}
sa := v1.ServiceAccount{}
@ -327,7 +324,7 @@ func DefaultSTSProvider(sess *session.Session) stsiface.STSAPI {
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.
func getAWSSession(config *aws.Config, enableCache bool, name, kind, namespace, resourceVersion string) (*session.Session, error) {
tmpSession := SessionCache{

View file

@ -311,7 +311,7 @@ func TestNewSession(t *testing.T) {
expectedSecretKey: "2222",
},
{
name: "namespace is mandatory when using ClusterStore with SecretKeySelector",
name: "ClusterStore should use credentials from a ExternalSecret namespace (referentAuth)",
namespace: esNamespaceKey,
store: &esv1beta1.ClusterSecretStore{
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",

View file

@ -42,8 +42,9 @@ var (
// ParameterStore is a provider for AWS ParameterStore.
type ParameterStore struct {
sess *session.Session
client PMInterface
sess *session.Session
client PMInterface
referentAuth bool
}
// PMInterface is a subset of the parameterstore api.
@ -61,10 +62,11 @@ const (
)
// 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{
sess: sess,
client: ssm.New(sess, cfg),
sess: sess,
referentAuth: referentAuth,
client: ssm.New(sess, cfg),
}, nil
}
@ -377,6 +379,11 @@ func (pm *ParameterStore) Close(ctx context.Context) 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()
if err != nil {
return esv1beta1.ValidationResultError, err

View file

@ -23,6 +23,7 @@ import (
awsclient "github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"sigs.k8s.io/controller-runtime/pkg/client"
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
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)
}
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)
}
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
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)
}
}
@ -114,6 +120,21 @@ func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Cl
storeSpec := store.GetSpec()
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)
if err != nil {
return nil, fmt.Errorf(errUnableCreateSession, err)
@ -146,9 +167,9 @@ func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Cl
switch prov.Service {
case esv1beta1.AWSServiceSecretsManager:
return secretsmanager.New(sess, cfg)
return secretsmanager.New(sess, cfg, false)
case esv1beta1.AWSServiceParameterStore:
return parameterstore.New(sess, cfg)
return parameterstore.New(sess, cfg, false)
}
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",
wantErr: true,
name: "referentAuth static creds / SecretAccessKey without namespace",
wantErr: false,
args: args{
store: &esv1beta1.ClusterSecretStore{
TypeMeta: v1.TypeMeta{
@ -263,8 +263,8 @@ func TestValidateStore(t *testing.T) {
},
},
{
name: "invalid static creds auth / AccessKeyID missing namespace",
wantErr: true,
name: "referentAuth static creds / AccessKeyID without namespace",
wantErr: false,
args: args{
store: &esv1beta1.ClusterSecretStore{
TypeMeta: v1.TypeMeta{
@ -288,8 +288,8 @@ func TestValidateStore(t *testing.T) {
},
},
{
name: "invalid jwt auth: missing sa selector namespace",
wantErr: true,
name: "referentAuth jwt: sa selector without namespace",
wantErr: false,
args: args{
store: &esv1beta1.ClusterSecretStore{
TypeMeta: v1.TypeMeta{

View file

@ -41,9 +41,10 @@ var _ esv1beta1.SecretsClient = &SecretsManager{}
// SecretsManager is a provider for AWS SecretsManager.
type SecretsManager struct {
sess *session.Session
client SMInterface
cache map[string]*awssm.GetSecretValueOutput
sess *session.Session
client SMInterface
referentAuth bool
cache map[string]*awssm.GetSecretValueOutput
}
// SMInterface is a subset of the smiface api.
@ -67,11 +68,12 @@ const (
var log = ctrl.Log.WithName("provider").WithName("aws").WithName("secretsmanager")
// 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{
sess: sess,
client: awssm.New(sess, cfg),
cache: make(map[string]*awssm.GetSecretValueOutput),
sess: sess,
client: awssm.New(sess, cfg),
referentAuth: referentAuth,
cache: make(map[string]*awssm.GetSecretValueOutput),
}, nil
}
@ -407,6 +409,11 @@ func (sm *SecretsManager) Close(ctx context.Context) 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()
if err != nil {
return esv1beta1.ValidationResultError, err

View file

@ -45,3 +45,17 @@ func GetAWSProvider(store esv1beta1.GenericStore) (*esv1beta1.AWSProvider, error
}
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 {
name = var.cluster_name
cluster_version = "1.22"
cluster_version = "1.24"
region = var.cluster_region
serviceaccount_name = var.irsa_sa_name