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

Add support to assume an AWS role and renew expired credentials #653

Merged
merged 1 commit into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ elasticsearch_exporter --help
```

| Argument | Introduced in Version | Description | Default |
| -------- | --------------------- | ----------- | ----------- |
| ----------------------- | --------------------- | ----------- | ----------- |
| es.uri | 1.0.2 | Address (host and port) of the Elasticsearch node we should connect to. This could be a local node (`localhost:9200`, for instance), or the address of a remote Elasticsearch server. When basic auth is needed, specify as: `<proto>://<user>:<password>@<host>:<port>`. E.G., `http://admin:pass@localhost:9200`. Special characters in the user credentials need to be URL-encoded. | <http://localhost:9200> |
| es.all | 1.0.2 | If true, query stats for all nodes in the cluster, rather than just the node we connect to. | false |
| es.cluster_settings | 1.1.0rc1 | If true, query stats for cluster settings. | false |
Expand All @@ -70,6 +70,7 @@ elasticsearch_exporter --help
| web.listen-address | 1.0.2 | Address to listen on for web interface and telemetry. | :9114 |
| web.telemetry-path | 1.0.2 | Path under which to expose metrics. | /metrics |
| aws.region | 1.5.0 | Region for AWS elasticsearch | |
| aws.role-arn | 1.6.0 | Role ARN of an IAM role to assume. | |
| version | 1.0.2 | Show version info on stdout and exit. | |

Commandline parameters start with a single `-` for versions less than `1.1.0rc1`.
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ go 1.19
require (
github.com/aws/aws-sdk-go-v2 v1.17.3
github.com/aws/aws-sdk-go-v2/config v1.18.7
github.com/aws/aws-sdk-go-v2/credentials v1.13.7
github.com/aws/aws-sdk-go-v2/service/sts v1.17.7
github.com/blang/semver/v4 v4.0.0
github.com/go-kit/log v0.2.1
github.com/imdario/mergo v0.3.13
Expand All @@ -17,15 +19,13 @@ require (
require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.7 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.11.28 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.17.7 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
Expand Down
5 changes: 4 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ func main() {
awsRegion = kingpin.Flag("aws.region",
"Region for AWS elasticsearch").
steveteuber marked this conversation as resolved.
Show resolved Hide resolved
Default("").String()
awsRoleArn = kingpin.Flag("aws.role-arn",
"Role ARN of an IAM role to assume.").
Default("").String()
)

kingpin.Version(version.Print(name))
Expand Down Expand Up @@ -174,7 +177,7 @@ func main() {
}

if *awsRegion != "" {
httpClient.Transport, err = roundtripper.NewAWSSigningTransport(httpTransport, *awsRegion, logger)
httpClient.Transport, err = roundtripper.NewAWSSigningTransport(httpTransport, *awsRegion, *awsRoleArn, logger)
if err != nil {
_ = level.Error(logger).Log("msg", "failed to create AWS transport", "err", err)
os.Exit(1)
Expand Down
32 changes: 24 additions & 8 deletions pkg/roundtripper/roundtripper.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (
"github.com/aws/aws-sdk-go-v2/aws"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
)
Expand All @@ -36,21 +38,28 @@ const (

type AWSSigningTransport struct {
t http.RoundTripper
creds aws.Credentials
creds aws.CredentialsProvider
region string
log log.Logger
}

func NewAWSSigningTransport(transport http.RoundTripper, region string, log log.Logger) (*AWSSigningTransport, error) {
func NewAWSSigningTransport(transport http.RoundTripper, region string, roleArn string, log log.Logger) (*AWSSigningTransport, error) {
cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(region))
if err != nil {
_ = level.Error(log).Log("msg", "fail to load aws default config", "err", err)
_ = level.Error(log).Log("msg", "failed to load aws default config", "err", err)
return nil, err
}

creds, err := cfg.Credentials.Retrieve(context.Background())
if roleArn != "" {
cfg.Credentials = stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg), roleArn)
}

creds := aws.NewCredentialsCache(cfg.Credentials)
steveteuber marked this conversation as resolved.
Show resolved Hide resolved
// Run a single fetch credentials operation to ensure that the credentials
// are valid before returning the transport.
_, err = cfg.Credentials.Retrieve(context.Background())
if err != nil {
_ = level.Error(log).Log("msg", "fail to retrive aws credentials", "err", err)
_ = level.Error(log).Log("msg", "failed to retrive aws credentials", "err", err)
return nil, err
}

Expand All @@ -66,13 +75,20 @@ func (a *AWSSigningTransport) RoundTrip(req *http.Request) (*http.Response, erro
signer := v4.NewSigner()
payloadHash, newReader, err := hashPayload(req.Body)
if err != nil {
_ = level.Error(a.log).Log("msg", "fail to hash request body", "err", err)
_ = level.Error(a.log).Log("msg", "failed to hash request body", "err", err)
return nil, err
}
req.Body = newReader
err = signer.SignHTTP(context.Background(), a.creds, req, payloadHash, service, a.region, time.Now())

creds, err := a.creds.Retrieve(context.Background())
if err != nil {
_ = level.Error(a.log).Log("msg", "failed to retrieve aws credentials", "err", err)
return nil, err
}

err = signer.SignHTTP(context.Background(), creds, req, payloadHash, service, a.region, time.Now())
if err != nil {
_ = level.Error(a.log).Log("msg", "fail to sign request body", "err", err)
_ = level.Error(a.log).Log("msg", "failed to sign request body", "err", err)
return nil, err
}
return a.t.RoundTrip(req)
Expand Down