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 "time" 25 26 "github.com/mwitkow/go-conntrack" 27 "gopkg.in/yaml.v2" 28) 29 30// BasicAuth contains basic HTTP authentication credentials. 31type BasicAuth struct { 32 Username string `yaml:"username"` 33 Password Secret `yaml:"password,omitempty"` 34 PasswordFile string `yaml:"password_file,omitempty"` 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 79// Validate validates the HTTPClientConfig to check only one of BearerToken, 80// BasicAuth and BearerTokenFile is configured. 81func (c *HTTPClientConfig) Validate() error { 82 if len(c.BearerToken) > 0 && len(c.BearerTokenFile) > 0 { 83 return fmt.Errorf("at most one of bearer_token & bearer_token_file must be configured") 84 } 85 if c.BasicAuth != nil && (len(c.BearerToken) > 0 || len(c.BearerTokenFile) > 0) { 86 return fmt.Errorf("at most one of basic_auth, bearer_token & bearer_token_file must be configured") 87 } 88 if c.BasicAuth != nil && (string(c.BasicAuth.Password) != "" && c.BasicAuth.PasswordFile != "") { 89 return fmt.Errorf("at most one of basic_auth password & password_file must be configured") 90 } 91 return nil 92} 93 94// UnmarshalYAML implements the yaml.Unmarshaler interface 95func (c *HTTPClientConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { 96 type plain HTTPClientConfig 97 if err := unmarshal((*plain)(c)); err != nil { 98 return err 99 } 100 return c.Validate() 101} 102 103// UnmarshalYAML implements the yaml.Unmarshaler interface. 104func (a *BasicAuth) UnmarshalYAML(unmarshal func(interface{}) error) error { 105 type plain BasicAuth 106 return unmarshal((*plain)(a)) 107} 108 109// NewClient returns a http.Client using the specified http.RoundTripper. 110func newClient(rt http.RoundTripper) *http.Client { 111 return &http.Client{Transport: rt} 112} 113 114// NewClientFromConfig returns a new HTTP client configured for the 115// given config.HTTPClientConfig. The name is used as go-conntrack metric label. 116func NewClientFromConfig(cfg HTTPClientConfig, name string) (*http.Client, error) { 117 rt, err := NewRoundTripperFromConfig(cfg, name) 118 if err != nil { 119 return nil, err 120 } 121 return newClient(rt), nil 122} 123 124// NewRoundTripperFromConfig returns a new HTTP RoundTripper configured for the 125// given config.HTTPClientConfig. The name is used as go-conntrack metric label. 126func NewRoundTripperFromConfig(cfg HTTPClientConfig, name string) (http.RoundTripper, error) { 127 tlsConfig, err := NewTLSConfig(&cfg.TLSConfig) 128 if err != nil { 129 return nil, err 130 } 131 // The only timeout we care about is the configured scrape timeout. 132 // It is applied on request. So we leave out any timings here. 133 var rt http.RoundTripper = &http.Transport{ 134 Proxy: http.ProxyURL(cfg.ProxyURL.URL), 135 MaxIdleConns: 20000, 136 MaxIdleConnsPerHost: 1000, // see https://github.com/golang/go/issues/13801 137 DisableKeepAlives: false, 138 TLSClientConfig: tlsConfig, 139 DisableCompression: true, 140 // 5 minutes is typically above the maximum sane scrape interval. So we can 141 // use keepalive for all configurations. 142 IdleConnTimeout: 5 * time.Minute, 143 DialContext: conntrack.NewDialContextFunc( 144 conntrack.DialWithTracing(), 145 conntrack.DialWithName(name), 146 ), 147 } 148 149 // If a bearer token is provided, create a round tripper that will set the 150 // Authorization header correctly on each request. 151 if len(cfg.BearerToken) > 0 { 152 rt = NewBearerAuthRoundTripper(cfg.BearerToken, rt) 153 } else if len(cfg.BearerTokenFile) > 0 { 154 rt = NewBearerAuthFileRoundTripper(cfg.BearerTokenFile, rt) 155 } 156 157 if cfg.BasicAuth != nil { 158 rt = NewBasicAuthRoundTripper(cfg.BasicAuth.Username, cfg.BasicAuth.Password, cfg.BasicAuth.PasswordFile, rt) 159 } 160 161 // Return a new configured RoundTripper. 162 return rt, nil 163} 164 165type bearerAuthRoundTripper struct { 166 bearerToken Secret 167 rt http.RoundTripper 168} 169 170// NewBearerAuthRoundTripper adds the provided bearer token to a request unless the authorization 171// header has already been set. 172func NewBearerAuthRoundTripper(token Secret, rt http.RoundTripper) http.RoundTripper { 173 return &bearerAuthRoundTripper{token, rt} 174} 175 176func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 177 if len(req.Header.Get("Authorization")) == 0 { 178 req = cloneRequest(req) 179 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", string(rt.bearerToken))) 180 } 181 return rt.rt.RoundTrip(req) 182} 183 184type bearerAuthFileRoundTripper struct { 185 bearerFile string 186 rt http.RoundTripper 187} 188 189// NewBearerAuthFileRoundTripper adds the bearer token read from the provided file to a request unless 190// the authorization header has already been set. This file is read for every request. 191func NewBearerAuthFileRoundTripper(bearerFile string, rt http.RoundTripper) http.RoundTripper { 192 return &bearerAuthFileRoundTripper{bearerFile, rt} 193} 194 195func (rt *bearerAuthFileRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 196 if len(req.Header.Get("Authorization")) == 0 { 197 b, err := ioutil.ReadFile(rt.bearerFile) 198 if err != nil { 199 return nil, fmt.Errorf("unable to read bearer token file %s: %s", rt.bearerFile, err) 200 } 201 bearerToken := strings.TrimSpace(string(b)) 202 203 req = cloneRequest(req) 204 req.Header.Set("Authorization", "Bearer "+bearerToken) 205 } 206 207 return rt.rt.RoundTrip(req) 208} 209 210type basicAuthRoundTripper struct { 211 username string 212 password Secret 213 passwordFile string 214 rt http.RoundTripper 215} 216 217// NewBasicAuthRoundTripper will apply a BASIC auth authorization header to a request unless it has 218// already been set. 219func NewBasicAuthRoundTripper(username string, password Secret, passwordFile string, rt http.RoundTripper) http.RoundTripper { 220 return &basicAuthRoundTripper{username, password, passwordFile, rt} 221} 222 223func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 224 if len(req.Header.Get("Authorization")) != 0 { 225 return rt.rt.RoundTrip(req) 226 } 227 req = cloneRequest(req) 228 if rt.passwordFile != "" { 229 bs, err := ioutil.ReadFile(rt.passwordFile) 230 if err != nil { 231 return nil, fmt.Errorf("unable to read basic auth password file %s: %s", rt.passwordFile, err) 232 } 233 req.SetBasicAuth(rt.username, strings.TrimSpace(string(bs))) 234 } else { 235 req.SetBasicAuth(rt.username, strings.TrimSpace(string(rt.password))) 236 } 237 return rt.rt.RoundTrip(req) 238} 239 240// cloneRequest returns a clone of the provided *http.Request. 241// The clone is a shallow copy of the struct and its Header map. 242func cloneRequest(r *http.Request) *http.Request { 243 // Shallow copy of the struct. 244 r2 := new(http.Request) 245 *r2 = *r 246 // Deep copy of the Header. 247 r2.Header = make(http.Header) 248 for k, s := range r.Header { 249 r2.Header[k] = s 250 } 251 return r2 252} 253 254// NewTLSConfig creates a new tls.Config from the given TLSConfig. 255func NewTLSConfig(cfg *TLSConfig) (*tls.Config, error) { 256 tlsConfig := &tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify} 257 258 // If a CA cert is provided then let's read it in so we can validate the 259 // scrape target's certificate properly. 260 if len(cfg.CAFile) > 0 { 261 caCertPool := x509.NewCertPool() 262 // Load CA cert. 263 caCert, err := ioutil.ReadFile(cfg.CAFile) 264 if err != nil { 265 return nil, fmt.Errorf("unable to use specified CA cert %s: %s", cfg.CAFile, err) 266 } 267 caCertPool.AppendCertsFromPEM(caCert) 268 tlsConfig.RootCAs = caCertPool 269 } 270 271 if len(cfg.ServerName) > 0 { 272 tlsConfig.ServerName = cfg.ServerName 273 } 274 // If a client cert & key is provided then configure TLS config accordingly. 275 if len(cfg.CertFile) > 0 && len(cfg.KeyFile) == 0 { 276 return nil, fmt.Errorf("client cert file %q specified without client key file", cfg.CertFile) 277 } else if len(cfg.KeyFile) > 0 && len(cfg.CertFile) == 0 { 278 return nil, fmt.Errorf("client key file %q specified without client cert file", cfg.KeyFile) 279 } else if len(cfg.CertFile) > 0 && len(cfg.KeyFile) > 0 { 280 cert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile) 281 if err != nil { 282 return nil, fmt.Errorf("unable to use specified client cert (%s) & key (%s): %s", cfg.CertFile, cfg.KeyFile, err) 283 } 284 tlsConfig.Certificates = []tls.Certificate{cert} 285 } 286 tlsConfig.BuildNameToCertificate() 287 288 return tlsConfig, nil 289} 290 291// TLSConfig configures the options for TLS connections. 292type TLSConfig struct { 293 // The CA cert to use for the targets. 294 CAFile string `yaml:"ca_file,omitempty"` 295 // The client cert file for the targets. 296 CertFile string `yaml:"cert_file,omitempty"` 297 // The client key file for the targets. 298 KeyFile string `yaml:"key_file,omitempty"` 299 // Used to verify the hostname for the targets. 300 ServerName string `yaml:"server_name,omitempty"` 301 // Disable target certificate validation. 302 InsecureSkipVerify bool `yaml:"insecure_skip_verify"` 303} 304 305// UnmarshalYAML implements the yaml.Unmarshaler interface. 306func (c *TLSConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { 307 type plain TLSConfig 308 return unmarshal((*plain)(c)) 309} 310 311func (c HTTPClientConfig) String() string { 312 b, err := yaml.Marshal(c) 313 if err != nil { 314 return fmt.Sprintf("<error creating http client config string: %s>", err) 315 } 316 return string(b) 317} 318