diff --git a/config/http_config.go b/config/http_config.go index 37aa9667..4763549b 100644 --- a/config/http_config.go +++ b/config/http_config.go @@ -129,6 +129,7 @@ func (tv *TLSVersion) String() string { // BasicAuth contains basic HTTP authentication credentials. type BasicAuth struct { Username string `yaml:"username" json:"username"` + UsernameFile string `yaml:"username_file,omitempty" json:"username_file,omitempty"` Password Secret `yaml:"password,omitempty" json:"password,omitempty"` PasswordFile string `yaml:"password_file,omitempty" json:"password_file,omitempty"` } @@ -139,6 +140,7 @@ func (a *BasicAuth) SetDirectory(dir string) { return } a.PasswordFile = JoinDir(dir, a.PasswordFile) + a.UsernameFile = JoinDir(dir, a.UsernameFile) } // Authorization contains HTTP authorization credentials. @@ -334,6 +336,9 @@ func (c *HTTPClientConfig) Validate() error { if (c.BasicAuth != nil || c.OAuth2 != nil) && (len(c.BearerToken) > 0 || len(c.BearerTokenFile) > 0) { return fmt.Errorf("at most one of basic_auth, oauth2, bearer_token & bearer_token_file must be configured") } + if c.BasicAuth != nil && (string(c.BasicAuth.Username) != "" && c.BasicAuth.UsernameFile != "") { + return fmt.Errorf("at most one of basic_auth username & username_file must be configured") + } if c.BasicAuth != nil && (string(c.BasicAuth.Password) != "" && c.BasicAuth.PasswordFile != "") { return fmt.Errorf("at most one of basic_auth password & password_file must be configured") } @@ -555,7 +560,7 @@ func NewRoundTripperFromConfig(cfg HTTPClientConfig, name string, optFuncs ...HT } if cfg.BasicAuth != nil { - rt = NewBasicAuthRoundTripper(cfg.BasicAuth.Username, cfg.BasicAuth.Password, cfg.BasicAuth.PasswordFile, rt) + rt = NewBasicAuthRoundTripper(cfg.BasicAuth.Username, cfg.BasicAuth.Password, cfg.BasicAuth.UsernameFile, cfg.BasicAuth.PasswordFile, rt) } if cfg.OAuth2 != nil { @@ -645,30 +650,43 @@ func (rt *authorizationCredentialsFileRoundTripper) CloseIdleConnections() { type basicAuthRoundTripper struct { username string password Secret + usernameFile string passwordFile string rt http.RoundTripper } // NewBasicAuthRoundTripper will apply a BASIC auth authorization header to a request unless it has // already been set. -func NewBasicAuthRoundTripper(username string, password Secret, passwordFile string, rt http.RoundTripper) http.RoundTripper { - return &basicAuthRoundTripper{username, password, passwordFile, rt} +func NewBasicAuthRoundTripper(username string, password Secret, usernameFile, passwordFile string, rt http.RoundTripper) http.RoundTripper { + return &basicAuthRoundTripper{username, password, usernameFile, passwordFile, rt} } func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + var username string + var password string if len(req.Header.Get("Authorization")) != 0 { return rt.rt.RoundTrip(req) } - req = cloneRequest(req) + if rt.usernameFile != "" { + usernameBytes, err := os.ReadFile(rt.usernameFile) + if err != nil { + return nil, fmt.Errorf("unable to read basic auth username file %s: %s", rt.usernameFile, err) + } + username = strings.TrimSpace(string(usernameBytes)) + } else { + username = rt.username + } if rt.passwordFile != "" { - bs, err := os.ReadFile(rt.passwordFile) + passwordBytes, err := os.ReadFile(rt.passwordFile) if err != nil { return nil, fmt.Errorf("unable to read basic auth password file %s: %s", rt.passwordFile, err) } - req.SetBasicAuth(rt.username, strings.TrimSpace(string(bs))) + password = strings.TrimSpace(string(passwordBytes)) } else { - req.SetBasicAuth(rt.username, strings.TrimSpace(string(rt.password))) + password = string(rt.password) } + req = cloneRequest(req) + req.SetBasicAuth(username, password) return rt.rt.RoundTrip(req) } diff --git a/config/http_config_test.go b/config/http_config_test.go index ca2ed71a..4ebe038b 100644 --- a/config/http_config_test.go +++ b/config/http_config_test.go @@ -83,6 +83,10 @@ var invalidHTTPClientConfigs = []struct { httpClientConfigFile: "testdata/http.conf.basic-auth.too-much.bad.yaml", errMsg: "at most one of basic_auth password & password_file must be configured", }, + { + httpClientConfigFile: "testdata/http.conf.basic-auth.bad-username.yaml", + errMsg: "at most one of basic_auth username & username_file must be configured", + }, { httpClientConfigFile: "testdata/http.conf.mix-bearer-and-creds.bad.yaml", errMsg: "authorization is not compatible with bearer_token & bearer_token_file", @@ -896,6 +900,32 @@ func TestBasicAuthPasswordFile(t *testing.T) { } } +func TestBasicUsernameFile(t *testing.T) { + cfg, _, err := LoadHTTPConfigFile("testdata/http.conf.basic-auth.username-file.good.yaml") + if err != nil { + t.Fatalf("Error loading HTTP client config: %v", err) + } + client, err := NewClientFromConfig(*cfg, "test") + if err != nil { + t.Fatalf("Error creating HTTP Client: %v", err) + } + + rt, ok := client.Transport.(*basicAuthRoundTripper) + if !ok { + t.Fatalf("Error casting to basic auth transport, %v", client.Transport) + } + + if rt.username != "" { + t.Errorf("Bad HTTP client username: %s", rt.username) + } + if string(rt.usernameFile) != "testdata/basic-auth-username" { + t.Errorf("Bad HTTP client usernameFile: %s", rt.usernameFile) + } + if string(rt.passwordFile) != "testdata/basic-auth-password" { + t.Errorf("Bad HTTP client passwordFile: %s", rt.passwordFile) + } +} + func getCertificateBlobs(t *testing.T) map[string][]byte { files := []string{ TLSCAChainPath, diff --git a/config/testdata/basic-auth-username b/config/testdata/basic-auth-username new file mode 100644 index 00000000..cc8c9d31 --- /dev/null +++ b/config/testdata/basic-auth-username @@ -0,0 +1 @@ +testuser \ No newline at end of file diff --git a/config/testdata/http.conf.basic-auth.bad-username.yaml b/config/testdata/http.conf.basic-auth.bad-username.yaml new file mode 100644 index 00000000..bfb911b8 --- /dev/null +++ b/config/testdata/http.conf.basic-auth.bad-username.yaml @@ -0,0 +1,4 @@ +basic_auth: + username: user + username_file: testdata/basic-auth-username + password: foo \ No newline at end of file diff --git a/config/testdata/http.conf.basic-auth.username-file.good.yaml b/config/testdata/http.conf.basic-auth.username-file.good.yaml new file mode 100644 index 00000000..24c3c3cb --- /dev/null +++ b/config/testdata/http.conf.basic-auth.username-file.good.yaml @@ -0,0 +1,3 @@ +basic_auth: + username_file: testdata/basic-auth-username + password_file: testdata/basic-auth-password \ No newline at end of file