1// Copyright 2016 The Prometheus Authors 2// Licensed under the Apache License, Version 2.0 (the "License"); 3// you may not use this file except in compliance with the License. 4// You may obtain a copy of the License at 5// 6// http://www.apache.org/licenses/LICENSE-2.0 7// 8// Unless required by applicable law or agreed to in writing, software 9// distributed under the License is distributed on an "AS IS" BASIS, 10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11// See the License for the specific language governing permissions and 12// limitations under the License. 13 14package config 15 16import ( 17 "crypto/tls" 18 "crypto/x509" 19 "fmt" 20 "io/ioutil" 21 "net/http" 22 "net/url" 23 "strings" 24 25 yaml "gopkg.in/yaml.v2" 26) 27 28// BasicAuth contains basic HTTP authentication credentials. 29type BasicAuth struct { 30 Username string `yaml:"username"` 31 Password Secret `yaml:"password"` 32 33 // Catches all undefined fields and must be empty after parsing. 34 XXX map[string]interface{} `yaml:",inline"` 35} 36 37// URL is a custom URL type that allows validation at configuration load time. 38type URL struct { 39 *url.URL 40} 41 42// UnmarshalYAML implements the yaml.Unmarshaler interface for URLs. 43func (u *URL) UnmarshalYAML(unmarshal func(interface{}) error) error { 44 var s string 45 if err := unmarshal(&s); err != nil { 46 return err 47 } 48 49 urlp, err := url.Parse(s) 50 if err != nil { 51 return err 52 } 53 u.URL = urlp 54 return nil 55} 56 57// MarshalYAML implements the yaml.Marshaler interface for URLs. 58func (u URL) MarshalYAML() (interface{}, error) { 59 if u.URL != nil { 60 return u.String(), nil 61 } 62 return nil, nil 63} 64 65// HTTPClientConfig configures an HTTP client. 66type HTTPClientConfig struct { 67 // The HTTP basic authentication credentials for the targets. 68 BasicAuth *BasicAuth `yaml:"basic_auth,omitempty"` 69 // The bearer token for the targets. 70 BearerToken Secret `yaml:"bearer_token,omitempty"` 71 // The bearer token file for the targets. 72 BearerTokenFile string `yaml:"bearer_token_file,omitempty"` 73 // HTTP proxy server to use to connect to the targets. 74 ProxyURL URL `yaml:"proxy_url,omitempty"` 75 // TLSConfig to use to connect to the targets. 76 TLSConfig TLSConfig `yaml:"tls_config,omitempty"` 77 78 // Catches all undefined fields and must be empty after parsing. 79 XXX map[string]interface{} `yaml:",inline"` 80} 81 82func (c *HTTPClientConfig) validate() error { 83 if len(c.BearerToken) > 0 && len(c.BearerTokenFile) > 0 { 84 return fmt.Errorf("at most one of bearer_token & bearer_token_file must be configured") 85 } 86 if c.BasicAuth != nil && (len(c.BearerToken) > 0 || len(c.BearerTokenFile) > 0) { 87 return fmt.Errorf("at most one of basic_auth, bearer_token & bearer_token_file must be configured") 88 } 89 return nil 90} 91 92// UnmarshalYAML implements the yaml.Unmarshaler interface 93func (c *HTTPClientConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { 94 type plain HTTPClientConfig 95 err := unmarshal((*plain)(c)) 96 if err != nil { 97 return err 98 } 99 err = c.validate() 100 if err != nil { 101 return c.validate() 102 } 103 return checkOverflow(c.XXX, "http_client_config") 104} 105 106// UnmarshalYAML implements the yaml.Unmarshaler interface. 107func (a *BasicAuth) UnmarshalYAML(unmarshal func(interface{}) error) error { 108 type plain BasicAuth 109 err := unmarshal((*plain)(a)) 110 if err != nil { 111 return err 112 } 113 return checkOverflow(a.XXX, "basic_auth") 114} 115 116// NewHTTPClientFromConfig returns a new HTTP client configured for the 117// given config.HTTPClientConfig. 118func NewHTTPClientFromConfig(cfg *HTTPClientConfig) (*http.Client, error) { 119 tlsConfig, err := NewTLSConfig(&cfg.TLSConfig) 120 if err != nil { 121 return nil, err 122 } 123 124 // It's the caller's job to handle timeouts 125 var rt http.RoundTripper = &http.Transport{ 126 Proxy: http.ProxyURL(cfg.ProxyURL.URL), 127 DisableKeepAlives: true, 128 TLSClientConfig: tlsConfig, 129 } 130 131 // If a bearer token is provided, create a round tripper that will set the 132 // Authorization header correctly on each request. 133 bearerToken := cfg.BearerToken 134 if len(bearerToken) == 0 && len(cfg.BearerTokenFile) > 0 { 135 b, err := ioutil.ReadFile(cfg.BearerTokenFile) 136 if err != nil { 137 return nil, fmt.Errorf("unable to read bearer token file %s: %s", cfg.BearerTokenFile, err) 138 } 139 bearerToken = Secret(strings.TrimSpace(string(b))) 140 } 141 142 if len(bearerToken) > 0 { 143 rt = NewBearerAuthRoundTripper(bearerToken, rt) 144 } 145 146 if cfg.BasicAuth != nil { 147 rt = NewBasicAuthRoundTripper(cfg.BasicAuth.Username, Secret(cfg.BasicAuth.Password), rt) 148 } 149 150 // Return a new client with the configured round tripper. 151 return &http.Client{Transport: rt}, nil 152} 153 154type bearerAuthRoundTripper struct { 155 bearerToken Secret 156 rt http.RoundTripper 157} 158 159type basicAuthRoundTripper struct { 160 username string 161 password Secret 162 rt http.RoundTripper 163} 164 165// NewBasicAuthRoundTripper will apply a BASIC auth authorization header to a request unless it has 166// already been set. 167func NewBasicAuthRoundTripper(username string, password Secret, rt http.RoundTripper) http.RoundTripper { 168 return &basicAuthRoundTripper{username, password, rt} 169} 170 171func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 172 if len(req.Header.Get("Authorization")) == 0 { 173 req = cloneRequest(req) 174 req.Header.Set("Authorization", "Bearer "+string(rt.bearerToken)) 175 } 176 177 return rt.rt.RoundTrip(req) 178} 179 180// NewBearerAuthRoundTripper adds the provided bearer token to a request unless the authorization 181// header has already been set. 182func NewBearerAuthRoundTripper(bearer Secret, rt http.RoundTripper) http.RoundTripper { 183 return &bearerAuthRoundTripper{bearer, rt} 184} 185 186func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 187 if len(req.Header.Get("Authorization")) != 0 { 188 return rt.RoundTrip(req) 189 } 190 req = cloneRequest(req) 191 req.SetBasicAuth(rt.username, string(rt.password)) 192 return rt.rt.RoundTrip(req) 193} 194 195// cloneRequest returns a clone of the provided *http.Request. 196// The clone is a shallow copy of the struct and its Header map. 197func cloneRequest(r *http.Request) *http.Request { 198 // Shallow copy of the struct. 199 r2 := new(http.Request) 200 *r2 = *r 201 // Deep copy of the Header. 202 r2.Header = make(http.Header) 203 for k, s := range r.Header { 204 r2.Header[k] = s 205 } 206 return r2 207} 208 209// NewTLSConfig creates a new tls.Config from the given config.TLSConfig. 210func NewTLSConfig(cfg *TLSConfig) (*tls.Config, error) { 211 tlsConfig := &tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify} 212 213 // If a CA cert is provided then let's read it in so we can validate the 214 // scrape target's certificate properly. 215 if len(cfg.CAFile) > 0 { 216 caCertPool := x509.NewCertPool() 217 // Load CA cert. 218 caCert, err := ioutil.ReadFile(cfg.CAFile) 219 if err != nil { 220 return nil, fmt.Errorf("unable to use specified CA cert %s: %s", cfg.CAFile, err) 221 } 222 caCertPool.AppendCertsFromPEM(caCert) 223 tlsConfig.RootCAs = caCertPool 224 } 225 226 if len(cfg.ServerName) > 0 { 227 tlsConfig.ServerName = cfg.ServerName 228 } 229 230 // If a client cert & key is provided then configure TLS config accordingly. 231 if len(cfg.CertFile) > 0 && len(cfg.KeyFile) == 0 { 232 return nil, fmt.Errorf("client cert file %q specified without client key file", cfg.CertFile) 233 } else if len(cfg.KeyFile) > 0 && len(cfg.CertFile) == 0 { 234 return nil, fmt.Errorf("client key file %q specified without client cert file", cfg.KeyFile) 235 } else if len(cfg.CertFile) > 0 && len(cfg.KeyFile) > 0 { 236 cert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile) 237 if err != nil { 238 return nil, fmt.Errorf("unable to use specified client cert (%s) & key (%s): %s", cfg.CertFile, cfg.KeyFile, err) 239 } 240 tlsConfig.Certificates = []tls.Certificate{cert} 241 } 242 tlsConfig.BuildNameToCertificate() 243 244 return tlsConfig, nil 245} 246 247// TLSConfig configures the options for TLS connections. 248type TLSConfig struct { 249 // The CA cert to use for the targets. 250 CAFile string `yaml:"ca_file,omitempty"` 251 // The client cert file for the targets. 252 CertFile string `yaml:"cert_file,omitempty"` 253 // The client key file for the targets. 254 KeyFile string `yaml:"key_file,omitempty"` 255 // Used to verify the hostname for the targets. 256 ServerName string `yaml:"server_name,omitempty"` 257 // Disable target certificate validation. 258 InsecureSkipVerify bool `yaml:"insecure_skip_verify"` 259 260 // Catches all undefined fields and must be empty after parsing. 261 XXX map[string]interface{} `yaml:",inline"` 262} 263 264// UnmarshalYAML implements the yaml.Unmarshaler interface. 265func (c *TLSConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { 266 type plain TLSConfig 267 if err := unmarshal((*plain)(c)); err != nil { 268 return err 269 } 270 return checkOverflow(c.XXX, "TLS config") 271} 272 273func (c HTTPClientConfig) String() string { 274 b, err := yaml.Marshal(c) 275 if err != nil { 276 return fmt.Sprintf("<error creating http client config string: %s>", err) 277 } 278 return string(b) 279} 280