1
0
Fork 0
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:
Gergely Brautigam 2024-07-31 12:29:07 +02:00 committed by GitHub
parent d10a66ee68
commit 8c709cfa43
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 104 additions and 20 deletions

View file

@ -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"`
} }

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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...)
} }

View file

@ -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, &param) tags, err := pm.getTagsByName(ctx, &param)
@ -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

View file

@ -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)

View file

@ -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)
} }