mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-27 22:49:40 -08:00
2aba238f31
This adds support for basic authentication which closes #3090 The support for specifying the client timeout was removed as discussed in https://github.com/prometheus/common/pull/123. Marathon was the only sd mechanism doing this and configuring the timeout is done through `Context`. DC/OS uses a custom `Authorization` header for authenticating. This adds 2 new configuration properties to reflect this. Existing configuration files that use the bearer token will no longer work. More work is required to make this backwards compatible.
198 lines
6.4 KiB
Go
198 lines
6.4 KiB
Go
// Copyright 2013 The Prometheus Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package httputil
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/mwitkow/go-conntrack"
|
|
config_util "github.com/prometheus/common/config"
|
|
)
|
|
|
|
// NewClient returns a http.Client using the specified http.RoundTripper.
|
|
func newClient(rt http.RoundTripper) *http.Client {
|
|
return &http.Client{Transport: rt}
|
|
}
|
|
|
|
// NewClientFromConfig returns a new HTTP client configured for the
|
|
// given config.HTTPClientConfig. The name is used as go-conntrack metric label.
|
|
func NewClientFromConfig(cfg config_util.HTTPClientConfig, name string) (*http.Client, error) {
|
|
rt, err := NewRoundTripperFromConfig(cfg, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return newClient(rt), nil
|
|
}
|
|
|
|
// NewRoundTripperFromConfig returns a new HTTP RoundTripper configured for the
|
|
// given config.HTTPClientConfig. The name is used as go-conntrack metric label.
|
|
func NewRoundTripperFromConfig(cfg config_util.HTTPClientConfig, name string) (http.RoundTripper, error) {
|
|
tlsConfig, err := NewTLSConfig(cfg.TLSConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// The only timeout we care about is the configured scrape timeout.
|
|
// It is applied on request. So we leave out any timings here.
|
|
var rt http.RoundTripper = &http.Transport{
|
|
Proxy: http.ProxyURL(cfg.ProxyURL.URL),
|
|
MaxIdleConns: 20000,
|
|
MaxIdleConnsPerHost: 1000, // see https://github.com/golang/go/issues/13801
|
|
DisableKeepAlives: false,
|
|
TLSClientConfig: tlsConfig,
|
|
DisableCompression: true,
|
|
// 5 minutes is typically above the maximum sane scrape interval. So we can
|
|
// use keepalive for all configurations.
|
|
IdleConnTimeout: 5 * time.Minute,
|
|
DialContext: conntrack.NewDialContextFunc(
|
|
conntrack.DialWithTracing(),
|
|
conntrack.DialWithName(name),
|
|
),
|
|
}
|
|
|
|
// If a bearer token is provided, create a round tripper that will set the
|
|
// Authorization header correctly on each request.
|
|
if len(cfg.BearerToken) > 0 {
|
|
rt = NewBearerAuthRoundTripper(string(cfg.BearerToken), rt)
|
|
} else if len(cfg.BearerTokenFile) > 0 {
|
|
rt = NewBearerAuthFileRoundTripper(cfg.BearerTokenFile, rt)
|
|
}
|
|
|
|
if cfg.BasicAuth != nil {
|
|
rt = NewBasicAuthRoundTripper(cfg.BasicAuth.Username, string(cfg.BasicAuth.Password), rt)
|
|
}
|
|
|
|
// Return a new configured RoundTripper.
|
|
return rt, nil
|
|
}
|
|
|
|
type bearerAuthRoundTripper struct {
|
|
bearerToken string
|
|
rt http.RoundTripper
|
|
}
|
|
|
|
// NewBearerAuthRoundTripper adds the provided bearer token to a request unless the authorization
|
|
// header has already been set.
|
|
func NewBearerAuthRoundTripper(bearer string, rt http.RoundTripper) http.RoundTripper {
|
|
return &bearerAuthRoundTripper{bearer, rt}
|
|
}
|
|
|
|
func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
if len(req.Header.Get("Authorization")) == 0 {
|
|
req = cloneRequest(req)
|
|
req.Header.Set("Authorization", "Bearer "+rt.bearerToken)
|
|
}
|
|
|
|
return rt.rt.RoundTrip(req)
|
|
}
|
|
|
|
type bearerAuthFileRoundTripper struct {
|
|
bearerFile string
|
|
rt http.RoundTripper
|
|
}
|
|
|
|
// NewBearerAuthFileRoundTripper adds the bearer token read from the provided file to a request unless
|
|
// the authorization header has already been set. This file is read for every request.
|
|
func NewBearerAuthFileRoundTripper(bearerFile string, rt http.RoundTripper) http.RoundTripper {
|
|
return &bearerAuthFileRoundTripper{bearerFile, rt}
|
|
}
|
|
|
|
func (rt *bearerAuthFileRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
if len(req.Header.Get("Authorization")) == 0 {
|
|
b, err := ioutil.ReadFile(rt.bearerFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to read bearer token file %s: %s", rt.bearerFile, err)
|
|
}
|
|
bearerToken := strings.TrimSpace(string(b))
|
|
|
|
req = cloneRequest(req)
|
|
req.Header.Set("Authorization", "Bearer "+bearerToken)
|
|
}
|
|
|
|
return rt.rt.RoundTrip(req)
|
|
}
|
|
|
|
type basicAuthRoundTripper struct {
|
|
username string
|
|
password string
|
|
rt http.RoundTripper
|
|
}
|
|
|
|
// NewBasicAuthRoundTripper will apply a BASIC auth authorization header to a request unless it has
|
|
// already been set.
|
|
func NewBasicAuthRoundTripper(username, password string, rt http.RoundTripper) http.RoundTripper {
|
|
return &basicAuthRoundTripper{username, password, rt}
|
|
}
|
|
|
|
func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
if len(req.Header.Get("Authorization")) != 0 {
|
|
return rt.rt.RoundTrip(req)
|
|
}
|
|
req = cloneRequest(req)
|
|
req.SetBasicAuth(rt.username, rt.password)
|
|
return rt.rt.RoundTrip(req)
|
|
}
|
|
|
|
// cloneRequest returns a clone of the provided *http.Request.
|
|
// The clone is a shallow copy of the struct and its Header map.
|
|
func cloneRequest(r *http.Request) *http.Request {
|
|
// Shallow copy of the struct.
|
|
r2 := new(http.Request)
|
|
*r2 = *r
|
|
// Deep copy of the Header.
|
|
r2.Header = make(http.Header)
|
|
for k, s := range r.Header {
|
|
r2.Header[k] = s
|
|
}
|
|
return r2
|
|
}
|
|
|
|
// NewTLSConfig creates a new tls.Config from the given config_util.TLSConfig.
|
|
func NewTLSConfig(cfg config_util.TLSConfig) (*tls.Config, error) {
|
|
tlsConfig := &tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify}
|
|
|
|
// If a CA cert is provided then let's read it in so we can validate the
|
|
// scrape target's certificate properly.
|
|
if len(cfg.CAFile) > 0 {
|
|
caCertPool := x509.NewCertPool()
|
|
// Load CA cert.
|
|
caCert, err := ioutil.ReadFile(cfg.CAFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to use specified CA cert %s: %s", cfg.CAFile, err)
|
|
}
|
|
caCertPool.AppendCertsFromPEM(caCert)
|
|
tlsConfig.RootCAs = caCertPool
|
|
}
|
|
|
|
if len(cfg.ServerName) > 0 {
|
|
tlsConfig.ServerName = cfg.ServerName
|
|
}
|
|
// If a client cert & key is provided then configure TLS config accordingly.
|
|
if len(cfg.CertFile) > 0 && len(cfg.KeyFile) > 0 {
|
|
cert, err := tls.LoadX509KeyPair(cfg.CertFile, cfg.KeyFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to use specified client cert (%s) & key (%s): %s", cfg.CertFile, cfg.KeyFile, err)
|
|
}
|
|
tlsConfig.Certificates = []tls.Certificate{cert}
|
|
}
|
|
tlsConfig.BuildNameToCertificate()
|
|
|
|
return tlsConfig, nil
|
|
}
|