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

Aws ssm parameterstore issue 1839 (#2350)

* update documentation

Signed-off-by: Luke Arntz <luke@blue42.net>

* default to GetParametersByPathWithContext

Add GetParametersByPathWithContext. To maintain backward compatibility moved the original `findByname` function to `fallbackFindByName` and created a new `findByName` function that uses the `GetParametersByPathWithContext` API call.

In function `findByName`, if we receive an `AccessDeniedException` when calling GetParametersByPathWithContext `return pm.fallbackFindByName(ctx, ref)`.

Signed-off-by: Luke Arntz <luke@blue42.net>

* feat: notify users about ssm permission improvements

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

* fix: get parameters recursively and decrypt them

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

---------

Signed-off-by: Luke Arntz <luke@blue42.net>
Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
Co-authored-by: Moritz Johner <beller.moritz@googlemail.com>
This commit is contained in:
Luke Arntz 2023-05-25 19:05:59 -04:00 committed by GitHub
parent 76c7f3b5b0
commit 00d66e0bc4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 79 additions and 14 deletions

View file

@ -21,7 +21,9 @@ way users of the `SecretStore` can only access the secrets necessary.
### IAM Policy
Create a IAM Policy to pin down access to secrets matching `dev-*`, for further information see [AWS Documentation](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-access.html):
The example policy below shows the minimum required permissions for fetching SSM parameters. This policy permits pinning down access to secrets with a path matching `dev-*`. Other operations may require additional permission. For example, finding parameters based on tags will also require `ssm:DescribeParameters` and `tag:GetResources` permission with `"Resource": "*"`. Generally, the specific permission required will be logged as an error if an operation fails.
For further information see [AWS Documentation](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-access.html).
``` json
{
@ -30,15 +32,14 @@ Create a IAM Policy to pin down access to secrets matching `dev-*`, for further
{
"Effect": "Allow",
"Action": [
"ssm:GetParameter",
"ssm:ListTagsForResource",
"ssm:DescribeParameters"
"ssm:GetParameter*",
],
"Resource": "arn:aws:ssm:us-east-2:1234567889911:parameter/dev-*"
}
]
}
```
### JSON Secret Values
You can store JSON objects in a parameter. You can access nested values or arrays using [gjson syntax](https://github.com/tidwall/gjson/blob/master/SYNTAX.md):

View file

@ -16,6 +16,7 @@ package constants
const (
ProviderAWSSM = "AWS/SecretsManager"
CallAWSSMGetSecretValue = "GetSecretValue"
CallAWSPSGetParametersByPath = "GetParametersByPath"
CallAWSSMDescribeSecret = "DescribeSecret"
CallAWSSMDeleteSecret = "DeleteSecret"
CallAWSSMCreateSecret = "CreateSecret"

View file

@ -26,6 +26,7 @@ import (
// Client implements the aws parameterstore interface.
type Client struct {
GetParameterWithContextFn GetParameterWithContextFn
GetParametersByPathWithContextFn GetParametersByPathWithContextFn
PutParameterWithContextFn PutParameterWithContextFn
DeleteParameterWithContextFn DeleteParameterWithContextFn
DescribeParametersWithContextFn DescribeParametersWithContextFn
@ -33,6 +34,7 @@ type Client struct {
}
type GetParameterWithContextFn func(aws.Context, *ssm.GetParameterInput, ...request.Option) (*ssm.GetParameterOutput, error)
type GetParametersByPathWithContextFn func(aws.Context, *ssm.GetParametersByPathInput, ...request.Option) (*ssm.GetParametersByPathOutput, error)
type PutParameterWithContextFn func(aws.Context, *ssm.PutParameterInput, ...request.Option) (*ssm.PutParameterOutput, error)
type DescribeParametersWithContextFn func(aws.Context, *ssm.DescribeParametersInput, ...request.Option) (*ssm.DescribeParametersOutput, error)
type ListTagsForResourceWithContextFn func(aws.Context, *ssm.ListTagsForResourceInput, ...request.Option) (*ssm.ListTagsForResourceOutput, error)
@ -62,6 +64,10 @@ func (sm *Client) GetParameterWithContext(ctx aws.Context, input *ssm.GetParamet
return sm.GetParameterWithContextFn(ctx, input, options...)
}
func (sm *Client) GetParametersByPathWithContext(ctx aws.Context, input *ssm.GetParametersByPathInput, options ...request.Option) (*ssm.GetParametersByPathOutput, error) {
return sm.GetParametersByPathWithContextFn(ctx, input, options...)
}
func NewGetParameterWithContextFn(output *ssm.GetParameterOutput, err error) GetParameterWithContextFn {
return func(aws.Context, *ssm.GetParameterInput, ...request.Option) (*ssm.GetParameterOutput, error) {
return output, err

View file

@ -27,6 +27,7 @@ import (
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/tidwall/gjson"
utilpointer "k8s.io/utils/pointer"
ctrl "sigs.k8s.io/controller-runtime"
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
"github.com/external-secrets/external-secrets/pkg/constants"
@ -40,6 +41,7 @@ var (
_ esv1beta1.SecretsClient = &ParameterStore{}
managedBy = "managed-by"
externalSecrets = "external-secrets"
logger = ctrl.Log.WithName("provider").WithName("parameterstore")
)
// ParameterStore is a provider for AWS ParameterStore.
@ -53,6 +55,7 @@ type ParameterStore struct {
// see: https://docs.aws.amazon.com/sdk-for-go/api/service/ssm/ssmiface/
type PMInterface interface {
GetParameterWithContext(aws.Context, *ssm.GetParameterInput, ...request.Option) (*ssm.GetParameterOutput, error)
GetParametersByPathWithContext(aws.Context, *ssm.GetParametersByPathInput, ...request.Option) (*ssm.GetParametersByPathOutput, error)
PutParameterWithContext(aws.Context, *ssm.PutParameterInput, ...request.Option) (*ssm.PutParameterOutput, error)
DescribeParametersWithContext(aws.Context, *ssm.DescribeParametersInput, ...request.Option) (*ssm.DescribeParametersOutput, error)
ListTagsForResourceWithContext(aws.Context, *ssm.ListTagsForResourceInput, ...request.Option) (*ssm.ListTagsForResourceOutput, error)
@ -61,6 +64,7 @@ type PMInterface interface {
const (
errUnexpectedFindOperator = "unexpected find operator"
errAccessDeniedException = "AccessDeniedException"
)
// New constructs a ParameterStore Provider that is specific to a store.
@ -219,7 +223,60 @@ func (pm *ParameterStore) GetAllSecrets(ctx context.Context, ref esv1beta1.Exter
return nil, errors.New(errUnexpectedFindOperator)
}
// findByName requires `ssm:GetParametersByPath` IAM permission, but the `Resource` scope can be limited.
func (pm *ParameterStore) findByName(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
matcher, err := find.New(*ref.Name)
if err != nil {
return nil, err
}
if ref.Path == nil {
ref.Path = aws.String("/")
}
data := make(map[string][]byte)
var nextToken *string
for {
it, err := pm.client.GetParametersByPathWithContext(
ctx,
&ssm.GetParametersByPathInput{
NextToken: nextToken,
Path: ref.Path,
Recursive: aws.Bool(true),
WithDecryption: aws.Bool(true),
})
metrics.ObserveAPICall(constants.ProviderAWSPS, constants.CallAWSPSGetParametersByPath, err)
if err != nil {
/*
Check for AccessDeniedException when calling `GetParametersByPathWithContext`. If so,
use fallbackFindByName and `DescribeParametersWithContext`.
https://github.com/external-secrets/external-secrets/issues/1839#issuecomment-1489023522
*/
var awsError awserr.Error
if errors.As(err, &awsError) && awsError.Code() == errAccessDeniedException {
logger.Info("GetParametersByPath: access denied. using fallback to describe parameters. It is recommended to add ssm:GetParametersByPath permissions", "path", ref.Path)
return pm.fallbackFindByName(ctx, ref)
}
return nil, err
}
for _, param := range it.Parameters {
if !matcher.MatchName(*param.Name) {
continue
}
err = pm.fetchAndSet(ctx, data, *param.Name)
if err != nil {
return nil, err
}
}
nextToken = it.NextToken
if nextToken == nil {
break
}
}
return data, nil
}
// fallbackFindByName requires `ssm:DescribeParameters` IAM permission on `"Resource": "*"`.
func (pm *ParameterStore) fallbackFindByName(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
matcher, err := find.New(*ref.Name)
if err != nil {
return nil, err
@ -259,10 +316,10 @@ func (pm *ParameterStore) findByName(ctx context.Context, ref esv1beta1.External
break
}
}
return data, nil
}
// findByTags requires ssm:DescribeParameters,tag:GetResources IAM permission on `"Resource": "*"`.
func (pm *ParameterStore) findByTags(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
filters := make([]*ssm.ParameterStringFilter, 0)
for k, v := range ref.Tags {