Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(remote/aws): support AWS Secrets Manager as remote component #718

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
97 changes: 97 additions & 0 deletions docs/sources/reference/components/remote.aws.secrets_manager.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
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 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.

{{< admonition type="note" >}}
The polling for changes could incur costs due to frequent API calls.
{{< /admonition >}}

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

```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
`poll_frequency` | `duration` | How often to poll the API for changes. | | no

## Blocks

Hierarchy | Name | Description | Required
----------|------------|----------------------------------------------------|---------
client | [client][] | Additional AWS client configuration options. | 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 the 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
}
}
```
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -318,7 +319,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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
1 change: 1 addition & 0 deletions internal/component/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
86 changes: 86 additions & 0 deletions internal/component/common/config/aws/aws.go
Original file line number Diff line number Diff line change
@@ -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
}