diff --git a/exporter/awsprometheusremotewriteexporter/README.md b/exporter/awsprometheusremotewriteexporter/README.md index d08a921a74b3..b33c0030809d 100644 --- a/exporter/awsprometheusremotewriteexporter/README.md +++ b/exporter/awsprometheusremotewriteexporter/README.md @@ -8,7 +8,7 @@ of the AWS SDK for Go. Note: this exporter imports and uses the [Prometheus remote write exporter](https://github.com/open-telemetry/opentelemetry-collector/tree/main/exporter/prometheusremotewriteexporter) from upstream, and simply wraps it in Sigv4 authentication logic -Same as the Prometheus remote write exporter, this exporter checks the temporality and the type of each incoming metric +Same as the Prometheus remote write exporter, this exporter checks the temporality and the type of each incoming metric and only exports the following combination: - Int64 or Double type with any temporality @@ -16,7 +16,7 @@ and only exports the following combination: ## Configuration The following settings are required: -- `endpoint`: protocol:host:port to which the exporter is going to send traces or metrics, using the HTTP/HTTPS protocol. +- `endpoint`: protocol:host:port to which the exporter is going to send traces or metrics, using the HTTP/HTTPS protocol. The following settings can be optionally configured: - `namespace`: prefix attached to each exported metric name. @@ -31,8 +31,8 @@ The following settings can be optionally configured: - `aws_auth`: specify if each request should be signed with AWS Sig v4. The following settings must be configured: - `region`: region of the AWS service being exported to. - `service`: AWS service being exported to. - - + - `role_arn`: Amazon Resource Name of the role to assume. + #### Examples: Simplest configuration: diff --git a/exporter/awsprometheusremotewriteexporter/auth.go b/exporter/awsprometheusremotewriteexporter/auth.go index f1d8092d9555..70535708abcb 100644 --- a/exporter/awsprometheusremotewriteexporter/auth.go +++ b/exporter/awsprometheusremotewriteexporter/auth.go @@ -20,10 +20,12 @@ import ( "errors" "io/ioutil" "net/http" + "strconv" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/credentials/stscreds" "github.com/aws/aws-sdk-go/aws/session" v4 "github.com/aws/aws-sdk-go/aws/signer/v4" ) @@ -70,23 +72,31 @@ func (si *signingRoundTripper) RoundTrip(req *http.Request) (*http.Response, err } func newSigningRoundTripper(auth AuthConfig, next http.RoundTripper) (http.RoundTripper, error) { - sess, err := session.NewSession(&aws.Config{ - Region: aws.String(auth.Region)}, - ) - if err != nil { - return nil, err - } - - if _, err = sess.Config.Credentials.Get(); err != nil { - return nil, err - } - - // Get Credentials, either from ./aws or from environmental variables - creds := sess.Config.Credentials + creds := getCredsFromConfig(auth) return createSigningRoundTripperWithCredentials(auth, creds, next) } +func getCredsFromConfig(auth AuthConfig) *credentials.Credentials { + + // Session Must ensure the Session is valid + sess := session.Must(session.NewSessionWithOptions(session.Options{ + Config: aws.Config{Region: aws.String(auth.Region)}, + })) + + var creds *credentials.Credentials + if auth.RoleArn != "" { + // Get credentials from an assumeRole API call + creds = stscreds.NewCredentials(sess, auth.RoleArn, func(p *stscreds.AssumeRoleProvider) { + p.RoleSessionName = "aws-otel-collector-" + strconv.FormatInt(time.Now().Unix(), 10) + }) + } else { + // Get Credentials, either from ./aws or from environmental variables + creds = sess.Config.Credentials + } + return creds +} + func createSigningRoundTripperWithCredentials(auth AuthConfig, creds *credentials.Credentials, next http.RoundTripper) (http.RoundTripper, error) { if !isValidAuth(auth) { return next, nil diff --git a/exporter/awsprometheusremotewriteexporter/auth_test.go b/exporter/awsprometheusremotewriteexporter/auth_test.go index e93c617fbff2..7a0c7ca5f7af 100644 --- a/exporter/awsprometheusremotewriteexporter/auth_test.go +++ b/exporter/awsprometheusremotewriteexporter/auth_test.go @@ -26,6 +26,7 @@ import ( "github.com/aws/aws-sdk-go/aws/credentials" v4 "github.com/aws/aws-sdk-go/aws/signer/v4" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/config/configtls" ) @@ -62,6 +63,31 @@ func TestRequestSignature(t *testing.T) { assert.NoError(t, err) } +func TestGetCredsFromConfig(t *testing.T) { + + tests := []struct { + name string + authConfig AuthConfig + }{ + { + "success_case_without_role", + AuthConfig{Region: "region", Service: "service"}, + }, + { + "success_case_with_role", + AuthConfig{Region: "region", Service: "service", RoleArn: "arn:aws:iam::123456789012:role/IAMRole"}, + }, + } + // run tests + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + creds := getCredsFromConfig(tt.authConfig) + require.NotNil(t, creds) + + }) + } +} + type ErrorRoundTripper struct{} func (ert *ErrorRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { @@ -79,16 +105,19 @@ func TestRoundTrip(t *testing.T) { name string rt http.RoundTripper shouldError bool + authConfig AuthConfig }{ { "valid_round_tripper", defaultRoundTripper, false, + AuthConfig{Region: "region", Service: "service"}, }, { "round_tripper_error", errorRoundTripper, true, + AuthConfig{Region: "region", Service: "service", RoleArn: "arn:aws:iam::123456789012:role/IAMRole"}, }, } diff --git a/exporter/awsprometheusremotewriteexporter/config.go b/exporter/awsprometheusremotewriteexporter/config.go index 59b31b507983..945c3a56cf1d 100644 --- a/exporter/awsprometheusremotewriteexporter/config.go +++ b/exporter/awsprometheusremotewriteexporter/config.go @@ -34,4 +34,6 @@ type AuthConfig struct { Region string `mapstructure:"region"` // Service is the service name for AWS Sig v4 Service string `mapstructure:"service"` + // Amazon Resource Name (ARN) of a role to assume + RoleArn string `mapstructure:"role_arn"` } diff --git a/exporter/awsprometheusremotewriteexporter/config_test.go b/exporter/awsprometheusremotewriteexporter/config_test.go index 329e9a048033..7f2ab2ad428a 100644 --- a/exporter/awsprometheusremotewriteexporter/config_test.go +++ b/exporter/awsprometheusremotewriteexporter/config_test.go @@ -95,6 +95,7 @@ func TestLoadConfig(t *testing.T) { AuthConfig: AuthConfig{ Region: "us-west-2", Service: "service-name", + RoleArn: "arn:aws:iam::123456789012:role/IAMRole", }, } // testing function equality is not supported in Go hence these will be ignored for this test diff --git a/exporter/awsprometheusremotewriteexporter/factory.go b/exporter/awsprometheusremotewriteexporter/factory.go index 969798c005f3..61f0332dd21b 100644 --- a/exporter/awsprometheusremotewriteexporter/factory.go +++ b/exporter/awsprometheusremotewriteexporter/factory.go @@ -51,6 +51,7 @@ func (af *awsFactory) CreateDefaultConfig() configmodels.Exporter { AuthConfig: AuthConfig{ Region: "", Service: "", + RoleArn: "", }, } diff --git a/exporter/awsprometheusremotewriteexporter/testdata/config.yaml b/exporter/awsprometheusremotewriteexporter/testdata/config.yaml index ce46a7a89df7..4c62799e957d 100644 --- a/exporter/awsprometheusremotewriteexporter/testdata/config.yaml +++ b/exporter/awsprometheusremotewriteexporter/testdata/config.yaml @@ -1,9 +1,9 @@ receivers: nop: - + processors: nop: - + exporters: awsprometheusremotewrite: awsprometheusremotewrite/2: @@ -26,6 +26,7 @@ exporters: aws_auth: region: "us-west-2" service: "service-name" + role_arn: "arn:aws:iam::123456789012:role/IAMRole" external_labels: key1: value1 key2: value2 @@ -35,5 +36,3 @@ service: receivers: [nop] processors: [nop] exporters: [awsprometheusremotewrite] - -