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:
parent
f4e70ddfed
commit
5384954f46
16 changed files with 320 additions and 145 deletions
4
.github/workflows/e2e.yml
vendored
4
.github/workflows/e2e.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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 | |
|
||||
|
|
|
@ -33,9 +33,7 @@ ginkgo_args=(
|
|||
"--randomize-all"
|
||||
"--flake-attempts=2"
|
||||
"-p"
|
||||
"-progress"
|
||||
"-trace"
|
||||
"--slow-spec-threshold=5m"
|
||||
"-r"
|
||||
"-v"
|
||||
"-timeout=45m"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue