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

AWSPrometheusRemoteWriteExporter - Add SDK and system information to User-Agent header #3317

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
35 changes: 24 additions & 11 deletions exporter/awsprometheusremotewriteexporter/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ 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
transport http.RoundTripper
signer *v4.Signer
region string
service string
runtimeInfo string
}

func (si *signingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
Expand All @@ -57,6 +58,17 @@ func (si *signingRoundTripper) RoundTrip(req *http.Request) (*http.Response, err

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

// Add the runtime information to the User-Agent header of the request
ua := req2.Header.Get("User-Agent")
if len(ua) > 0 {
ua = ua + " " + si.runtimeInfo
} else {
ua = si.runtimeInfo
}
req2.Header.Set("User-Agent", ua)

// Sign the request
_, err = si.signer.Sign(req2, body, si.service, si.region, time.Now())
if err != nil {
return nil, err
Expand All @@ -71,7 +83,7 @@ func (si *signingRoundTripper) RoundTrip(req *http.Request) (*http.Response, err
return resp, err
}

func newSigningRoundTripper(cfg *Config, next http.RoundTripper) (http.RoundTripper, error) {
func newSigningRoundTripper(cfg *Config, next http.RoundTripper, runtimeInfo string) (http.RoundTripper, error) {
auth := cfg.AuthConfig
if auth.Region == "" {
region, err := parseEndpointRegion(cfg.Config.HTTPClientSettings.Endpoint)
Expand All @@ -88,7 +100,7 @@ func newSigningRoundTripper(cfg *Config, next http.RoundTripper) (http.RoundTrip
if err != nil {
return next, err
}
return newSigningRoundTripperWithCredentials(auth, creds, next)
return newSigningRoundTripperWithCredentials(auth, creds, next, runtimeInfo)
}

func getCredsFromConfig(auth AuthConfig) (*credentials.Credentials, error) {
Expand Down Expand Up @@ -122,16 +134,17 @@ func parseEndpointRegion(endpoint string) (region string, err error) {
return p[1], nil
}

func newSigningRoundTripperWithCredentials(auth AuthConfig, creds *credentials.Credentials, next http.RoundTripper) (http.RoundTripper, error) {
func newSigningRoundTripperWithCredentials(auth AuthConfig, creds *credentials.Credentials, next http.RoundTripper, runtimeInfo string) (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,
transport: next,
signer: signer,
region: auth.Region,
service: auth.Service,
runtimeInfo: runtimeInfo,
}
return &rt, nil
}
Expand Down
16 changes: 12 additions & 4 deletions exporter/awsprometheusremotewriteexporter/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@ package awsprometheusremotewriteexporter

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm not mistaken we don't have a test verifying the user agent (it would be some sort of fuzzy match I guess)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have modified the tests to add verification of the user-agent header

import (
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"runtime"
"strings"
"sync"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
"github.com/stretchr/testify/assert"
Expand All @@ -32,6 +35,8 @@ import (
"go.opentelemetry.io/collector/config/configtls"
)

var sdkInformation string = fmt.Sprintf("%s/%s (%s; %s; %s)", aws.SDKName, aws.SDKVersion, runtime.Version(), runtime.GOOS, runtime.GOARCH)

func TestRequestSignature(t *testing.T) {
// Some form of AWS credentials must be set up for tests to succeed
awsCreds := fetchMockCredentials()
Expand All @@ -40,6 +45,7 @@ func TestRequestSignature(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := v4.GetSignedRequestSignature(r)
assert.NoError(t, err)
assert.Equal(t, sdkInformation, r.Header.Get("User-Agent"))
w.WriteHeader(200)
}))
defer server.Close()
Expand All @@ -54,7 +60,7 @@ func TestRequestSignature(t *testing.T) {
WriteBufferSize: 0,
Timeout: 0,
CustomRoundTripper: func(next http.RoundTripper) (http.RoundTripper, error) {
return newSigningRoundTripperWithCredentials(authConfig, awsCreds, next)
return newSigningRoundTripperWithCredentials(authConfig, awsCreds, next, sdkInformation)
},
}
client, _ := setting.ToClient()
Expand Down Expand Up @@ -85,6 +91,7 @@ func TestLeakingBody(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := v4.GetSignedRequestSignature(r)
assert.NoError(t, err)
assert.Equal(t, sdkInformation, r.Header.Get("User-Agent"))
w.WriteHeader(200)
}))
defer server.Close()
Expand All @@ -99,7 +106,7 @@ func TestLeakingBody(t *testing.T) {
WriteBufferSize: 0,
Timeout: 0,
CustomRoundTripper: func(next http.RoundTripper) (http.RoundTripper, error) {
return newSigningRoundTripperWithCredentials(authConfig, awsCreds, next)
return newSigningRoundTripperWithCredentials(authConfig, awsCreds, next, sdkInformation)
},
}
client, _ := setting.ToClient()
Expand Down Expand Up @@ -177,12 +184,13 @@ func TestRoundTrip(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := v4.GetSignedRequestSignature(r)
assert.NoError(t, err)
assert.Equal(t, sdkInformation, r.Header.Get("User-Agent"))
w.WriteHeader(200)
}))
defer server.Close()
serverURL, _ := url.Parse(server.URL)
authConfig := AuthConfig{Region: "region", Service: "service"}
rt, err := newSigningRoundTripperWithCredentials(authConfig, awsCreds, tt.rt)
rt, err := newSigningRoundTripperWithCredentials(authConfig, awsCreds, tt.rt, sdkInformation)
assert.NoError(t, err)
req, err := http.NewRequest("POST", serverURL.String(), strings.NewReader(""))
assert.NoError(t, err)
Expand Down Expand Up @@ -239,7 +247,7 @@ func TestCreateSigningRoundTripperWithCredentials(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rtp, err := newSigningRoundTripperWithCredentials(tt.authConfig, tt.creds, tt.roundTripper)
rtp, err := newSigningRoundTripperWithCredentials(tt.authConfig, tt.creds, tt.roundTripper, sdkInformation)
if tt.returnError {
assert.Error(t, err)
return
Expand Down
12 changes: 11 additions & 1 deletion exporter/awsprometheusremotewriteexporter/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ package awsprometheusremotewriteexporter

import (
"context"
"fmt"
"net/http"
"os"
"runtime"
"strings"

"github.com/aws/aws-sdk-go/aws"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config"
prw "go.opentelemetry.io/collector/exporter/prometheusremotewriteexporter"
Expand Down Expand Up @@ -56,7 +61,12 @@ func (af *awsFactory) CreateDefaultConfig() config.Exporter {

cfg.ExporterSettings = config.NewExporterSettings(config.NewID(typeStr))
cfg.HTTPClientSettings.CustomRoundTripper = func(next http.RoundTripper) (http.RoundTripper, error) {
return newSigningRoundTripper(cfg, next)
extras := []string{runtime.Version(), runtime.GOOS, runtime.GOARCH}
if v := os.Getenv("AWS_EXECUTION_ENV"); v != "" {
extras = append(extras, v)
}
runtimeInfo := fmt.Sprintf("%s/%s (%s)", aws.SDKName, aws.SDKVersion, strings.Join(extras, "; "))
return newSigningRoundTripper(cfg, next, runtimeInfo)
}

return cfg
Expand Down