Skip to content

Commit

Permalink
Use the correct default for aws_auth.service for the AWS PRW exporter (
Browse files Browse the repository at this point in the history
…#3161)

* Use the correct default for aws_auth.region for the AWS PRW exporter

"aps" is currently the only value that is acceptable for this field. We should have never provided it as a configuration setting but we can't remove and break the existing users. This change is setting it to "aps" by default.

A follow up change will be provided to automatically parse the region from the workspace remote write endpoint.

* Reorder

* Improve godoc
  • Loading branch information
rakyll authored Apr 23, 2021
1 parent d37dc81 commit 17399fa
Show file tree
Hide file tree
Showing 7 changed files with 34 additions and 62 deletions.
1 change: 1 addition & 0 deletions exporter/awsprometheusremotewriteexporter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ exporters:
endpoint: "https://aps-workspaces.us-east-1.amazonaws.com/workspaces/ws-XXX/api/v1/remote_write"
aws_auth:
region: "us-east-1" # need to match workspace region
service: "aps"
role_arn: "arn:aws:iam::123456789012:role/aws-service-role/access"
ca_file: "/var/lib/mycert.pem"
write_buffer_size: 524288
Expand Down
40 changes: 15 additions & 25 deletions exporter/awsprometheusremotewriteexporter/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package awsprometheusremotewriteexporter provides a Prometheus Remote Write Exporter with AWS Sigv4 authentication
package awsprometheusremotewriteexporter

import (
Expand All @@ -30,40 +29,37 @@ import (
v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
)

// signingRoundTripper is a Custom RoundTripper that performs AWS Sig V4
const defaultAMPSigV4Service = "aps"

// signingRoundTripper is a Custom RoundTripper that performs AWS Sig V4.
type signingRoundTripper struct {
transport http.RoundTripper
signer *v4.Signer
region string
service string
}

// RoundTrip signs each outgoing request
func (si *signingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
reqBody, err := req.GetBody()
if err != nil {
return nil, err
}

// Get the body
content, err := ioutil.ReadAll(reqBody)
reqBody.Close()
if err != nil {
return nil, err
}

body := bytes.NewReader(content)

// Clone request to ensure thread safety
// Clone request to ensure thread safety.
req2 := cloneRequest(req)

// Sign the request
_, err = si.signer.Sign(req2, body, si.service, si.region, time.Now())
if err != nil {
return nil, err
}

// Send the request to Prometheus Remote Write Backend
// Send the request to Prometheus Remote Write Backend.
resp, err := si.transport.RoundTrip(req2)
if err != nil {
return nil, err
Expand All @@ -73,14 +69,20 @@ func (si *signingRoundTripper) RoundTrip(req *http.Request) (*http.Response, err
}

func newSigningRoundTripper(auth AuthConfig, next http.RoundTripper) (http.RoundTripper, error) {
if auth.Region == "" {
// TODO(jbd): Automatically parse the region from the workspace.
return next, nil
}
if auth.Service == "" {
auth.Service = defaultAMPSigV4Service
}

creds := getCredsFromConfig(auth)
return createSigningRoundTripperWithCredentials(auth, creds, next)
return newSigningRoundTripperWithCredentials(auth, creds, next)
}

func getCredsFromConfig(auth AuthConfig) *credentials.Credentials {

// Session Must ensure the Session is valid
// TODO: Don't panic, handle the error from NewSessionWithOptions.
sess := session.Must(session.NewSessionWithOptions(session.Options{
Config: aws.Config{Region: aws.String(auth.Region)},
}))
Expand All @@ -98,32 +100,20 @@ func getCredsFromConfig(auth AuthConfig) *credentials.Credentials {
return creds
}

func createSigningRoundTripperWithCredentials(auth AuthConfig, creds *credentials.Credentials, next http.RoundTripper) (http.RoundTripper, error) {
if !isValidAuth(auth) {
return next, nil
}

func newSigningRoundTripperWithCredentials(auth AuthConfig, creds *credentials.Credentials, next http.RoundTripper) (http.RoundTripper, error) {
if creds == nil {
return nil, errors.New("no AWS credentials exist")
}

signer := v4.NewSigner(creds)

rt := signingRoundTripper{
transport: next,
signer: signer,
region: auth.Region,
service: auth.Service,
}

// return a RoundTripper
return &rt, nil
}

func isValidAuth(params AuthConfig) bool {
return params.Region != "" && params.Service != ""
}

func cloneRequest(r *http.Request) *http.Request {
// shallow copy of the struct
r2 := new(http.Request)
Expand Down
19 changes: 8 additions & 11 deletions exporter/awsprometheusremotewriteexporter/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package awsprometheusremotewriteexporter provides a Prometheus Remote Write Exporter with AWS Sigv4 authentication
package awsprometheusremotewriteexporter

import (
Expand Down Expand Up @@ -55,7 +54,7 @@ func TestRequestSignature(t *testing.T) {
WriteBufferSize: 0,
Timeout: 0,
CustomRoundTripper: func(next http.RoundTripper) (http.RoundTripper, error) {
return createSigningRoundTripperWithCredentials(authConfig, awsCreds, next)
return newSigningRoundTripperWithCredentials(authConfig, awsCreds, next)
},
}
client, _ := setting.ToClient()
Expand Down Expand Up @@ -100,7 +99,7 @@ func TestLeakingBody(t *testing.T) {
WriteBufferSize: 0,
Timeout: 0,
CustomRoundTripper: func(next http.RoundTripper) (http.RoundTripper, error) {
return createSigningRoundTripperWithCredentials(authConfig, awsCreds, next)
return newSigningRoundTripperWithCredentials(authConfig, awsCreds, next)
},
}
client, _ := setting.ToClient()
Expand Down Expand Up @@ -184,7 +183,7 @@ func TestRoundTrip(t *testing.T) {
defer server.Close()
serverURL, _ := url.Parse(server.URL)
authConfig := AuthConfig{Region: "region", Service: "service"}
rt, err := createSigningRoundTripperWithCredentials(authConfig, awsCreds, tt.rt)
rt, err := newSigningRoundTripperWithCredentials(authConfig, awsCreds, tt.rt)
assert.NoError(t, err)
req, err := http.NewRequest("POST", serverURL.String(), strings.NewReader(""))
assert.NoError(t, err)
Expand All @@ -201,7 +200,6 @@ func TestRoundTrip(t *testing.T) {
}

func TestCreateSigningRoundTripperWithCredentials(t *testing.T) {

defaultRoundTripper := (http.RoundTripper)(http.DefaultTransport.(*http.Transport).Clone())

// Some form of AWS credentials must be set up for tests to succeed
Expand Down Expand Up @@ -240,10 +238,9 @@ func TestCreateSigningRoundTripperWithCredentials(t *testing.T) {
true,
},
}
// run tests
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rtp, err := createSigningRoundTripperWithCredentials(tt.authConfig, tt.creds, tt.roundTripper)
rtp, err := newSigningRoundTripperWithCredentials(tt.authConfig, tt.creds, tt.roundTripper)
if tt.returnError {
assert.Error(t, err)
return
Expand All @@ -253,8 +250,6 @@ func TestCreateSigningRoundTripperWithCredentials(t *testing.T) {
sRtp := rtp.(*signingRoundTripper)
assert.Equal(t, sRtp.transport, tt.roundTripper)
assert.Equal(t, tt.authConfig.Service, sRtp.service)
} else {
assert.Equal(t, rtp, tt.roundTripper)
}
})
}
Expand Down Expand Up @@ -294,7 +289,9 @@ func TestCloneRequest(t *testing.T) {
}

func fetchMockCredentials() *credentials.Credentials {
return credentials.NewStaticCredentials("MOCK_AWS_ACCESS_KEY",
return credentials.NewStaticCredentials(
"MOCK_AWS_ACCESS_KEY",
"MOCK_AWS_SECRET_ACCESS_KEY",
"MOCK_TOKEN")
"MOCK_TOKEN",
)
}
15 changes: 8 additions & 7 deletions exporter/awsprometheusremotewriteexporter/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package awsprometheusremotewriteexporter provides a Prometheus Remote Write Exporter with AWS Sigv4 authentication
package awsprometheusremotewriteexporter

import (
Expand All @@ -21,19 +20,21 @@ import (

// Config defines configuration for Remote Write exporter.
type Config struct {
// Config represents the Prometheus Remote Write Exporter configuration
// Config represents the Prometheus Remote Write Exporter configuration.
prw.Config `mapstructure:",squash"`

// AuthConfig represents the AWS Sig V4 configuration options
// AuthConfig represents the AWS SigV4 configuration options.
AuthConfig AuthConfig `mapstructure:"aws_auth"`
}

// AuthConfig defines AWS authentication configurations for SigningRoundTripper
// AuthConfig defines AWS authentication configurations for SigningRoundTripper.
type AuthConfig struct {
// Region is the AWS region for AWS Sig v4.
// Region is the AWS region for AWS SigV4.
Region string `mapstructure:"region"`
// Service is the service name for AWS Sig v4

// Service is the AWS service for AWS SigV4, this is by default "aps". Optional.
Service string `mapstructure:"service"`
// Amazon Resource Name (ARN) of a role to assume

// Amazon Resource Name (ARN) of a role to assume. Optional.
RoleArn string `mapstructure:"role_arn"`
}
3 changes: 1 addition & 2 deletions exporter/awsprometheusremotewriteexporter/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package awsprometheusremotewriteexporter provides a Prometheus Remote Write Exporter with AWS Sigv4 authentication
package awsprometheusremotewriteexporter

import (
Expand All @@ -31,7 +30,7 @@ import (
prw "go.opentelemetry.io/collector/exporter/prometheusremotewriteexporter"
)

// TestLoadConfig checks whether yaml configuration can be loaded correctly
// TestLoadConfig checks whether yaml configuration can be loaded correctly.
func TestLoadConfig(t *testing.T) {
factories, err := componenttest.NopFactories()
assert.NoError(t, err)
Expand Down
12 changes: 1 addition & 11 deletions exporter/awsprometheusremotewriteexporter/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package awsprometheusremotewriteexporter

import (
"context"
"errors"
"net/http"

"go.opentelemetry.io/collector/component"
Expand Down Expand Up @@ -50,25 +49,16 @@ func (af *awsFactory) CreateDefaultConfig() config.Exporter {
Config: *af.ExporterFactory.CreateDefaultConfig().(*prw.Config),
AuthConfig: AuthConfig{
Region: "",
Service: "",
Service: defaultAMPSigV4Service,
RoleArn: "",
},
}

cfg.TypeVal = typeStr
cfg.NameVal = typeStr

cfg.HTTPClientSettings.CustomRoundTripper = func(next http.RoundTripper) (http.RoundTripper, error) {
if !isAuthConfigValid(cfg.AuthConfig) {
return nil, errors.New("invalid authentication configuration")
}

return newSigningRoundTripper(cfg.AuthConfig, next)
}

return cfg
}

func isAuthConfigValid(params AuthConfig) bool {
return !(params.Region != "" && params.Service == "" || params.Region == "" && params.Service != "")
}
6 changes: 0 additions & 6 deletions exporter/awsprometheusremotewriteexporter/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package awsprometheusremotewriteexporter provides a Prometheus Remote Write Exporter with AWS Sigv4 authentication
package awsprometheusremotewriteexporter

import (
Expand Down Expand Up @@ -87,11 +86,6 @@ func TestCreateMetricsExporter(t *testing.T) {
component.ExporterCreateParams{Logger: zap.NewNop()},
false,
},
{"invalid_auth_case",
invalidConfigWithAuth,
component.ExporterCreateParams{Logger: zap.NewNop()},
true,
},
{"invalid_config_case",
invalidConfig,
component.ExporterCreateParams{Logger: zap.NewNop()},
Expand Down

0 comments on commit 17399fa

Please sign in to comment.