Skip to content

Commit 7a93127

Browse files
author
Julien Pivotto
authored
Merge pull request #272 from roidelapluie/add-auth-credentials
Support authorization_credentials
2 parents 3dae578 + 84ebc5b commit 7a93127

6 files changed

+220
-42
lines changed

config/http_config.go

+94-30
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,21 @@ func (a *BasicAuth) SetDirectory(dir string) {
5252
a.PasswordFile = JoinDir(dir, a.PasswordFile)
5353
}
5454

55+
// Authorization contains HTTP authorization credentials.
56+
type Authorization struct {
57+
Type string `yaml:"type,omitempty"`
58+
Credentials Secret `yaml:"credentials,omitempty"`
59+
CredentialsFile string `yaml:"credentials_file,omitempty"`
60+
}
61+
62+
// SetDirectory joins any relative file paths with dir.
63+
func (a *Authorization) SetDirectory(dir string) {
64+
if a == nil {
65+
return
66+
}
67+
a.CredentialsFile = JoinDir(dir, a.CredentialsFile)
68+
}
69+
5570
// URL is a custom URL type that allows validation at configuration load time.
5671
type URL struct {
5772
*url.URL
@@ -84,14 +99,21 @@ func (u URL) MarshalYAML() (interface{}, error) {
8499
type HTTPClientConfig struct {
85100
// The HTTP basic authentication credentials for the targets.
86101
BasicAuth *BasicAuth `yaml:"basic_auth,omitempty"`
87-
// The bearer token for the targets.
102+
// The HTTP authorization credentials for the targets.
103+
Authorization *Authorization `yaml:"authorization,omitempty"`
104+
// The bearer token for the targets. Deprecated in favour of
105+
// Authorization.Credentials.
88106
BearerToken Secret `yaml:"bearer_token,omitempty"`
89-
// The bearer token file for the targets.
107+
// The bearer token file for the targets. Deprecated in favour of
108+
// Authorization.CredentialsFile.
90109
BearerTokenFile string `yaml:"bearer_token_file,omitempty"`
91110
// HTTP proxy server to use to connect to the targets.
92111
ProxyURL URL `yaml:"proxy_url,omitempty"`
93112
// TLSConfig to use to connect to the targets.
94113
TLSConfig TLSConfig `yaml:"tls_config,omitempty"`
114+
// Used to make sure that the configuration is valid and that BearerToken to
115+
// Authorization.Credentials change has been handled.
116+
valid bool
95117
}
96118

97119
// SetDirectory joins any relative file paths with dir.
@@ -101,12 +123,14 @@ func (c *HTTPClientConfig) SetDirectory(dir string) {
101123
}
102124
c.TLSConfig.SetDirectory(dir)
103125
c.BasicAuth.SetDirectory(dir)
126+
c.Authorization.SetDirectory(dir)
104127
c.BearerTokenFile = JoinDir(dir, c.BearerTokenFile)
105128
}
106129

107130
// Validate validates the HTTPClientConfig to check only one of BearerToken,
108131
// BasicAuth and BearerTokenFile is configured.
109132
func (c *HTTPClientConfig) Validate() error {
133+
// Backwards compatibility with the bearer_token field.
110134
if len(c.BearerToken) > 0 && len(c.BearerTokenFile) > 0 {
111135
return fmt.Errorf("at most one of bearer_token & bearer_token_file must be configured")
112136
}
@@ -116,6 +140,37 @@ func (c *HTTPClientConfig) Validate() error {
116140
if c.BasicAuth != nil && (string(c.BasicAuth.Password) != "" && c.BasicAuth.PasswordFile != "") {
117141
return fmt.Errorf("at most one of basic_auth password & password_file must be configured")
118142
}
143+
if c.Authorization != nil {
144+
if len(c.BearerToken) > 0 || len(c.BearerTokenFile) > 0 {
145+
return fmt.Errorf("authorization is not compatible with bearer_token & bearer_token_file")
146+
}
147+
if string(c.Authorization.Credentials) != "" && c.Authorization.CredentialsFile != "" {
148+
return fmt.Errorf("at most one of authorization credentials & credentials_file must be configured")
149+
}
150+
c.Authorization.Type = strings.TrimSpace(c.Authorization.Type)
151+
if len(c.Authorization.Type) == 0 {
152+
c.Authorization.Type = "Bearer"
153+
}
154+
if strings.ToLower(c.Authorization.Type) == "basic" {
155+
return fmt.Errorf(`authorization type cannot be set to "basic", use "basic_auth" instead`)
156+
}
157+
if c.BasicAuth != nil {
158+
return fmt.Errorf("at most one of basic_auth & authorization must be configured")
159+
}
160+
} else {
161+
if len(c.BearerToken) > 0 {
162+
c.Authorization = &Authorization{Credentials: c.BearerToken}
163+
c.Authorization.Type = "Bearer"
164+
c.BearerToken = ""
165+
}
166+
if len(c.BearerTokenFile) > 0 {
167+
c.Authorization = &Authorization{CredentialsFile: c.BearerTokenFile}
168+
c.Authorization.Type = "Bearer"
169+
c.BearerTokenFile = ""
170+
}
171+
}
172+
173+
c.valid = true
119174
return nil
120175
}
121176

@@ -152,6 +207,12 @@ func NewClientFromConfig(cfg HTTPClientConfig, name string, disableKeepAlives, e
152207
// NewRoundTripperFromConfig returns a new HTTP RoundTripper configured for the
153208
// given config.HTTPClientConfig. The name is used as go-conntrack metric label.
154209
func NewRoundTripperFromConfig(cfg HTTPClientConfig, name string, disableKeepAlives, enableHTTP2 bool) (http.RoundTripper, error) {
210+
// Make sure that the configuration is valid.
211+
if !cfg.valid {
212+
if err := cfg.Validate(); err != nil {
213+
return nil, err
214+
}
215+
}
155216
newRT := func(tlsConfig *tls.Config) (http.RoundTripper, error) {
156217
// The only timeout we care about is the configured scrape timeout.
157218
// It is applied on request. So we leave out any timings here.
@@ -186,12 +247,12 @@ func NewRoundTripperFromConfig(cfg HTTPClientConfig, name string, disableKeepAli
186247
}
187248
}
188249

189-
// If a bearer token is provided, create a round tripper that will set the
250+
// If a authorization_credentials is provided, create a round tripper that will set the
190251
// Authorization header correctly on each request.
191-
if len(cfg.BearerToken) > 0 {
192-
rt = NewBearerAuthRoundTripper(cfg.BearerToken, rt)
193-
} else if len(cfg.BearerTokenFile) > 0 {
194-
rt = NewBearerAuthFileRoundTripper(cfg.BearerTokenFile, rt)
252+
if cfg.Authorization != nil && len(cfg.Authorization.Credentials) > 0 {
253+
rt = NewAuthorizationCredentialsRoundTripper(cfg.Authorization.Type, cfg.Authorization.Credentials, rt)
254+
} else if cfg.Authorization != nil && len(cfg.Authorization.CredentialsFile) > 0 {
255+
rt = NewAuthorizationCredentialsFileRoundTripper(cfg.Authorization.Type, cfg.Authorization.CredentialsFile, rt)
195256
}
196257

197258
if cfg.BasicAuth != nil {
@@ -214,58 +275,61 @@ func NewRoundTripperFromConfig(cfg HTTPClientConfig, name string, disableKeepAli
214275
return newTLSRoundTripper(tlsConfig, cfg.TLSConfig.CAFile, newRT)
215276
}
216277

217-
type bearerAuthRoundTripper struct {
218-
bearerToken Secret
219-
rt http.RoundTripper
278+
type authorizationCredentialsRoundTripper struct {
279+
authType string
280+
authCredentials Secret
281+
rt http.RoundTripper
220282
}
221283

222-
// NewBearerAuthRoundTripper adds the provided bearer token to a request unless the authorization
223-
// header has already been set.
224-
func NewBearerAuthRoundTripper(token Secret, rt http.RoundTripper) http.RoundTripper {
225-
return &bearerAuthRoundTripper{token, rt}
284+
// NewAuthorizationCredentialsRoundTripper adds the provided credentials to a
285+
// request unless the authorization header has already been set.
286+
func NewAuthorizationCredentialsRoundTripper(authType string, authCredentials Secret, rt http.RoundTripper) http.RoundTripper {
287+
return &authorizationCredentialsRoundTripper{authType, authCredentials, rt}
226288
}
227289

228-
func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
290+
func (rt *authorizationCredentialsRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
229291
if len(req.Header.Get("Authorization")) == 0 {
230292
req = cloneRequest(req)
231-
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", string(rt.bearerToken)))
293+
req.Header.Set("Authorization", fmt.Sprintf("%s %s", rt.authType, string(rt.authCredentials)))
232294
}
233295
return rt.rt.RoundTrip(req)
234296
}
235297

236-
func (rt *bearerAuthRoundTripper) CloseIdleConnections() {
298+
func (rt *authorizationCredentialsRoundTripper) CloseIdleConnections() {
237299
if ci, ok := rt.rt.(closeIdler); ok {
238300
ci.CloseIdleConnections()
239301
}
240302
}
241303

242-
type bearerAuthFileRoundTripper struct {
243-
bearerFile string
244-
rt http.RoundTripper
304+
type authorizationCredentialsFileRoundTripper struct {
305+
authType string
306+
authCredentialsFile string
307+
rt http.RoundTripper
245308
}
246309

247-
// NewBearerAuthFileRoundTripper adds the bearer token read from the provided file to a request unless
248-
// the authorization header has already been set. This file is read for every request.
249-
func NewBearerAuthFileRoundTripper(bearerFile string, rt http.RoundTripper) http.RoundTripper {
250-
return &bearerAuthFileRoundTripper{bearerFile, rt}
310+
// NewAuthorizationCredentialsFileRoundTripper adds the authorization
311+
// credentials read from the provided file to a request unless the authorization
312+
// header has already been set. This file is read for every request.
313+
func NewAuthorizationCredentialsFileRoundTripper(authType, authCredentialsFile string, rt http.RoundTripper) http.RoundTripper {
314+
return &authorizationCredentialsFileRoundTripper{authType, authCredentialsFile, rt}
251315
}
252316

253-
func (rt *bearerAuthFileRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
317+
func (rt *authorizationCredentialsFileRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
254318
if len(req.Header.Get("Authorization")) == 0 {
255-
b, err := ioutil.ReadFile(rt.bearerFile)
319+
b, err := ioutil.ReadFile(rt.authCredentialsFile)
256320
if err != nil {
257-
return nil, fmt.Errorf("unable to read bearer token file %s: %s", rt.bearerFile, err)
321+
return nil, fmt.Errorf("unable to read authorization credentials file %s: %s", rt.authCredentialsFile, err)
258322
}
259-
bearerToken := strings.TrimSpace(string(b))
323+
authCredentials := strings.TrimSpace(string(b))
260324

261325
req = cloneRequest(req)
262-
req.Header.Set("Authorization", "Bearer "+bearerToken)
326+
req.Header.Set("Authorization", fmt.Sprintf("%s %s", rt.authType, authCredentials))
263327
}
264328

265329
return rt.rt.RoundTrip(req)
266330
}
267331

268-
func (rt *bearerAuthFileRoundTripper) CloseIdleConnections() {
332+
func (rt *authorizationCredentialsFileRoundTripper) CloseIdleConnections() {
269333
if ci, ok := rt.rt.(closeIdler); ok {
270334
ci.CloseIdleConnections()
271335
}

config/http_config_test.go

+113-12
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,17 @@ const (
4949
MissingCert = "missing/cert.crt"
5050
MissingKey = "missing/secret.key"
5151

52-
ExpectedMessage = "I'm here to serve you!!!"
53-
BearerToken = "theanswertothegreatquestionoflifetheuniverseandeverythingisfortytwo"
54-
BearerTokenFile = "testdata/bearer.token"
55-
MissingBearerTokenFile = "missing/bearer.token"
56-
ExpectedBearer = "Bearer " + BearerToken
57-
ExpectedUsername = "arthurdent"
58-
ExpectedPassword = "42"
52+
ExpectedMessage = "I'm here to serve you!!!"
53+
AuthorizationCredentials = "theanswertothegreatquestionoflifetheuniverseandeverythingisfortytwo"
54+
AuthorizationCredentialsFile = "testdata/bearer.token"
55+
AuthorizationType = "APIKEY"
56+
BearerToken = AuthorizationCredentials
57+
BearerTokenFile = AuthorizationCredentialsFile
58+
MissingBearerTokenFile = "missing/bearer.token"
59+
ExpectedBearer = "Bearer " + BearerToken
60+
ExpectedAuthenticationCredentials = AuthorizationType + " " + BearerToken
61+
ExpectedUsername = "arthurdent"
62+
ExpectedPassword = "42"
5963
)
6064

6165
var invalidHTTPClientConfigs = []struct {
@@ -74,6 +78,22 @@ var invalidHTTPClientConfigs = []struct {
7478
httpClientConfigFile: "testdata/http.conf.basic-auth.too-much.bad.yaml",
7579
errMsg: "at most one of basic_auth password & password_file must be configured",
7680
},
81+
{
82+
httpClientConfigFile: "testdata/http.conf.mix-bearer-and-creds.bad.yaml",
83+
errMsg: "authorization is not compatible with bearer_token & bearer_token_file",
84+
},
85+
{
86+
httpClientConfigFile: "testdata/http.conf.auth-creds-and-file-set.too-much.bad.yaml",
87+
errMsg: "at most one of authorization credentials & credentials_file must be configured",
88+
},
89+
{
90+
httpClientConfigFile: "testdata/http.conf.basic-auth-and-auth-creds.too-much.bad.yaml",
91+
errMsg: "at most one of basic_auth & authorization must be configured",
92+
},
93+
{
94+
httpClientConfigFile: "testdata/http.conf.auth-creds-no-basic.bad.yaml",
95+
errMsg: `authorization type cannot be set to "basic", use "basic_auth" instead`,
96+
},
7797
}
7898

7999
func newTestServer(handler func(w http.ResponseWriter, r *http.Request)) (*httptest.Server, error) {
@@ -170,6 +190,87 @@ func TestNewClientFromConfig(t *testing.T) {
170190
fmt.Fprint(w, ExpectedMessage)
171191
}
172192
},
193+
}, {
194+
clientConfig: HTTPClientConfig{
195+
Authorization: &Authorization{Credentials: BearerToken},
196+
TLSConfig: TLSConfig{
197+
CAFile: TLSCAChainPath,
198+
CertFile: ClientCertificatePath,
199+
KeyFile: ClientKeyNoPassPath,
200+
ServerName: "",
201+
InsecureSkipVerify: false},
202+
},
203+
handler: func(w http.ResponseWriter, r *http.Request) {
204+
bearer := r.Header.Get("Authorization")
205+
if bearer != ExpectedBearer {
206+
fmt.Fprintf(w, "The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
207+
ExpectedBearer, bearer)
208+
} else {
209+
fmt.Fprint(w, ExpectedMessage)
210+
}
211+
},
212+
}, {
213+
clientConfig: HTTPClientConfig{
214+
Authorization: &Authorization{CredentialsFile: AuthorizationCredentialsFile, Type: AuthorizationType},
215+
TLSConfig: TLSConfig{
216+
CAFile: TLSCAChainPath,
217+
CertFile: ClientCertificatePath,
218+
KeyFile: ClientKeyNoPassPath,
219+
ServerName: "",
220+
InsecureSkipVerify: false},
221+
},
222+
handler: func(w http.ResponseWriter, r *http.Request) {
223+
bearer := r.Header.Get("Authorization")
224+
if bearer != ExpectedAuthenticationCredentials {
225+
fmt.Fprintf(w, "The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
226+
ExpectedAuthenticationCredentials, bearer)
227+
} else {
228+
fmt.Fprint(w, ExpectedMessage)
229+
}
230+
},
231+
}, {
232+
clientConfig: HTTPClientConfig{
233+
Authorization: &Authorization{
234+
Credentials: AuthorizationCredentials,
235+
Type: AuthorizationType,
236+
},
237+
TLSConfig: TLSConfig{
238+
CAFile: TLSCAChainPath,
239+
CertFile: ClientCertificatePath,
240+
KeyFile: ClientKeyNoPassPath,
241+
ServerName: "",
242+
InsecureSkipVerify: false},
243+
},
244+
handler: func(w http.ResponseWriter, r *http.Request) {
245+
bearer := r.Header.Get("Authorization")
246+
if bearer != ExpectedAuthenticationCredentials {
247+
fmt.Fprintf(w, "The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
248+
ExpectedAuthenticationCredentials, bearer)
249+
} else {
250+
fmt.Fprint(w, ExpectedMessage)
251+
}
252+
},
253+
}, {
254+
clientConfig: HTTPClientConfig{
255+
Authorization: &Authorization{
256+
CredentialsFile: BearerTokenFile,
257+
},
258+
TLSConfig: TLSConfig{
259+
CAFile: TLSCAChainPath,
260+
CertFile: ClientCertificatePath,
261+
KeyFile: ClientKeyNoPassPath,
262+
ServerName: "",
263+
InsecureSkipVerify: false},
264+
},
265+
handler: func(w http.ResponseWriter, r *http.Request) {
266+
bearer := r.Header.Get("Authorization")
267+
if bearer != ExpectedBearer {
268+
fmt.Fprintf(w, "The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
269+
ExpectedBearer, bearer)
270+
} else {
271+
fmt.Fprint(w, ExpectedMessage)
272+
}
273+
},
173274
}, {
174275
clientConfig: HTTPClientConfig{
175276
BasicAuth: &BasicAuth{
@@ -304,7 +405,7 @@ func TestMissingBearerAuthFile(t *testing.T) {
304405
t.Fatal("No error is returned here")
305406
}
306407

307-
if !strings.Contains(err.Error(), "unable to read bearer token file missing/bearer.token: open missing/bearer.token: no such file or directory") {
408+
if !strings.Contains(err.Error(), "unable to read authorization credentials file missing/bearer.token: open missing/bearer.token: no such file or directory") {
308409
t.Fatal("wrong error message being returned")
309410
}
310411
}
@@ -323,7 +424,7 @@ func TestBearerAuthRoundTripper(t *testing.T) {
323424
}, nil, nil)
324425

325426
// Normal flow.
326-
bearerAuthRoundTripper := NewBearerAuthRoundTripper(BearerToken, fakeRoundTripper)
427+
bearerAuthRoundTripper := NewAuthorizationCredentialsRoundTripper("Bearer", BearerToken, fakeRoundTripper)
327428
request, _ := http.NewRequest("GET", "/hitchhiker", nil)
328429
request.Header.Set("User-Agent", "Douglas Adams mind")
329430
_, err := bearerAuthRoundTripper.RoundTrip(request)
@@ -332,7 +433,7 @@ func TestBearerAuthRoundTripper(t *testing.T) {
332433
}
333434

334435
// Should honor already Authorization header set.
335-
bearerAuthRoundTripperShouldNotModifyExistingAuthorization := NewBearerAuthRoundTripper(newBearerToken, fakeRoundTripper)
436+
bearerAuthRoundTripperShouldNotModifyExistingAuthorization := NewAuthorizationCredentialsRoundTripper("Bearer", newBearerToken, fakeRoundTripper)
336437
request, _ = http.NewRequest("GET", "/hitchhiker", nil)
337438
request.Header.Set("Authorization", ExpectedBearer)
338439
_, err = bearerAuthRoundTripperShouldNotModifyExistingAuthorization.RoundTrip(request)
@@ -351,7 +452,7 @@ func TestBearerAuthFileRoundTripper(t *testing.T) {
351452
}, nil, nil)
352453

353454
// Normal flow.
354-
bearerAuthRoundTripper := NewBearerAuthFileRoundTripper(BearerTokenFile, fakeRoundTripper)
455+
bearerAuthRoundTripper := NewAuthorizationCredentialsFileRoundTripper("Bearer", BearerTokenFile, fakeRoundTripper)
355456
request, _ := http.NewRequest("GET", "/hitchhiker", nil)
356457
request.Header.Set("User-Agent", "Douglas Adams mind")
357458
_, err := bearerAuthRoundTripper.RoundTrip(request)
@@ -360,7 +461,7 @@ func TestBearerAuthFileRoundTripper(t *testing.T) {
360461
}
361462

362463
// Should honor already Authorization header set.
363-
bearerAuthRoundTripperShouldNotModifyExistingAuthorization := NewBearerAuthFileRoundTripper(MissingBearerTokenFile, fakeRoundTripper)
464+
bearerAuthRoundTripperShouldNotModifyExistingAuthorization := NewAuthorizationCredentialsFileRoundTripper("Bearer", MissingBearerTokenFile, fakeRoundTripper)
364465
request, _ = http.NewRequest("GET", "/hitchhiker", nil)
365466
request.Header.Set("Authorization", ExpectedBearer)
366467
_, err = bearerAuthRoundTripperShouldNotModifyExistingAuthorization.RoundTrip(request)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
authorization:
2+
credentials: bearertoken
3+
credentials_file: key.txt

0 commit comments

Comments
 (0)