mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
feat: add prefix definition to all secret keys for aws parameter store (#3718)
* feat: add prefix definition to all secret keys for aws parameter store Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * added a push secret test to verify called parameter has a prefix Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> --------- Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
This commit is contained in:
parent
d10a66ee68
commit
8c709cfa43
9 changed files with 104 additions and 20 deletions
|
@ -124,4 +124,8 @@ type AWSProvider struct {
|
||||||
// AWS STS assume role transitive session tags. Required when multiple rules are used with the provider
|
// AWS STS assume role transitive session tags. Required when multiple rules are used with the provider
|
||||||
// +optional
|
// +optional
|
||||||
TransitiveTagKeys []*string `json:"transitiveTagKeys,omitempty"`
|
TransitiveTagKeys []*string `json:"transitiveTagKeys,omitempty"`
|
||||||
|
|
||||||
|
// Prefix adds a prefix to all retrieved values.
|
||||||
|
// +optional
|
||||||
|
Prefix string `json:"prefix,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -2084,6 +2084,9 @@ spec:
|
||||||
externalID:
|
externalID:
|
||||||
description: AWS External ID set on assumed IAM roles
|
description: AWS External ID set on assumed IAM roles
|
||||||
type: string
|
type: string
|
||||||
|
prefix:
|
||||||
|
description: Prefix adds a prefix to all retrieved values.
|
||||||
|
type: string
|
||||||
region:
|
region:
|
||||||
description: AWS Region to be used for the provider
|
description: AWS Region to be used for the provider
|
||||||
type: string
|
type: string
|
||||||
|
|
|
@ -2084,6 +2084,9 @@ spec:
|
||||||
externalID:
|
externalID:
|
||||||
description: AWS External ID set on assumed IAM roles
|
description: AWS External ID set on assumed IAM roles
|
||||||
type: string
|
type: string
|
||||||
|
prefix:
|
||||||
|
description: Prefix adds a prefix to all retrieved values.
|
||||||
|
type: string
|
||||||
region:
|
region:
|
||||||
description: AWS Region to be used for the provider
|
description: AWS Region to be used for the provider
|
||||||
type: string
|
type: string
|
||||||
|
|
|
@ -2613,6 +2613,9 @@ spec:
|
||||||
externalID:
|
externalID:
|
||||||
description: AWS External ID set on assumed IAM roles
|
description: AWS External ID set on assumed IAM roles
|
||||||
type: string
|
type: string
|
||||||
|
prefix:
|
||||||
|
description: Prefix adds a prefix to all retrieved values.
|
||||||
|
type: string
|
||||||
region:
|
region:
|
||||||
description: AWS Region to be used for the provider
|
description: AWS Region to be used for the provider
|
||||||
type: string
|
type: string
|
||||||
|
@ -8244,6 +8247,9 @@ spec:
|
||||||
externalID:
|
externalID:
|
||||||
description: AWS External ID set on assumed IAM roles
|
description: AWS External ID set on assumed IAM roles
|
||||||
type: string
|
type: string
|
||||||
|
prefix:
|
||||||
|
description: Prefix adds a prefix to all retrieved values.
|
||||||
|
type: string
|
||||||
region:
|
region:
|
||||||
description: AWS Region to be used for the provider
|
description: AWS Region to be used for the provider
|
||||||
type: string
|
type: string
|
||||||
|
|
|
@ -281,6 +281,18 @@ SecretsManager
|
||||||
<p>AWS STS assume role transitive session tags. Required when multiple rules are used with the provider</p>
|
<p>AWS STS assume role transitive session tags. Required when multiple rules are used with the provider</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>prefix</code></br>
|
||||||
|
<em>
|
||||||
|
string
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>Prefix adds a prefix to all retrieved values.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<h3 id="external-secrets.io/v1beta1.AWSServiceType">AWSServiceType
|
<h3 id="external-secrets.io/v1beta1.AWSServiceType">AWSServiceType
|
||||||
|
|
|
@ -26,13 +26,14 @@ import (
|
||||||
|
|
||||||
// Client implements the aws parameterstore interface.
|
// Client implements the aws parameterstore interface.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
GetParameterWithContextFn GetParameterWithContextFn
|
GetParameterWithContextFn GetParameterWithContextFn
|
||||||
GetParametersByPathWithContextFn GetParametersByPathWithContextFn
|
GetParametersByPathWithContextFn GetParametersByPathWithContextFn
|
||||||
PutParameterWithContextFn PutParameterWithContextFn
|
PutParameterWithContextFn PutParameterWithContextFn
|
||||||
PutParameterWithContextCalledN int
|
PutParameterWithContextCalledN int
|
||||||
DeleteParameterWithContextFn DeleteParameterWithContextFn
|
PutParameterWithContextFnCalledWith [][]*ssm.PutParameterInput
|
||||||
DescribeParametersWithContextFn DescribeParametersWithContextFn
|
DeleteParameterWithContextFn DeleteParameterWithContextFn
|
||||||
ListTagsForResourceWithContextFn ListTagsForResourceWithContextFn
|
DescribeParametersWithContextFn DescribeParametersWithContextFn
|
||||||
|
ListTagsForResourceWithContextFn ListTagsForResourceWithContextFn
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetParameterWithContextFn func(aws.Context, *ssm.GetParameterInput, ...request.Option) (*ssm.GetParameterOutput, error)
|
type GetParameterWithContextFn func(aws.Context, *ssm.GetParameterInput, ...request.Option) (*ssm.GetParameterOutput, error)
|
||||||
|
@ -88,6 +89,7 @@ func NewDescribeParametersWithContextFn(output *ssm.DescribeParametersOutput, er
|
||||||
|
|
||||||
func (sm *Client) PutParameterWithContext(ctx aws.Context, input *ssm.PutParameterInput, options ...request.Option) (*ssm.PutParameterOutput, error) {
|
func (sm *Client) PutParameterWithContext(ctx aws.Context, input *ssm.PutParameterInput, options ...request.Option) (*ssm.PutParameterOutput, error) {
|
||||||
sm.PutParameterWithContextCalledN++
|
sm.PutParameterWithContextCalledN++
|
||||||
|
sm.PutParameterWithContextFnCalledWith = append(sm.PutParameterWithContextFnCalledWith, []*ssm.PutParameterInput{input})
|
||||||
return sm.PutParameterWithContextFn(ctx, input, options...)
|
return sm.PutParameterWithContextFn(ctx, input, options...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,7 @@ type ParameterStore struct {
|
||||||
sess *session.Session
|
sess *session.Session
|
||||||
client PMInterface
|
client PMInterface
|
||||||
referentAuth bool
|
referentAuth bool
|
||||||
|
prefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
// PMInterface is a subset of the parameterstore api.
|
// PMInterface is a subset of the parameterstore api.
|
||||||
|
@ -79,11 +80,12 @@ 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, referentAuth bool) (*ParameterStore, error) {
|
func New(sess *session.Session, cfg *aws.Config, prefix string, referentAuth bool) (*ParameterStore, error) {
|
||||||
return &ParameterStore{
|
return &ParameterStore{
|
||||||
sess: sess,
|
sess: sess,
|
||||||
referentAuth: referentAuth,
|
referentAuth: referentAuth,
|
||||||
client: ssm.New(sess, cfg),
|
client: ssm.New(sess, cfg),
|
||||||
|
prefix: prefix,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +107,7 @@ func (pm *ParameterStore) getTagsByName(ctx aws.Context, ref *ssm.GetParameterOu
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *ParameterStore) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushSecretRemoteRef) error {
|
func (pm *ParameterStore) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushSecretRemoteRef) error {
|
||||||
secretName := remoteRef.GetRemoteKey()
|
secretName := pm.prefix + remoteRef.GetRemoteKey()
|
||||||
secretValue := ssm.GetParameterInput{
|
secretValue := ssm.GetParameterInput{
|
||||||
Name: &secretName,
|
Name: &secretName,
|
||||||
}
|
}
|
||||||
|
@ -179,7 +181,7 @@ func (pm *ParameterStore) PushSecret(ctx context.Context, secret *corev1.Secret,
|
||||||
}
|
}
|
||||||
|
|
||||||
stringValue := string(value)
|
stringValue := string(value)
|
||||||
secretName := data.GetRemoteKey()
|
secretName := pm.prefix + data.GetRemoteKey()
|
||||||
|
|
||||||
secretRequest := ssm.PutParameterInput{
|
secretRequest := ssm.PutParameterInput{
|
||||||
Name: &secretName,
|
Name: &secretName,
|
||||||
|
@ -466,7 +468,7 @@ func (pm *ParameterStore) GetSecret(ctx context.Context, ref esv1beta1.ExternalS
|
||||||
func (pm *ParameterStore) getParameterTags(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (*ssm.GetParameterOutput, error) {
|
func (pm *ParameterStore) getParameterTags(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (*ssm.GetParameterOutput, error) {
|
||||||
param := ssm.GetParameterOutput{
|
param := ssm.GetParameterOutput{
|
||||||
Parameter: &ssm.Parameter{
|
Parameter: &ssm.Parameter{
|
||||||
Name: parameterNameWithVersion(ref),
|
Name: pm.parameterNameWithVersion(ref),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
tags, err := pm.getTagsByName(ctx, ¶m)
|
tags, err := pm.getTagsByName(ctx, ¶m)
|
||||||
|
@ -487,7 +489,7 @@ func (pm *ParameterStore) getParameterTags(ctx context.Context, ref esv1beta1.Ex
|
||||||
|
|
||||||
func (pm *ParameterStore) getParameterValue(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (*ssm.GetParameterOutput, error) {
|
func (pm *ParameterStore) getParameterValue(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (*ssm.GetParameterOutput, error) {
|
||||||
out, err := pm.client.GetParameterWithContext(ctx, &ssm.GetParameterInput{
|
out, err := pm.client.GetParameterWithContext(ctx, &ssm.GetParameterInput{
|
||||||
Name: parameterNameWithVersion(ref),
|
Name: pm.parameterNameWithVersion(ref),
|
||||||
WithDecryption: aws.Bool(true),
|
WithDecryption: aws.Bool(true),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -518,8 +520,8 @@ func (pm *ParameterStore) GetSecretMap(ctx context.Context, ref esv1beta1.Extern
|
||||||
return secretData, nil
|
return secretData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parameterNameWithVersion(ref esv1beta1.ExternalSecretDataRemoteRef) *string {
|
func (pm *ParameterStore) parameterNameWithVersion(ref esv1beta1.ExternalSecretDataRemoteRef) *string {
|
||||||
name := ref.Key
|
name := pm.prefix + ref.Key
|
||||||
if ref.Version != "" {
|
if ref.Version != "" {
|
||||||
// see docs: https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-versions.html#reference-parameter-version
|
// see docs: https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-versions.html#reference-parameter-version
|
||||||
name += ":" + ref.Version
|
name += ":" + ref.Version
|
||||||
|
|
|
@ -42,6 +42,11 @@ const (
|
||||||
invalidProp = "INVALPROP"
|
invalidProp = "INVALPROP"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fakeSecretKey = "fakeSecretKey"
|
||||||
|
fakeValue = "fakeValue"
|
||||||
|
)
|
||||||
|
|
||||||
type parameterstoreTestCase struct {
|
type parameterstoreTestCase struct {
|
||||||
fakeClient *fakeps.Client
|
fakeClient *fakeps.Client
|
||||||
apiInput *ssm.GetParameterInput
|
apiInput *ssm.GetParameterInput
|
||||||
|
@ -51,6 +56,7 @@ type parameterstoreTestCase struct {
|
||||||
expectError string
|
expectError string
|
||||||
expectedSecret string
|
expectedSecret string
|
||||||
expectedData map[string][]byte
|
expectedData map[string][]byte
|
||||||
|
prefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeValidParameterStoreTestCase() *parameterstoreTestCase {
|
func makeValidParameterStoreTestCase() *parameterstoreTestCase {
|
||||||
|
@ -60,6 +66,7 @@ func makeValidParameterStoreTestCase() *parameterstoreTestCase {
|
||||||
apiOutput: makeValidAPIOutput(),
|
apiOutput: makeValidAPIOutput(),
|
||||||
remoteRef: makeValidRemoteRef(),
|
remoteRef: makeValidRemoteRef(),
|
||||||
apiErr: nil,
|
apiErr: nil,
|
||||||
|
prefix: "",
|
||||||
expectError: "",
|
expectError: "",
|
||||||
expectedSecret: "",
|
expectedSecret: "",
|
||||||
expectedData: make(map[string][]byte),
|
expectedData: make(map[string][]byte),
|
||||||
|
@ -270,8 +277,6 @@ const remoteKey = "fake-key"
|
||||||
func TestPushSecret(t *testing.T) {
|
func TestPushSecret(t *testing.T) {
|
||||||
invalidParameters := errors.New(ssm.ErrCodeInvalidParameters)
|
invalidParameters := errors.New(ssm.ErrCodeInvalidParameters)
|
||||||
alreadyExistsError := errors.New(ssm.ErrCodeAlreadyExistsException)
|
alreadyExistsError := errors.New(ssm.ErrCodeAlreadyExistsException)
|
||||||
fakeSecretKey := "fakeSecretKey"
|
|
||||||
fakeValue := "fakeValue"
|
|
||||||
fakeSecret := &corev1.Secret{
|
fakeSecret := &corev1.Secret{
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
fakeSecretKey: []byte(fakeValue),
|
fakeSecretKey: []byte(fakeValue),
|
||||||
|
@ -518,9 +523,43 @@ func TestPushSecret(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPushSecretWithPrefix(t *testing.T) {
|
||||||
|
fakeSecret := &corev1.Secret{
|
||||||
|
Data: map[string][]byte{
|
||||||
|
fakeSecretKey: []byte(fakeValue),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
managedByESO := ssm.Tag{
|
||||||
|
Key: &managedBy,
|
||||||
|
Value: &externalSecrets,
|
||||||
|
}
|
||||||
|
putParameterOutput := &ssm.PutParameterOutput{}
|
||||||
|
getParameterOutput := &ssm.GetParameterOutput{}
|
||||||
|
describeParameterOutput := &ssm.DescribeParametersOutput{}
|
||||||
|
validListTagsForResourceOutput := &ssm.ListTagsForResourceOutput{
|
||||||
|
TagList: []*ssm.Tag{&managedByESO},
|
||||||
|
}
|
||||||
|
|
||||||
|
client := fakeps.Client{
|
||||||
|
PutParameterWithContextFn: fakeps.NewPutParameterWithContextFn(putParameterOutput, nil),
|
||||||
|
GetParameterWithContextFn: fakeps.NewGetParameterWithContextFn(getParameterOutput, nil),
|
||||||
|
DescribeParametersWithContextFn: fakeps.NewDescribeParametersWithContextFn(describeParameterOutput, nil),
|
||||||
|
ListTagsForResourceWithContextFn: fakeps.NewListTagsForResourceWithContextFn(validListTagsForResourceOutput, nil),
|
||||||
|
}
|
||||||
|
|
||||||
|
psd := fake.PushSecretData{SecretKey: fakeSecretKey, RemoteKey: remoteKey}
|
||||||
|
ps := ParameterStore{
|
||||||
|
client: &client,
|
||||||
|
prefix: "/test/this/thing/",
|
||||||
|
}
|
||||||
|
err := ps.PushSecret(context.TODO(), fakeSecret, psd)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
input := client.PutParameterWithContextFnCalledWith[0][0]
|
||||||
|
assert.Equal(t, "/test/this/thing/fake-key", *input.Name)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPushSecretCalledOnlyOnce(t *testing.T) {
|
func TestPushSecretCalledOnlyOnce(t *testing.T) {
|
||||||
fakeSecretKey := "fakeSecretKey"
|
|
||||||
fakeValue := "fakeValue"
|
|
||||||
fakeSecret := &corev1.Secret{
|
fakeSecret := &corev1.Secret{
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
fakeSecretKey: []byte(fakeValue),
|
fakeSecretKey: []byte(fakeValue),
|
||||||
|
@ -569,6 +608,17 @@ func TestGetSecret(t *testing.T) {
|
||||||
pstc.expectedSecret = "RRRRR"
|
pstc.expectedSecret = "RRRRR"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// good case: key is passed in and prefix is set, output is sent back
|
||||||
|
setSecretStringWithPrefix := func(pstc *parameterstoreTestCase) {
|
||||||
|
pstc.apiInput = &ssm.GetParameterInput{
|
||||||
|
Name: aws.String("/test/this/baz"),
|
||||||
|
WithDecryption: aws.Bool(true),
|
||||||
|
}
|
||||||
|
pstc.prefix = "/test/this"
|
||||||
|
pstc.apiOutput.Parameter.Value = aws.String("RRRRR")
|
||||||
|
pstc.expectedSecret = "RRRRR"
|
||||||
|
}
|
||||||
|
|
||||||
// good case: extract property
|
// good case: extract property
|
||||||
setExtractProperty := func(pstc *parameterstoreTestCase) {
|
setExtractProperty := func(pstc *parameterstoreTestCase) {
|
||||||
pstc.apiOutput.Parameter.Value = aws.String(`{"/shmoo": "bang"}`)
|
pstc.apiOutput.Parameter.Value = aws.String(`{"/shmoo": "bang"}`)
|
||||||
|
@ -649,6 +699,7 @@ func TestGetSecret(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
successCases := []*parameterstoreTestCase{
|
successCases := []*parameterstoreTestCase{
|
||||||
|
makeValidParameterStoreTestCaseCustom(setSecretStringWithPrefix),
|
||||||
makeValidParameterStoreTestCaseCustom(setSecretString),
|
makeValidParameterStoreTestCaseCustom(setSecretString),
|
||||||
makeValidParameterStoreTestCaseCustom(setExtractProperty),
|
makeValidParameterStoreTestCaseCustom(setExtractProperty),
|
||||||
makeValidParameterStoreTestCaseCustom(setMissingProperty),
|
makeValidParameterStoreTestCaseCustom(setMissingProperty),
|
||||||
|
@ -665,6 +716,7 @@ func TestGetSecret(t *testing.T) {
|
||||||
ps := ParameterStore{}
|
ps := ParameterStore{}
|
||||||
for k, v := range successCases {
|
for k, v := range successCases {
|
||||||
ps.client = v.fakeClient
|
ps.client = v.fakeClient
|
||||||
|
ps.prefix = v.prefix
|
||||||
out, err := ps.GetSecret(context.Background(), *v.remoteRef)
|
out, err := ps.GetSecret(context.Background(), *v.remoteRef)
|
||||||
if !ErrorContains(err, v.expectError) {
|
if !ErrorContains(err, v.expectError) {
|
||||||
t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
|
t.Errorf("[%d] unexpected error: %s, expected: '%s'", k, err.Error(), v.expectError)
|
||||||
|
|
|
@ -156,7 +156,7 @@ func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Cl
|
||||||
case esv1beta1.AWSServiceSecretsManager:
|
case esv1beta1.AWSServiceSecretsManager:
|
||||||
return secretsmanager.New(sess, cfg, prov.SecretsManager, true)
|
return secretsmanager.New(sess, cfg, prov.SecretsManager, true)
|
||||||
case esv1beta1.AWSServiceParameterStore:
|
case esv1beta1.AWSServiceParameterStore:
|
||||||
return parameterstore.New(sess, cfg, true)
|
return parameterstore.New(sess, cfg, storeSpec.Provider.AWS.Prefix, true)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf(errUnknownProviderService, prov.Service)
|
return nil, fmt.Errorf(errUnknownProviderService, prov.Service)
|
||||||
}
|
}
|
||||||
|
@ -195,7 +195,7 @@ func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Cl
|
||||||
case esv1beta1.AWSServiceSecretsManager:
|
case esv1beta1.AWSServiceSecretsManager:
|
||||||
return secretsmanager.New(sess, cfg, prov.SecretsManager, false)
|
return secretsmanager.New(sess, cfg, prov.SecretsManager, false)
|
||||||
case esv1beta1.AWSServiceParameterStore:
|
case esv1beta1.AWSServiceParameterStore:
|
||||||
return parameterstore.New(sess, cfg, false)
|
return parameterstore.New(sess, cfg, storeSpec.Provider.AWS.Prefix, false)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf(errUnknownProviderService, prov.Service)
|
return nil, fmt.Errorf(errUnknownProviderService, prov.Service)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue