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

Adding 'Headers' field on HTTPClientConfig #326

Closed
wants to merge 3 commits into from
Closed
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
64 changes: 64 additions & 0 deletions config/http_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,32 @@ import (
"gopkg.in/yaml.v2"
)

var (
reservedHeaders = map[string]struct{}{
// NOTE: authorization is checked specially,
// see HTTPClientConfig.Validate.
// "authorization": {},
"host": {},
"content-encoding": {},
"content-length": {},
"content-type": {},
"user-agent": {},
"connection": {},
"keep-alive": {},
"proxy-authenticate": {},
"proxy-authorization": {},
"www-authenticate": {},
"accept-encoding": {},
"x-prometheus-remote-write-version": {},
"x-prometheus-remote-read-version": {},

// Added by SigV4.
"x-amz-date": {},
"x-amz-security-token": {},
"x-amz-content-sha256": {},
}
)

// DefaultHTTPClientConfig is the default HTTP client configuration.
var DefaultHTTPClientConfig = HTTPClientConfig{
FollowRedirects: true,
Expand Down Expand Up @@ -177,6 +203,8 @@ type HTTPClientConfig struct {
// The omitempty flag is not set, because it would be hidden from the
// marshalled configuration when set to false.
FollowRedirects bool `yaml:"follow_redirects" json:"follow_redirects"`
// Extra Headers to be added on the Http Request
Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
}

// SetDirectory joins any relative file paths with dir.
Expand Down Expand Up @@ -250,6 +278,18 @@ func (c *HTTPClientConfig) Validate() error {
return fmt.Errorf("at most one of oauth2 client_secret & client_secret_file must be configured")
}
}

if c.Headers != nil {
for header := range c.Headers {
if strings.ToLower(header) == "authorization" {
return fmt.Errorf("authorization header must be changed via the basic_auth, authorization, oauth2, or sigv4 parameter")
}
if _, ok := reservedHeaders[strings.ToLower(header)]; ok {
return fmt.Errorf("%s is a reserved header. It must not be changed", header)
}
}
}

return nil
}

Expand Down Expand Up @@ -418,6 +458,10 @@ func NewRoundTripperFromConfig(cfg HTTPClientConfig, name string, optFuncs ...HT
rt = NewBasicAuthRoundTripper(cfg.BasicAuth.Username, cfg.BasicAuth.Password, cfg.BasicAuth.PasswordFile, rt)
}

if cfg.Headers != nil && len(cfg.Headers) > 0 {
rt = NewHeadersRoundTripper(cfg.Headers, rt)
}

if cfg.OAuth2 != nil {
rt = NewOAuth2RoundTripper(cfg.OAuth2, rt)
}
Expand All @@ -438,6 +482,26 @@ func NewRoundTripperFromConfig(cfg HTTPClientConfig, name string, optFuncs ...HT
return NewTLSRoundTripper(tlsConfig, cfg.TLSConfig.CAFile, newRT)
}

type headersRoundTripper struct {
headers map[string]string
rt http.RoundTripper
}

func (rt *headersRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
req = cloneRequest(req)
if len(rt.headers) > 0 {
for key, value := range rt.headers {
req.Header.Set(key, value)
}
}
return rt.rt.RoundTrip(req)
}

// NewHeadersRoundTripper adds the provided headers to a request
func NewHeadersRoundTripper(headers map[string]string, rt http.RoundTripper) http.RoundTripper {
return &headersRoundTripper{headers, rt}
}

type authorizationCredentialsRoundTripper struct {
authType string
authCredentials Secret
Expand Down
65 changes: 62 additions & 3 deletions config/http_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ const (
ExpectedPassword = "42"
)

var expectedHeaders = map[string]string{"Header1": "Value1", "Header2": "Value2"}

var invalidHTTPClientConfigs = []struct {
httpClientConfigFile string
errMsg string
Expand Down Expand Up @@ -197,6 +199,26 @@ func TestNewClientFromConfig(t *testing.T) {
fmt.Fprint(w, ExpectedMessage)
}
},
},
{
clientConfig: HTTPClientConfig{
Headers: expectedHeaders,
TLSConfig: TLSConfig{
CAFile: TLSCAChainPath,
CertFile: ClientCertificatePath,
KeyFile: ClientKeyNoPassPath,
ServerName: "",
InsecureSkipVerify: false},
},
handler: func(w http.ResponseWriter, r *http.Request) {
for key, value := range expectedHeaders {
if r.Header.Get(key) != value {
fmt.Fprintf(w, "The received Headers (%s) does not contain all expected headers (%s).", r.Header, expectedHeaders)
return
}
}
fmt.Fprint(w, ExpectedMessage)
},
}, {
clientConfig: HTTPClientConfig{
BearerTokenFile: BearerTokenFile,
Expand Down Expand Up @@ -1058,9 +1080,46 @@ func TestValidateHTTPConfig(t *testing.T) {
if err != nil {
t.Errorf("Error loading HTTP client config: %v", err)
}
err = cfg.Validate()
if err != nil {
t.Fatalf("Error validating %s: %s", "testdata/http.conf.good.yml", err)

var testCases = []struct {
name string
clientConfig HTTPClientConfig
expectedErrorMsg string
}{
{
name: "Valid Config",
clientConfig: *cfg,
expectedErrorMsg: "",
},
{
name: "Invalid Auth Header Config",
clientConfig: HTTPClientConfig{
Headers: map[string]string{"Authorization": "auth"},
},
expectedErrorMsg: "authorization header must be changed via the basic_auth, authorization, oauth2, or sigv4 parameter",
},
{
name: "Invalid User Agent Config",
clientConfig: HTTPClientConfig{
Headers: map[string]string{"uSer-Agent": "ua"},
},
expectedErrorMsg: "uSer-Agent is a reserved header. It must not be changed",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
errMsg := ""
err = tc.clientConfig.Validate()

if err != nil {
errMsg = err.Error()
}

if errMsg != tc.expectedErrorMsg {
t.Fatalf("Error validating %s: %s", tc.clientConfig, err)
}
})
}
}

Expand Down