From 0613cec1498773a976759a5b285a3befcc376ec1 Mon Sep 17 00:00:00 2001 From: hainenber Date: Tue, 30 Apr 2024 19:11:39 +0700 Subject: [PATCH 01/11] feat(comp/common/cfg/aws): separate package for common typse and functions used in remote AWS components Signed-off-by: hainenber --- internal/component/common/config/aws/aws.go | 86 +++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 internal/component/common/config/aws/aws.go diff --git a/internal/component/common/config/aws/aws.go b/internal/component/common/config/aws/aws.go new file mode 100644 index 0000000000..08da3adc90 --- /dev/null +++ b/internal/component/common/config/aws/aws.go @@ -0,0 +1,86 @@ +package aws + +import ( + "context" + "crypto/tls" + "fmt" + "net/http" + + "github.com/aws/aws-sdk-go-v2/aws" + aws_config "github.com/aws/aws-sdk-go-v2/config" + "github.com/grafana/alloy/syntax/alloytypes" +) + +// Client implements specific AWS configuration options +type Client struct { + AccessKey string `alloy:"key,attr,optional"` + Secret alloytypes.Secret `alloy:"secret,attr,optional"` + Endpoint string `alloy:"endpoint,attr,optional"` + DisableSSL bool `alloy:"disable_ssl,attr,optional"` + Region string `alloy:"region,attr,optional"` + SigningRegion string `alloy:"signing_region,attr,optional"` +} + +func GenerateAWSConfig(o Client) (*aws.Config, error) { + configOptions := make([]func(*aws_config.LoadOptions) error, 0) + // Override the endpoint. + if o.Endpoint != "" { + endFunc := aws.EndpointResolverWithOptionsFunc(func(service, region string, _ ...interface{}) (aws.Endpoint, error) { + // The S3 compatible system used for testing with does not require signing region, so it's fine to be blank + // but when using a proxy to real S3 it needs to be injected. + return aws.Endpoint{ + PartitionID: "aws", + URL: o.Endpoint, + SigningRegion: o.SigningRegion, + }, nil + }) + endResolver := aws_config.WithEndpointResolverWithOptions(endFunc) + configOptions = append(configOptions, endResolver) + } + + // This incredibly nested option turns off SSL. + if o.DisableSSL { + httpOverride := aws_config.WithHTTPClient( + &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: o.DisableSSL, + }, + }, + }, + ) + configOptions = append(configOptions, httpOverride) + } + + if o.Region != "" { + configOptions = append(configOptions, aws_config.WithRegion(o.Region)) + } + + // Check to see if we need to override the credentials, else it will use the default ones. + // https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html + if o.AccessKey != "" { + if o.Secret == "" { + return nil, fmt.Errorf("if accesskey or secret are specified then the other must also be specified") + } + credFunc := aws.CredentialsProviderFunc(func(ctx context.Context) (aws.Credentials, error) { + return aws.Credentials{ + AccessKeyID: o.AccessKey, + SecretAccessKey: string(o.Secret), + }, nil + }) + credProvider := aws_config.WithCredentialsProvider(credFunc) + configOptions = append(configOptions, credProvider) + } + + cfg, err := aws_config.LoadDefaultConfig(context.TODO(), configOptions...) + if err != nil { + return nil, err + } + + // Set region. + if o.Region != "" { + cfg.Region = o.Region + } + + return &cfg, nil +} From 4043010b8b9ec49cf45267a145fe356e723f7952 Mon Sep 17 00:00:00 2001 From: hainenber Date: Tue, 30 Apr 2024 19:12:49 +0700 Subject: [PATCH 02/11] feat(comp/remote/s3): use util func and type for AWS client config Signed-off-by: hainenber --- internal/component/remote/s3/s3.go | 75 +++------------------------ internal/component/remote/s3/types.go | 13 ++--- 2 files changed, 11 insertions(+), 77 deletions(-) diff --git a/internal/component/remote/s3/s3.go b/internal/component/remote/s3/s3.go index 6636bc97ef..9a631a6cb2 100644 --- a/internal/component/remote/s3/s3.go +++ b/internal/component/remote/s3/s3.go @@ -2,17 +2,13 @@ package s3 import ( "context" - "crypto/tls" - "fmt" - "net/http" "strings" "sync" "time" - "github.com/aws/aws-sdk-go-v2/aws" - aws_config "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/grafana/alloy/internal/component" + aws_common_config "github.com/grafana/alloy/internal/component/common/config/aws" "github.com/grafana/alloy/internal/featuregate" "github.com/grafana/alloy/syntax/alloytypes" "github.com/prometheus/client_golang/prometheus" @@ -51,12 +47,12 @@ var ( // New initializes the S3 component. func New(o component.Options, args Arguments) (*Component, error) { - s3cfg, err := generateS3Config(args) + awsCfg, err := aws_common_config.GenerateAWSConfig(args.Options.Client) if err != nil { return nil, err } - s3Client := s3.NewFromConfig(*s3cfg, func(s3o *s3.Options) { + s3Client := s3.NewFromConfig(*awsCfg, func(s3o *s3.Options) { s3o.UsePathStyle = args.Options.UsePathStyle }) @@ -79,12 +75,10 @@ func New(o component.Options, args Arguments) (*Component, error) { w := newWatcher(bucket, file, s.updateChan, args.PollFrequency, s3Client) s.watcher = w - err = o.Registerer.Register(s.s3Errors) - if err != nil { + if err := o.Registerer.Register(s.s3Errors); err != nil { return nil, err } - err = o.Registerer.Register(s.lastAccessed) - if err != nil { + if err := o.Registerer.Register(s.lastAccessed); err != nil { return nil, err } @@ -106,11 +100,11 @@ func (s *Component) Run(ctx context.Context) error { func (s *Component) Update(args component.Arguments) error { newArgs := args.(Arguments) - s3cfg, err := generateS3Config(newArgs) + awsCfg, err := aws_common_config.GenerateAWSConfig(newArgs.Options.Client) if err != nil { return nil } - s3Client := s3.NewFromConfig(*s3cfg, func(s3o *s3.Options) { + s3Client := s3.NewFromConfig(*awsCfg, func(s3o *s3.Options) { s3o.UsePathStyle = newArgs.Options.UsePathStyle }) @@ -131,61 +125,6 @@ func (s *Component) CurrentHealth() component.Health { return s.health } -func generateS3Config(args Arguments) (*aws.Config, error) { - configOptions := make([]func(*aws_config.LoadOptions) error, 0) - // Override the endpoint. - if args.Options.Endpoint != "" { - endFunc := aws.EndpointResolverWithOptionsFunc(func(service, region string, _ ...interface{}) (aws.Endpoint, error) { - // The S3 compatible system used for testing with does not require signing region, so it's fine to be blank - // but when using a proxy to real S3 it needs to be injected. - return aws.Endpoint{URL: args.Options.Endpoint, SigningRegion: args.Options.SigningRegion}, nil - }) - endResolver := aws_config.WithEndpointResolverWithOptions(endFunc) - configOptions = append(configOptions, endResolver) - } - - // This incredibly nested option turns off SSL. - if args.Options.DisableSSL { - httpOverride := aws_config.WithHTTPClient( - &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: args.Options.DisableSSL, - }, - }, - }, - ) - configOptions = append(configOptions, httpOverride) - } - - // Check to see if we need to override the credentials, else it will use the default ones. - // https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html - if args.Options.AccessKey != "" { - if args.Options.Secret == "" { - return nil, fmt.Errorf("if accesskey or secret are specified then the other must also be specified") - } - credFunc := aws.CredentialsProviderFunc(func(ctx context.Context) (aws.Credentials, error) { - return aws.Credentials{ - AccessKeyID: args.Options.AccessKey, - SecretAccessKey: string(args.Options.Secret), - }, nil - }) - credProvider := aws_config.WithCredentialsProvider(credFunc) - configOptions = append(configOptions, credProvider) - } - - cfg, err := aws_config.LoadDefaultConfig(context.TODO(), configOptions...) - if err != nil { - return nil, err - } - // Set region. - if args.Options.Region != "" { - cfg.Region = args.Options.Region - } - - return &cfg, nil -} - // handleContentUpdate reads from the update and error channels setting as appropriate func (s *Component) handleContentUpdate(ctx context.Context) { for { diff --git a/internal/component/remote/s3/types.go b/internal/component/remote/s3/types.go index a3f3a21639..cc017563ad 100644 --- a/internal/component/remote/s3/types.go +++ b/internal/component/remote/s3/types.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + aws_common_config "github.com/grafana/alloy/internal/component/common/config/aws" "github.com/grafana/alloy/syntax/alloytypes" ) @@ -15,19 +16,13 @@ type Arguments struct { PollFrequency time.Duration `alloy:"poll_frequency,attr,optional"` // IsSecret determines if the content should be displayed to the user. IsSecret bool `alloy:"is_secret,attr,optional"` - // Options allows the overriding of default settings. + // Options allows the overriding of default AWS settings. Options Client `alloy:"client,block,optional"` } -// Client implements specific AWS configuration options type Client struct { - AccessKey string `alloy:"key,attr,optional"` - Secret alloytypes.Secret `alloy:"secret,attr,optional"` - Endpoint string `alloy:"endpoint,attr,optional"` - DisableSSL bool `alloy:"disable_ssl,attr,optional"` - UsePathStyle bool `alloy:"use_path_style,attr,optional"` - Region string `alloy:"region,attr,optional"` - SigningRegion string `alloy:"signing_region,attr,optional"` + aws_common_config.Client + UsePathStyle bool `alloy:"use_path_style,attr,optional"` } const minimumPollFrequency = 30 * time.Second From cbdb0e339292fbad9720e0662ea6c579f82a222f Mon Sep 17 00:00:00 2001 From: hainenber Date: Tue, 30 Apr 2024 19:13:59 +0700 Subject: [PATCH 03/11] feat(comp/test): expose internal test component to check for its health Signed-off-by: hainenber --- internal/runtime/componenttest/componenttest.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/runtime/componenttest/componenttest.go b/internal/runtime/componenttest/componenttest.go index 1793a6b96a..846b8a509f 100644 --- a/internal/runtime/componenttest/componenttest.go +++ b/internal/runtime/componenttest/componenttest.go @@ -188,3 +188,7 @@ func (c *Controller) Update(args component.Arguments) error { } return c.inner.Update(args) } + +func (c *Controller) GetInnerComponent() component.Component { + return c.inner +} From 7824d1d5e70ed9fbf00f48d5df8b5ab51662faf4 Mon Sep 17 00:00:00 2001 From: hainenber Date: Tue, 30 Apr 2024 19:14:31 +0700 Subject: [PATCH 04/11] feat(comp/remote/aws/secrets_manager): add `remote.aws.secrets_manager` Signed-off-by: hainenber --- CHANGELOG.md | 3 + .../components/remote.aws.secrets_manager.md | 90 ++++++++++ go.mod | 1 - internal/component/all/all.go | 1 + .../aws/secrets_manager/secrets_manager.go | 165 ++++++++++++++++++ .../secrets_manager/secrets_manager_test.go | 108 ++++++++++++ 6 files changed, 367 insertions(+), 1 deletion(-) create mode 100644 docs/sources/reference/components/remote.aws.secrets_manager.md create mode 100644 internal/component/remote/aws/secrets_manager/secrets_manager.go create mode 100644 internal/component/remote/aws/secrets_manager/secrets_manager_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a04da7d0a..5077697b69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,9 @@ v1.1.0 - Introduce a `otelcol.receiver.file_stats` component from the upstream OpenTelemetry `filestatsreceiver` component. (@rfratto) +- New component `remote.aws.secrets_manager` to obtain decrypted secrets + from AWS Secrets Manager. (@hainenber) + ### Enhancements - Update `prometheus.exporter.kafka` with the following functionalities (@wildum): diff --git a/docs/sources/reference/components/remote.aws.secrets_manager.md b/docs/sources/reference/components/remote.aws.secrets_manager.md new file mode 100644 index 0000000000..e05fefbf4d --- /dev/null +++ b/docs/sources/reference/components/remote.aws.secrets_manager.md @@ -0,0 +1,90 @@ +--- +canonical: https://grafana.com/docs/alloy/latest/reference/components/remote.aws.secrets_manager/ +description: Learn about remote.aws.secrets_manager +title: remote.aws.secrets_manager +--- + +# remote.aws.secret_manager + +`remote.aws.secrets_manager` securely exposes value of secrets located in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) to other components. +The secret would be fetched one time only at startup. Restart Alloy if you have updated to new value and would like +the component to fetch the latest version. + +Multiple `remote.aws.secrets_manager` components can be specified using different name +labels. By default, [AWS environment variables](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html) are used to authenticate against AWS. The `key` and `secret` arguments inside `client` blocks can be used to provide custom authentication. + +## Usage + +```alloy +remote.aws.secrets_manager "LABEL" { + id = AWS_SECRETS_MANAGER_SECRET_ID +} +``` + +## Arguments + +The following arguments are supported: + +Name | Type | Description | Default | Required +-----------------|------------|--------------------------------------------------------------------------|---------|--------- +`id` | `string` | Secret ID. | | yes + +## Blocks + +Hierarchy | Name | Description | Required +----------|------------|----------------------------------------------------|--------- +client | [client][] | Additional options for configuring the AWS client. | no + +[client]: #client-block + +### client block + +The `client` block customizes options to connect to the AWS server. + +Name | Type | Description | Default | Required +-----------------|----------|-----------------------------------------------------------------------------------------|---------|--------- +`key` | `string` | Used to override default access key. | | no +`secret` | `secret` | Used to override default secret value. | | no +`disable_ssl` | `bool` | Used to disable SSL, generally used for testing. | | no +`region` | `string` | Used to override default region. | | no +`signing_region` | `string` | Used to override the signing region when using a custom endpoint. | | no + + +## Exported fields + +The following fields are exported and can be referenced by other components: + +Name | Type | Description +----------|----------------------|--------------------------------------------------------- +`data` | `map(secret)` | Data from the secret obtained from AWS Secrets Manager. + +The `data` field contains a mapping from data field names to values. + +## Component health + +Instances of `remote.aws.secrets_manager` report as healthy if most recent fetch of stored secrets was successful. + +## Debug information + +`remote.aws.secrets_manager` doesn't expose any component-specific debug information. + +## Debug metrics + +`remote.aws.secrets_manager` doesn't expose any component-specific debug metrics. + +## Example + +```alloy +remote.aws.secrets_manager "data" { + id = "foo" +} + +metrics.remote_write "prod" { + remote_write { + url = "https://onprem-mimir:9009/api/v1/push" + basic_auth { + username = remote.vault.remote_write.data.username + password = remote.vault.remote_write.data.password + } + } +``` diff --git a/go.mod b/go.mod index 5eefe59389..930a736e46 100644 --- a/go.mod +++ b/go.mod @@ -318,7 +318,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.0 // indirect github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.20.0 // indirect - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.27.0 // indirect github.com/aws/aws-sdk-go-v2/service/shield v1.24.0 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect diff --git a/internal/component/all/all.go b/internal/component/all/all.go index cd29bebf6d..abb433ff65 100644 --- a/internal/component/all/all.go +++ b/internal/component/all/all.go @@ -131,6 +131,7 @@ import ( _ "github.com/grafana/alloy/internal/component/pyroscope/java" // Import pyroscope.java _ "github.com/grafana/alloy/internal/component/pyroscope/scrape" // Import pyroscope.scrape _ "github.com/grafana/alloy/internal/component/pyroscope/write" // Import pyroscope.write + _ "github.com/grafana/alloy/internal/component/remote/aws/secrets_manager" // Import remote.aws.secrets_manager _ "github.com/grafana/alloy/internal/component/remote/http" // Import remote.http _ "github.com/grafana/alloy/internal/component/remote/kubernetes/configmap" // Import remote.kubernetes.configmap _ "github.com/grafana/alloy/internal/component/remote/kubernetes/secret" // Import remote.kubernetes.secret diff --git a/internal/component/remote/aws/secrets_manager/secrets_manager.go b/internal/component/remote/aws/secrets_manager/secrets_manager.go new file mode 100644 index 0000000000..33a5216842 --- /dev/null +++ b/internal/component/remote/aws/secrets_manager/secrets_manager.go @@ -0,0 +1,165 @@ +package secrets_manager + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" + "github.com/grafana/alloy/internal/component" + aws_common_config "github.com/grafana/alloy/internal/component/common/config/aws" + "github.com/grafana/alloy/internal/featuregate" + "github.com/grafana/alloy/syntax/alloytypes" + "github.com/prometheus/client_golang/prometheus" +) + +func init() { + component.Register(component.Registration{ + Name: "remote.aws.secrets_manager", + Stability: featuregate.StabilityExperimental, + Args: Arguments{}, + Exports: Exports{}, + Build: func(opts component.Options, args component.Arguments) (component.Component, error) { + return New(opts, args.(Arguments)) + }, + }) +} + +type Component struct { + opts component.Options + mut sync.Mutex + health component.Health + errors prometheus.Counter + lastAccessed prometheus.Gauge +} + +var ( + _ component.Component = (*Component)(nil) + _ component.HealthComponent = (*Component)(nil) +) + +type Arguments struct { + // Options allows the overriding of default AWS settings. + Options aws_common_config.Client `alloy:"client,block,optional"` + SecretId string `alloy:"id,attr"` + SecretVersion string `alloy:"version,attr,optional"` +} + +// DefaultArguments holds default settings for Arguments. +var DefaultArguments = Arguments{ + SecretVersion: "AWSCURRENT", +} + +// SetToDefault implements syntax.Defaulter. +func (a *Arguments) SetToDefault() { + *a = DefaultArguments +} + +type Exports struct { + Data map[string]alloytypes.Secret `alloy:"data,attr"` +} + +// New initializes a new component +func New(o component.Options, args Arguments) (*Component, error) { + s := &Component{ + opts: o, + health: component.Health{}, + errors: prometheus.NewCounter(prometheus.CounterOpts{ + Name: "remote_aws_secrets_manager_errors_total", + Help: "Total number of errors when accessing AWS Secrets Manager", + }), + lastAccessed: prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "remote_aws_secrets_manager_timestamp_last_accessed_unix_seconds", + Help: "The last successful access in unix seconds", + }), + } + + if err := o.Registerer.Register(s.errors); err != nil { + return nil, err + } + if err := o.Registerer.Register(s.lastAccessed); err != nil { + return nil, err + } + + if err := s.Update(args); err != nil { + return nil, err + } + + return s, nil +} + +func (c *Component) Run(ctx context.Context) error { + <-ctx.Done() + return nil +} + +// Update is called whenever the arguments have changed. +func (c *Component) Update(args component.Arguments) (err error) { + defer c.updateHealth(err) + newArgs := args.(Arguments) + + awsCfg, err := aws_common_config.GenerateAWSConfig(newArgs.Options) + if err != nil { + return err + } + + // Create Secrets Manager client + svc := secretsmanager.NewFromConfig(*awsCfg) + + result, err := svc.GetSecretValue(context.TODO(), &secretsmanager.GetSecretValueInput{ + SecretId: aws.String(newArgs.SecretId), + VersionStage: aws.String(newArgs.SecretVersion), + }) + if err != nil { + return err + } + + if result == nil { + err = fmt.Errorf("unable to retrieve secret at path %s", newArgs.SecretId) + return err + } + + c.exportSecret(result) + + return nil +} + +// exportSecret converts the secret into exports and exports it to the +// controller. +func (c *Component) exportSecret(secret *secretsmanager.GetSecretValueOutput) { + if secret != nil { + newExports := Exports{ + Data: make(map[string]alloytypes.Secret), + } + newExports.Data[*secret.Name] = alloytypes.Secret(*secret.SecretString) + c.opts.OnStateChange(newExports) + } +} + +// CurrentHealth returns the health of the component. +func (s *Component) CurrentHealth() component.Health { + s.mut.Lock() + defer s.mut.Unlock() + return s.health +} + +func (c *Component) updateHealth(err error) { + c.mut.Lock() + defer c.mut.Unlock() + + if err != nil { + c.health = component.Health{ + Health: component.HealthTypeUnhealthy, + Message: err.Error(), + UpdateTime: time.Now(), + } + } else { + c.health = component.Health{ + Health: component.HealthTypeHealthy, + Message: "remote.aws.secrets_manager updated", + UpdateTime: time.Now(), + } + } +} diff --git a/internal/component/remote/aws/secrets_manager/secrets_manager_test.go b/internal/component/remote/aws/secrets_manager/secrets_manager_test.go new file mode 100644 index 0000000000..414e286a3e --- /dev/null +++ b/internal/component/remote/aws/secrets_manager/secrets_manager_test.go @@ -0,0 +1,108 @@ +// go:build !nodocker +package secrets_manager + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" + "github.com/docker/go-connections/nat" + "github.com/grafana/alloy/internal/alloy/componenttest" + "github.com/grafana/alloy/internal/component" + aws_common_config "github.com/grafana/alloy/internal/component/common/config/aws" + "github.com/grafana/alloy/internal/util" + "github.com/grafana/alloy/syntax" + "github.com/grafana/alloy/syntax/alloytypes" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +func Test_GetSecrets(t *testing.T) { + var ( + ctx = componenttest.TestContext(t) + ep = runTestLocalSecretManager(t) + l = util.TestLogger(t) + ) + + cfg := fmt.Sprintf(` + client { + endpoint = "%s" + key = "test" + secret = "test" + } + id = "foo" +`, ep) + + var args Arguments + require.NoError(t, syntax.Unmarshal([]byte(cfg), &args)) + + ctrl, err := componenttest.NewControllerFromID(l, "remote.aws.secrets_manager") + require.NoError(t, err) + + go func() { + require.NoError(t, ctrl.Run(ctx, args)) + }() + + require.NoError(t, ctrl.WaitRunning(time.Minute)) + require.NoError(t, ctrl.WaitExports(time.Minute)) + + var ( + expectExports = Exports{ + Data: map[string]alloytypes.Secret{ + "foo": alloytypes.Secret("bar"), + }, + } + actualExports = ctrl.Exports().(Exports) + ) + require.Equal(t, expectExports, actualExports) + + innerComponent := ctrl.GetInnerComponent().(*Component) + require.Equal(t, innerComponent.CurrentHealth().Health, component.HealthTypeHealthy) +} + +func runTestLocalSecretManager(t *testing.T) string { + ctx := componenttest.TestContext(t) + container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Image: "localstack/localstack:3.4.0", + ExposedPorts: []string{"4566/tcp"}, + WaitingFor: wait.ForAll( + wait.ForListeningPort("4566/tcp"), + ), + }, + Started: true, + }) + require.NoError(t, err) + + t.Cleanup(func() { + require.NoError(t, container.Terminate(ctx)) + }) + + ep, err := container.PortEndpoint(ctx, nat.Port("4566/tcp"), "http") + require.NoError(t, err) + + // Create a secret with Localstack's SecretManager client + awsCfg, err := aws_common_config.GenerateAWSConfig(aws_common_config.Client{ + AccessKey: "test", + Secret: alloytypes.Secret("test"), + Endpoint: ep, + Region: "us-east-1", + }) + require.NoError(t, err) + + svc := secretsmanager.NewFromConfig(*awsCfg) + + secretName := "foo" + secretString := "bar" + result, err := svc.CreateSecret(context.TODO(), &secretsmanager.CreateSecretInput{ + Name: &secretName, + SecretString: &secretString, + }) + require.NoError(t, err) + require.NotNil(t, result) + + return ep +} From 5ccfc92b2d192e1aff5969eb83ce062c0e80cbdd Mon Sep 17 00:00:00 2001 From: hainenber Date: Tue, 30 Apr 2024 21:25:52 +0700 Subject: [PATCH 05/11] fix(comp/remote/aws/secret_manager): correct go:build directive to run test in Docker-present env Signed-off-by: hainenber --- .../remote/aws/secrets_manager/secrets_manager_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/component/remote/aws/secrets_manager/secrets_manager_test.go b/internal/component/remote/aws/secrets_manager/secrets_manager_test.go index 414e286a3e..aa871acccf 100644 --- a/internal/component/remote/aws/secrets_manager/secrets_manager_test.go +++ b/internal/component/remote/aws/secrets_manager/secrets_manager_test.go @@ -1,4 +1,5 @@ -// go:build !nodocker +//go:build !nodocker + package secrets_manager import ( From 631a2a2a151d18663a9b3a26862ecef9a350b9f2 Mon Sep 17 00:00:00 2001 From: hainenber Date: Wed, 1 May 2024 22:26:12 +0700 Subject: [PATCH 06/11] feat(remote/aws/secrets_manager): implement poller + update doc Signed-off-by: hainenber --- .../components/remote.aws.secrets_manager.md | 6 +- .../aws/secrets_manager/secrets_manager.go | 105 +++++++++++------ .../secrets_manager/secrets_manager_test.go | 84 ++++++++++++-- .../remote/aws/secrets_manager/watcher.go | 109 ++++++++++++++++++ 4 files changed, 254 insertions(+), 50 deletions(-) create mode 100644 internal/component/remote/aws/secrets_manager/watcher.go diff --git a/docs/sources/reference/components/remote.aws.secrets_manager.md b/docs/sources/reference/components/remote.aws.secrets_manager.md index e05fefbf4d..fa4c15e3a9 100644 --- a/docs/sources/reference/components/remote.aws.secrets_manager.md +++ b/docs/sources/reference/components/remote.aws.secrets_manager.md @@ -7,8 +7,9 @@ title: remote.aws.secrets_manager # remote.aws.secret_manager `remote.aws.secrets_manager` securely exposes value of secrets located in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) to other components. -The secret would be fetched one time only at startup. Restart Alloy if you have updated to new value and would like -the component to fetch the latest version. +By default, the secret would be fetched one time only at startup. If configured, the secret will be polled for changes so that the most recent value is always available. + +Beware that this could incur cost due to frequent API calls. Multiple `remote.aws.secrets_manager` components can be specified using different name labels. By default, [AWS environment variables](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html) are used to authenticate against AWS. The `key` and `secret` arguments inside `client` blocks can be used to provide custom authentication. @@ -28,6 +29,7 @@ The following arguments are supported: Name | Type | Description | Default | Required -----------------|------------|--------------------------------------------------------------------------|---------|--------- `id` | `string` | Secret ID. | | yes +`poll_frequency` | `duration` | How often to poll the API for changes. | | no ## Blocks diff --git a/internal/component/remote/aws/secrets_manager/secrets_manager.go b/internal/component/remote/aws/secrets_manager/secrets_manager.go index 33a5216842..b638929a0b 100644 --- a/internal/component/remote/aws/secrets_manager/secrets_manager.go +++ b/internal/component/remote/aws/secrets_manager/secrets_manager.go @@ -2,11 +2,9 @@ package secrets_manager import ( "context" - "fmt" "sync" "time" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/secretsmanager" "github.com/grafana/alloy/internal/component" aws_common_config "github.com/grafana/alloy/internal/component/common/config/aws" @@ -28,11 +26,14 @@ func init() { } type Component struct { - opts component.Options - mut sync.Mutex - health component.Health - errors prometheus.Counter - lastAccessed prometheus.Gauge + opts component.Options + mut sync.Mutex + health component.Health + pollFrequency time.Duration + watcher *watcher + updateChan chan result + errors prometheus.Counter + lastAccessed prometheus.Gauge } var ( @@ -45,6 +46,7 @@ type Arguments struct { Options aws_common_config.Client `alloy:"client,block,optional"` SecretId string `alloy:"id,attr"` SecretVersion string `alloy:"version,attr,optional"` + PollFrequency time.Duration `alloy:"poll_frequency,attr,optional"` } // DefaultArguments holds default settings for Arguments. @@ -63,9 +65,18 @@ type Exports struct { // New initializes a new component func New(o component.Options, args Arguments) (*Component, error) { + // Create AWS and AWS's Secrets Manager client + awsCfg, err := aws_common_config.GenerateAWSConfig(args.Options) + if err != nil { + return nil, err + } + client := secretsmanager.NewFromConfig(*awsCfg) + s := &Component{ - opts: o, - health: component.Health{}, + opts: o, + pollFrequency: args.PollFrequency, + health: component.Health{}, + updateChan: make(chan result), errors: prometheus.NewCounter(prometheus.CounterOpts{ Name: "remote_aws_secrets_manager_errors_total", Help: "Total number of errors when accessing AWS Secrets Manager", @@ -76,6 +87,9 @@ func New(o component.Options, args Arguments) (*Component, error) { }), } + w := newWatcher(args.SecretId, args.SecretVersion, s.updateChan, args.PollFrequency, client) + s.watcher = w + if err := o.Registerer.Register(s.errors); err != nil { return nil, err } @@ -83,59 +97,74 @@ func New(o component.Options, args Arguments) (*Component, error) { return nil, err } - if err := s.Update(args); err != nil { - return nil, err + res := w.getSecret(context.TODO()) + if res.err != nil { + return nil, res.err } + s.handlePolledSecret(res) + return s, nil } -func (c *Component) Run(ctx context.Context) error { +func (s *Component) Run(ctx context.Context) error { + if s.pollFrequency > 0 { + go s.handleSecretUpdate(ctx) + go s.watcher.run(ctx) + } <-ctx.Done() return nil } // Update is called whenever the arguments have changed. -func (c *Component) Update(args component.Arguments) (err error) { - defer c.updateHealth(err) +func (s *Component) Update(args component.Arguments) (err error) { + defer s.updateHealth(err) newArgs := args.(Arguments) + // Create AWS and AWS's Secrets Manager client awsCfg, err := aws_common_config.GenerateAWSConfig(newArgs.Options) if err != nil { return err } + client := secretsmanager.NewFromConfig(*awsCfg) - // Create Secrets Manager client - svc := secretsmanager.NewFromConfig(*awsCfg) + s.mut.Lock() + defer s.mut.Unlock() + s.pollFrequency = newArgs.PollFrequency + s.watcher.updateValues(newArgs.SecretId, newArgs.SecretVersion, newArgs.PollFrequency, client) - result, err := svc.GetSecretValue(context.TODO(), &secretsmanager.GetSecretValueInput{ - SecretId: aws.String(newArgs.SecretId), - VersionStage: aws.String(newArgs.SecretVersion), - }) - if err != nil { - return err - } + return nil +} - if result == nil { - err = fmt.Errorf("unable to retrieve secret at path %s", newArgs.SecretId) - return err +// handleSecretUpdate reads from update and error channels, setting as approriate +func (s *Component) handleSecretUpdate(ctx context.Context) { + for { + select { + case r := <-s.updateChan: + s.handlePolledSecret(r) + case <-ctx.Done(): + return + } } - - c.exportSecret(result) - - return nil } -// exportSecret converts the secret into exports and exports it to the +// handledPolledSecret converts the secret into exports and exports it to the // controller. -func (c *Component) exportSecret(secret *secretsmanager.GetSecretValueOutput) { - if secret != nil { - newExports := Exports{ - Data: make(map[string]alloytypes.Secret), - } - newExports.Data[*secret.Name] = alloytypes.Secret(*secret.SecretString) - c.opts.OnStateChange(newExports) +func (s *Component) handlePolledSecret(res result) { + var err error + if validated := res.Validate(); validated { + s.opts.OnStateChange(Exports{ + Data: map[string]alloytypes.Secret{ + res.secretId: alloytypes.Secret(res.secret), + }, + }) + s.lastAccessed.SetToCurrentTime() + } else { + s.errors.Inc() + err = res.err } + + s.updateHealth(err) } // CurrentHealth returns the health of the component. diff --git a/internal/component/remote/aws/secrets_manager/secrets_manager_test.go b/internal/component/remote/aws/secrets_manager/secrets_manager_test.go index aa871acccf..727310b172 100644 --- a/internal/component/remote/aws/secrets_manager/secrets_manager_test.go +++ b/internal/component/remote/aws/secrets_manager/secrets_manager_test.go @@ -23,9 +23,10 @@ import ( func Test_GetSecrets(t *testing.T) { var ( - ctx = componenttest.TestContext(t) - ep = runTestLocalSecretManager(t) - l = util.TestLogger(t) + secretId = "foo" + ctx = componenttest.TestContext(t) + ep, _ = runTestLocalSecretManager(t, secretId) + l = util.TestLogger(t) ) cfg := fmt.Sprintf(` @@ -34,8 +35,8 @@ func Test_GetSecrets(t *testing.T) { key = "test" secret = "test" } - id = "foo" -`, ep) + id = "%s" +`, ep, secretId) var args Arguments require.NoError(t, syntax.Unmarshal([]byte(cfg), &args)) @@ -53,7 +54,7 @@ func Test_GetSecrets(t *testing.T) { var ( expectExports = Exports{ Data: map[string]alloytypes.Secret{ - "foo": alloytypes.Secret("bar"), + secretId: alloytypes.Secret("bar"), }, } actualExports = ctrl.Exports().(Exports) @@ -64,7 +65,71 @@ func Test_GetSecrets(t *testing.T) { require.Equal(t, innerComponent.CurrentHealth().Health, component.HealthTypeHealthy) } -func runTestLocalSecretManager(t *testing.T) string { +func Test_PollSecrets(t *testing.T) { + var ( + secretId = "foo_poll" + ctx = componenttest.TestContext(t) + ep, client = runTestLocalSecretManager(t, secretId) + l = util.TestLogger(t) + ) + + cfg := fmt.Sprintf(` + client { + endpoint = "%s" + key = "test" + secret = "test" + } + + poll_frequency = "1s" + + id = "%s" +`, ep, secretId) + + var args Arguments + require.NoError(t, syntax.Unmarshal([]byte(cfg), &args)) + + ctrl, err := componenttest.NewControllerFromID(l, "remote.aws.secrets_manager") + require.NoError(t, err) + + go func() { + require.NoError(t, ctrl.Run(ctx, args)) + }() + + require.NoError(t, ctrl.WaitRunning(time.Minute)) + require.NoError(t, ctrl.WaitExports(time.Minute)) + + var ( + expectExports = Exports{ + Data: map[string]alloytypes.Secret{ + secretId: alloytypes.Secret("bar"), + }, + } + actualExports = ctrl.Exports().(Exports) + ) + require.Equal(t, expectExports, actualExports) + + // Updated the secret to something else + updatedSecretString := "bar_poll" + result, err := client.UpdateSecret(context.TODO(), &secretsmanager.UpdateSecretInput{ + SecretId: &secretId, + SecretString: &updatedSecretString, + }) + require.NoError(t, err) + require.NotNil(t, result) + + require.NoError(t, ctrl.WaitExports(time.Minute)) + + expectExports = Exports{ + Data: map[string]alloytypes.Secret{ + secretId: alloytypes.Secret(updatedSecretString), + }, + } + actualExports = ctrl.Exports().(Exports) + require.Equal(t, expectExports, actualExports) + +} + +func runTestLocalSecretManager(t *testing.T, secretId string) (string, *secretsmanager.Client) { ctx := componenttest.TestContext(t) container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ @@ -96,14 +161,13 @@ func runTestLocalSecretManager(t *testing.T) string { svc := secretsmanager.NewFromConfig(*awsCfg) - secretName := "foo" secretString := "bar" result, err := svc.CreateSecret(context.TODO(), &secretsmanager.CreateSecretInput{ - Name: &secretName, + Name: &secretId, SecretString: &secretString, }) require.NoError(t, err) require.NotNil(t, result) - return ep + return ep, svc } diff --git a/internal/component/remote/aws/secrets_manager/watcher.go b/internal/component/remote/aws/secrets_manager/watcher.go new file mode 100644 index 0000000000..bddc3bd940 --- /dev/null +++ b/internal/component/remote/aws/secrets_manager/watcher.go @@ -0,0 +1,109 @@ +package secrets_manager + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" +) + +type watcher struct { + mut sync.Mutex + secretId string + secretVersion string + output chan result + pollTicker *time.Ticker + client *secretsmanager.Client +} + +type result struct { + secret string + secretId string + secretVersion string + err error +} + +func (r result) Validate() bool { + return r.secretId != "" && r.secretVersion != "" && r.err == nil +} + +func newWatcher( + secretId, secretVersion string, + out chan result, + frequency time.Duration, + client *secretsmanager.Client, +) *watcher { + w := &watcher{ + secretId: secretId, + secretVersion: secretVersion, + output: out, + client: client, + } + + if frequency > 0 { + w.pollTicker = time.NewTicker(frequency) + } + return w +} + +func (w *watcher) updateValues(secretId, secretVersion string, frequency time.Duration, client *secretsmanager.Client) { + w.mut.Lock() + defer w.mut.Unlock() + w.secretId = secretId + w.secretVersion = secretVersion + if frequency > 0 { + w.pollTicker.Reset(frequency) + } + w.client = client +} + +func (w *watcher) run(ctx context.Context) { + w.getSecretAsynchronously(ctx) + defer w.pollTicker.Stop() + for { + select { + case <-w.pollTicker.C: + w.getSecretAsynchronously(ctx) + case <-ctx.Done(): + return + } + } +} + +func (w *watcher) getSecretAsynchronously(ctx context.Context) { + w.mut.Lock() + defer w.mut.Unlock() + + res := w.getSecret(context.Background()) + + select { + case <-ctx.Done(): + return + case w.output <- res: + } +} + +func (w *watcher) getSecret(ctx context.Context) result { + res := result{} + fetchedResult, err := w.client.GetSecretValue(ctx, &secretsmanager.GetSecretValueInput{ + SecretId: aws.String(w.secretId), + VersionStage: aws.String(w.secretVersion), + }) + + if err != nil { + res.err = err + } + + if fetchedResult != nil { + res.secret = *fetchedResult.SecretString + res.secretId = *fetchedResult.Name + res.secretVersion = *fetchedResult.VersionId + } else { + res.err = fmt.Errorf("error fetching secret %s from AWS Secrets Manager: %s", w.secretId, err.Error()) + } + + return res +} From 4a9dc8091d8195f88fcdf8871e34a09d6b417588 Mon Sep 17 00:00:00 2001 From: hainenber Date: Thu, 2 May 2024 21:25:01 +0700 Subject: [PATCH 07/11] fix(remote/aws/secrets_manager): only reset poller's ticker when initial poller is configured Signed-off-by: hainenber --- internal/component/remote/aws/secrets_manager/watcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/component/remote/aws/secrets_manager/watcher.go b/internal/component/remote/aws/secrets_manager/watcher.go index bddc3bd940..cde8ac9aab 100644 --- a/internal/component/remote/aws/secrets_manager/watcher.go +++ b/internal/component/remote/aws/secrets_manager/watcher.go @@ -54,7 +54,7 @@ func (w *watcher) updateValues(secretId, secretVersion string, frequency time.Du defer w.mut.Unlock() w.secretId = secretId w.secretVersion = secretVersion - if frequency > 0 { + if w.pollTicker != nil && frequency > 0 { w.pollTicker.Reset(frequency) } w.client = client From 21f1b2e1f367a3db4d647aeac230dc35c5832c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90=E1=BB=97=20Tr=E1=BB=8Dng=20H=E1=BA=A3i?= <41283691+hainenber@users.noreply.github.com> Date: Wed, 8 May 2024 07:40:53 +0700 Subject: [PATCH 08/11] Apply suggestions from Clayton's code review Co-authored-by: Clayton Cornell <131809008+clayton-cornell@users.noreply.github.com> --- .../components/remote.aws.secrets_manager.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/sources/reference/components/remote.aws.secrets_manager.md b/docs/sources/reference/components/remote.aws.secrets_manager.md index fa4c15e3a9..21aa08fb6c 100644 --- a/docs/sources/reference/components/remote.aws.secrets_manager.md +++ b/docs/sources/reference/components/remote.aws.secrets_manager.md @@ -6,13 +6,18 @@ title: remote.aws.secrets_manager # remote.aws.secret_manager -`remote.aws.secrets_manager` securely exposes value of secrets located in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) to other components. -By default, the secret would be fetched one time only at startup. If configured, the secret will be polled for changes so that the most recent value is always available. +`remote.aws.secrets_manager` securely exposes the secrets located in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) to other components. +By default, the secret is fetched once only at startup. If configured, the secret is polled for changes so that the most recent value is always available. -Beware that this could incur cost due to frequent API calls. +{{< admonition type="note" >}} +The polling for changes could incur costs due to frequent API calls. +{{< /admonition >}} -Multiple `remote.aws.secrets_manager` components can be specified using different name -labels. By default, [AWS environment variables](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html) are used to authenticate against AWS. The `key` and `secret` arguments inside `client` blocks can be used to provide custom authentication. +You can specify multiple `remote.aws.secrets_manager` components by giving them different labels. +By default, [AWS environment variables][] are used to authenticate against AWS. +For custom authentication, you can use the `key` and `secret` arguments inside `client` blocks. + + [AWS environment variables]: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html ## Usage @@ -35,7 +40,7 @@ Name | Type | Description Hierarchy | Name | Description | Required ----------|------------|----------------------------------------------------|--------- -client | [client][] | Additional options for configuring the AWS client. | no +client | [client][] | Additional AWS client configuration options. | no [client]: #client-block @@ -64,7 +69,7 @@ The `data` field contains a mapping from data field names to values. ## Component health -Instances of `remote.aws.secrets_manager` report as healthy if most recent fetch of stored secrets was successful. +Instances of `remote.aws.secrets_manager` report as healthy if the most recent fetch of stored secrets was successful. ## Debug information From 1e2dcfccfc2ad06f60023b1151b1bc5c18d7c87d Mon Sep 17 00:00:00 2001 From: hainenber Date: Tue, 14 May 2024 22:01:41 +0700 Subject: [PATCH 09/11] chore: clean up `go.mod` Signed-off-by: hainenber --- go.mod | 1 + go.sum | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 930a736e46..0bf5458c37 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.27.11 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.49.0 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0 github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.29.5 github.com/blang/semver/v4 v4.0.0 github.com/bmatcuk/doublestar v1.3.4 diff --git a/go.sum b/go.sum index b702982f21..c8142c47e2 100644 --- a/go.sum +++ b/go.sum @@ -364,8 +364,8 @@ github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.20.0 h1:MaTOKZE github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.20.0/go.mod h1:BRuiq4shgrokCvNWSXVHz1hhH5sNSLW0ZruTV0jiNMQ= github.com/aws/aws-sdk-go-v2/service/s3 v1.49.0 h1:VfU15izXQjz4m9y1DkbY79iylIiuPwWtrram4cSpWEI= github.com/aws/aws-sdk-go-v2/service/s3 v1.49.0/go.mod h1:1o/W6JFUuREj2ExoQ21vHJgO7wakvjhol91M9eknFgs= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.27.0 h1:64jRTsqBcIqlA4N7ZFYy+ysGPE7Rz/nJgU2fwv2cymk= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.27.0/go.mod h1:JsJDZFHwLGZu6dxhV9EV1gJrMnCeE4GEXubSZA59xdA= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0 h1:dPCRgAL4WD9tSMaDglRNGOiAtSTjkwNiUW5GDpWFfHA= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0/go.mod h1:4Ae1NCLK6ghmjzd45Tc33GgCKhUWD2ORAlULtMO1Cbs= github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.29.5 h1:a3nFS1TFNTH9TVizItnHz3BgPCk5/7ygrZQZAoUV3GA= github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.29.5/go.mod h1:3pzLFJnbjkymz6RdZ963DuvMR9rzrKMXrlbteSk4Sxc= github.com/aws/aws-sdk-go-v2/service/shield v1.24.0 h1:DasZw37v6ciRecoPkslCl8rHmoPfzfwpnR48pxWJaGg= From 20a34d072d7c4a22bf905986c39951c2fc0a1d30 Mon Sep 17 00:00:00 2001 From: hainenber Date: Thu, 16 May 2024 22:07:15 +0700 Subject: [PATCH 10/11] chore: use renamed `internal/runtime` for component test Signed-off-by: hainenber --- .../remote/aws/secrets_manager/secrets_manager_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/component/remote/aws/secrets_manager/secrets_manager_test.go b/internal/component/remote/aws/secrets_manager/secrets_manager_test.go index 727310b172..1efc15931f 100644 --- a/internal/component/remote/aws/secrets_manager/secrets_manager_test.go +++ b/internal/component/remote/aws/secrets_manager/secrets_manager_test.go @@ -10,9 +10,9 @@ import ( "github.com/aws/aws-sdk-go-v2/service/secretsmanager" "github.com/docker/go-connections/nat" - "github.com/grafana/alloy/internal/alloy/componenttest" "github.com/grafana/alloy/internal/component" aws_common_config "github.com/grafana/alloy/internal/component/common/config/aws" + "github.com/grafana/alloy/internal/runtime/componenttest" "github.com/grafana/alloy/internal/util" "github.com/grafana/alloy/syntax" "github.com/grafana/alloy/syntax/alloytypes" From 48fd0801e59531da392ff9ece6cee36410b339b7 Mon Sep 17 00:00:00 2001 From: hainenber Date: Tue, 9 Jul 2024 20:47:51 +0700 Subject: [PATCH 11/11] chore(build): reconcile go.sum Signed-off-by: hainenber --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index c895ed7aba..e49fd78117 100644 --- a/go.sum +++ b/go.sum @@ -377,8 +377,8 @@ github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.20.0 h1:MaTOKZE github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.20.0/go.mod h1:BRuiq4shgrokCvNWSXVHz1hhH5sNSLW0ZruTV0jiNMQ= github.com/aws/aws-sdk-go-v2/service/s3 v1.49.0 h1:VfU15izXQjz4m9y1DkbY79iylIiuPwWtrram4cSpWEI= github.com/aws/aws-sdk-go-v2/service/s3 v1.49.0/go.mod h1:1o/W6JFUuREj2ExoQ21vHJgO7wakvjhol91M9eknFgs= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.27.0 h1:64jRTsqBcIqlA4N7ZFYy+ysGPE7Rz/nJgU2fwv2cymk= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.27.0/go.mod h1:JsJDZFHwLGZu6dxhV9EV1gJrMnCeE4GEXubSZA59xdA= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0 h1:dPCRgAL4WD9tSMaDglRNGOiAtSTjkwNiUW5GDpWFfHA= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.0/go.mod h1:4Ae1NCLK6ghmjzd45Tc33GgCKhUWD2ORAlULtMO1Cbs= github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.29.10 h1:MNECBvcQiQxwBsVwZKShXRc1mrYawtj39jIxPXWeAQY= github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.29.10/go.mod h1:/tT3hQYAj8aGFmy4hYqeR8I5R1uFVaIlHwj6jNU+ohs= github.com/aws/aws-sdk-go-v2/service/shield v1.24.0 h1:DasZw37v6ciRecoPkslCl8rHmoPfzfwpnR48pxWJaGg=