From 52cf6b3e6ea08e5013e6bef42b0f59e8124e0b7f Mon Sep 17 00:00:00 2001 From: Jimmi Dyson Date: Wed, 22 Jul 2015 16:48:22 +0100 Subject: [PATCH] Configuration options for bearer tokens, client certs & CA certs Fixes #918, fixes #917 --- .gitignore | 2 + config/config.go | 25 +++- config/config_test.go | 21 +++ config/testdata/bearertoken.bad.yml | 6 + config/testdata/bearertoken_basicauth.bad.yml | 8 + config/testdata/conf.good.yml | 8 + retrieval/target.go | 62 +++++++- retrieval/target_test.go | 140 ++++++++++++++++++ retrieval/testdata/bearertoken.txt | 1 + retrieval/testdata/ca.cer | 22 +++ retrieval/testdata/ca.key | 27 ++++ retrieval/testdata/client.cer | 25 ++++ retrieval/testdata/client.key | 51 +++++++ retrieval/testdata/server.cer | 20 +++ retrieval/testdata/server.key | 27 ++++ util/httputil/client.go | 86 +++++++++++ util/httputil/deadline_client.go | 43 ------ 17 files changed, 529 insertions(+), 45 deletions(-) create mode 100644 config/testdata/bearertoken.bad.yml create mode 100644 config/testdata/bearertoken_basicauth.bad.yml create mode 100644 retrieval/testdata/bearertoken.txt create mode 100644 retrieval/testdata/ca.cer create mode 100644 retrieval/testdata/ca.key create mode 100644 retrieval/testdata/client.cer create mode 100644 retrieval/testdata/client.key create mode 100644 retrieval/testdata/server.cer create mode 100644 retrieval/testdata/server.key create mode 100644 util/httputil/client.go delete mode 100644 util/httputil/deadline_client.go diff --git a/.gitignore b/.gitignore index 6db81d442..755efc557 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,8 @@ *~ .*.swp .*.swo +*.iml +.idea .DS_Store ._* diff --git a/config/config.go b/config/config.go index 22ae0d519..136a4f898 100644 --- a/config/config.go +++ b/config/config.go @@ -20,7 +20,7 @@ var ( patJobName = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*$`) patFileSDName = regexp.MustCompile(`^[^*]*(\*[^/]*)?\.(json|yml|yaml|JSON|YML|YAML)$`) patRulePath = regexp.MustCompile(`^[^*]*(\*[^/]*)?$`) - patAuthLine = regexp.MustCompile(`((?:username|password):\s+)(".+"|'.+'|[^\s]+)`) + patAuthLine = regexp.MustCompile(`((?:username|password|bearer_token):\s+)(".+"|'.+'|[^\s]+)`) ) // Load parses the YAML input s into a Config. @@ -229,6 +229,14 @@ type ScrapeConfig struct { Scheme string `yaml:"scheme,omitempty"` // The HTTP basic authentication credentials for the targets. BasicAuth *BasicAuth `yaml:"basic_auth,omitempty"` + // The bearer token for the targets. + BearerToken string `yaml:"bearer_token,omitempty"` + // The bearer token file for the targets. + BearerTokenFile string `yaml:"bearer_token_file,omitempty"` + // The ca cert to use for the targets. + CACert string `yaml:"ca_cert,omitempty"` + // The client cert authentication credentials for the targets. + ClientCert *ClientCert `yaml:"client_cert,omitempty"` // List of labeled target groups for this job. TargetGroups []*TargetGroup `yaml:"target_groups,omitempty"` @@ -261,6 +269,12 @@ func (c *ScrapeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if !patJobName.MatchString(c.JobName) { return fmt.Errorf("%q is not a valid job name", c.JobName) } + if len(c.BearerToken) > 0 && len(c.BearerTokenFile) > 0 { + return fmt.Errorf("at most one of bearer_token & bearer_token_file must be configured") + } + if c.BasicAuth != nil && (len(c.BearerToken) > 0 || len(c.BearerTokenFile) > 0) { + return fmt.Errorf("at most one of basic_auth, bearer_token & bearer_token_file must be configured") + } return checkOverflow(c.XXX, "scrape_config") } @@ -273,6 +287,15 @@ type BasicAuth struct { XXX map[string]interface{} `yaml:",inline"` } +// ClientCert contains client cert credentials. +type ClientCert struct { + Cert string `yaml:"cert"` + Key string `yaml:"key"` + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline"` +} + // UnmarshalYAML implements the yaml.Unmarshaler interface. func (a *BasicAuth) UnmarshalYAML(unmarshal func(interface{}) error) error { type plain BasicAuth diff --git a/config/config_test.go b/config/config_test.go index 9d78f6522..64436fb36 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -157,6 +157,21 @@ var expectedConf = &Config{ }, }, }, + { + JobName: "service-z", + + ScrapeInterval: Duration(15 * time.Second), + ScrapeTimeout: Duration(10 * time.Second), + + MetricsPath: "/metrics", + Scheme: "http", + + ClientCert: &ClientCert{ + Cert: "valid_cert_file", + Key: "valid_key_file", + }, + BearerToken: "avalidtoken", + }, }, original: "", } @@ -224,6 +239,12 @@ var expectedErrors = []struct { }, { filename: "unknown_attr.bad.yml", errMsg: "unknown fields in scrape_config: consult_sd_configs", + }, { + filename: "bearertoken.bad.yml", + errMsg: "at most one of bearer_token & bearer_token_file must be configured", + }, { + filename: "bearertoken_basicauth.bad.yml", + errMsg: "at most one of basic_auth, bearer_token & bearer_token_file must be configured", }, } diff --git a/config/testdata/bearertoken.bad.yml b/config/testdata/bearertoken.bad.yml new file mode 100644 index 000000000..58efc2395 --- /dev/null +++ b/config/testdata/bearertoken.bad.yml @@ -0,0 +1,6 @@ +scrape_configs: + - job_name: prometheus + + bearer_token: 1234 + bearer_token_file: somefile + diff --git a/config/testdata/bearertoken_basicauth.bad.yml b/config/testdata/bearertoken_basicauth.bad.yml new file mode 100644 index 000000000..2584f7fe7 --- /dev/null +++ b/config/testdata/bearertoken_basicauth.bad.yml @@ -0,0 +1,8 @@ +scrape_configs: + - job_name: prometheus + + bearer_token: 1234 + basic_auth: + username: user + password: password + diff --git a/config/testdata/conf.good.yml b/config/testdata/conf.good.yml index 63338c60b..df8cf62c6 100644 --- a/config/testdata/conf.good.yml +++ b/config/testdata/conf.good.yml @@ -89,3 +89,11 @@ scrape_configs: consul_sd_configs: - server: 'localhost:1234' services: ['nginx', 'cache', 'mysql'] + +- job_name: service-z + + client_cert: + cert: valid_cert_file + key: valid_key_file + + bearer_token: avalidtoken diff --git a/retrieval/target.go b/retrieval/target.go index 0ab77a47d..d9502b025 100644 --- a/retrieval/target.go +++ b/retrieval/target.go @@ -14,8 +14,11 @@ package retrieval import ( + "crypto/tls" + "crypto/x509" "errors" "fmt" + "io/ioutil" "math/rand" "net/http" "net/url" @@ -195,6 +198,13 @@ func (t *Target) Update(cfg *config.ScrapeConfig, baseLabels, metaLabels clientm t.Lock() defer t.Unlock() + httpClient, err := newHTTPClient(cfg) + if err != nil { + log.Errorf("cannot create HTTP client: %v", err) + return + } + t.httpClient = httpClient + t.url.Scheme = cfg.Scheme t.url.Path = string(baseLabels[clientmodel.MetricsPathLabel]) params := url.Values{} @@ -218,7 +228,6 @@ func (t *Target) Update(cfg *config.ScrapeConfig, baseLabels, metaLabels clientm t.scrapeInterval = time.Duration(cfg.ScrapeInterval) t.deadline = time.Duration(cfg.ScrapeTimeout) - t.httpClient = httputil.NewDeadlineClient(time.Duration(cfg.ScrapeTimeout)) t.honorLabels = cfg.HonorLabels t.metaLabels = metaLabels @@ -235,6 +244,57 @@ func (t *Target) Update(cfg *config.ScrapeConfig, baseLabels, metaLabels clientm t.metricRelabelConfigs = cfg.MetricRelabelConfigs } +func newHTTPClient(cfg *config.ScrapeConfig) (*http.Client, error) { + tlsConfig := &tls.Config{} + + // 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.CACert) > 0 { + caCertPool := x509.NewCertPool() + // Load CA cert. + caCert, err := ioutil.ReadFile(cfg.CACert) + if err != nil { + return nil, fmt.Errorf("Unable to use specified CA cert %s: %s", cfg.CACert, err) + } + caCertPool.AppendCertsFromPEM(caCert) + tlsConfig.RootCAs = caCertPool + } + + // If a client cert & key is provided then configure TLS config accordingly. + if cfg.ClientCert != nil && len(cfg.ClientCert.Cert) > 0 && len(cfg.ClientCert.Key) > 0 { + cert, err := tls.LoadX509KeyPair(cfg.ClientCert.Cert, cfg.ClientCert.Key) + if err != nil { + return nil, fmt.Errorf("Unable to use specified client cert (%s) & key (%s): %s", cfg.ClientCert.Cert, cfg.ClientCert.Key, err) + } + tlsConfig.Certificates = []tls.Certificate{cert} + } + tlsConfig.BuildNameToCertificate() + + // Get a default roundtripper with the scrape timeout. + rt := httputil.NewDeadlineRoundTripper(time.Duration(cfg.ScrapeTimeout)) + tr := rt.(*http.Transport) + // Set the TLS config from above + tr.TLSClientConfig = tlsConfig + rt = tr + + // If a bearer token is provided, create a round tripper that will set the + // Authorization header correctly on each request. + bearerToken := cfg.BearerToken + if len(bearerToken) == 0 && len(cfg.BearerTokenFile) > 0 { + if b, err := ioutil.ReadFile(cfg.BearerTokenFile); err != nil { + return nil, fmt.Errorf("Unable to read bearer token file %s: %s", cfg.BearerTokenFile, err) + } else { + bearerToken = string(b) + } + } + if len(bearerToken) > 0 { + rt = httputil.NewBearerAuthRoundTripper(bearerToken, rt) + } + + // Return a new client with the configured round tripper. + return httputil.NewClient(rt), nil +} + func (t *Target) String() string { return t.url.Host } diff --git a/retrieval/target_test.go b/retrieval/target_test.go index bcf2469c2..c03a47c25 100644 --- a/retrieval/target_test.go +++ b/retrieval/target_test.go @@ -14,8 +14,11 @@ package retrieval import ( + "crypto/tls" + "crypto/x509" "errors" "fmt" + "io/ioutil" "net/http" "net/http/httptest" "net/url" @@ -455,3 +458,140 @@ func newTestTarget(targetURL string, deadline time.Duration, baseLabels clientmo } return t } + +func TestNewHTTPBearerToken(t *testing.T) { + server := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + expected := "Bearer 1234" + received := r.Header.Get("Authorization") + if expected != received { + t.Fatalf("Authorization header was not set correctly: expected '%v', got '%v'", expected, received) + } + }, + ), + ) + defer server.Close() + + cfg := &config.ScrapeConfig{ + ScrapeTimeout: config.Duration(1 * time.Second), + BearerToken: "1234", + } + c, err := newHTTPClient(cfg) + if err != nil { + t.Fatal(err) + } + _, err = c.Get(server.URL) + if err != nil { + t.Fatal(err) + } +} + +func TestNewHTTPBearerTokenFile(t *testing.T) { + server := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + expected := "Bearer 12345" + received := r.Header.Get("Authorization") + if expected != received { + t.Fatalf("Authorization header was not set correctly: expected '%v', got '%v'", expected, received) + } + }, + ), + ) + defer server.Close() + + cfg := &config.ScrapeConfig{ + ScrapeTimeout: config.Duration(1 * time.Second), + BearerTokenFile: "testdata/bearertoken.txt", + } + c, err := newHTTPClient(cfg) + if err != nil { + t.Fatal(err) + } + _, err = c.Get(server.URL) + if err != nil { + t.Fatal(err) + } +} + +func TestNewHTTPCACert(t *testing.T) { + server := httptest.NewUnstartedServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", `text/plain; version=0.0.4`) + w.Write([]byte{}) + }, + ), + ) + server.TLS = newTLSConfig(t) + server.StartTLS() + defer server.Close() + + cfg := &config.ScrapeConfig{ + ScrapeTimeout: config.Duration(1 * time.Second), + CACert: "testdata/ca.cer", + } + c, err := newHTTPClient(cfg) + if err != nil { + t.Fatal(err) + } + _, err = c.Get(server.URL) + if err != nil { + t.Fatal(err) + } +} + +func TestNewHTTPClientCert(t *testing.T) { + server := httptest.NewUnstartedServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", `text/plain; version=0.0.4`) + w.Write([]byte{}) + }, + ), + ) + tlsConfig := newTLSConfig(t) + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + tlsConfig.ClientCAs = tlsConfig.RootCAs + tlsConfig.BuildNameToCertificate() + server.TLS = tlsConfig + server.StartTLS() + defer server.Close() + + cfg := &config.ScrapeConfig{ + ScrapeTimeout: config.Duration(1 * time.Second), + CACert: "testdata/ca.cer", + ClientCert: &config.ClientCert{ + Cert: "testdata/client.cer", + Key: "testdata/client.key", + }, + } + c, err := newHTTPClient(cfg) + if err != nil { + t.Fatal(err) + } + _, err = c.Get(server.URL) + if err != nil { + t.Fatal(err) + } +} + +func newTLSConfig(t *testing.T) *tls.Config { + tlsConfig := &tls.Config{} + caCertPool := x509.NewCertPool() + caCert, err := ioutil.ReadFile("testdata/ca.cer") + if err != nil { + t.Fatalf("Couldn't set up TLS server: %v", err) + } + caCertPool.AppendCertsFromPEM(caCert) + tlsConfig.RootCAs = caCertPool + tlsConfig.ServerName = "127.0.0.1" + cert, err := tls.LoadX509KeyPair("testdata/server.cer", "testdata/server.key") + if err != nil { + t.Errorf("Unable to use specified server cert (%s) & key (%v): %s", "testdata/server.cer", "testdata/server.key") + } + tlsConfig.Certificates = []tls.Certificate{cert} + tlsConfig.BuildNameToCertificate() + return tlsConfig +} diff --git a/retrieval/testdata/bearertoken.txt b/retrieval/testdata/bearertoken.txt new file mode 100644 index 000000000..e56e15bb7 --- /dev/null +++ b/retrieval/testdata/bearertoken.txt @@ -0,0 +1 @@ +12345 diff --git a/retrieval/testdata/ca.cer b/retrieval/testdata/ca.cer new file mode 100644 index 000000000..86f627a90 --- /dev/null +++ b/retrieval/testdata/ca.cer @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkTCCAnmgAwIBAgIJAJNsnimNN3tmMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV +BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg +Q29tcGFueSBMdGQxGzAZBgNVBAMMElByb21ldGhldXMgVGVzdCBDQTAeFw0xNTA4 +MDQxNDA5MjFaFw0yNTA4MDExNDA5MjFaMF8xCzAJBgNVBAYTAlhYMRUwEwYDVQQH +DAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQxGzAZ +BgNVBAMMElByb21ldGhldXMgVGVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAOlSBU3yWpUELbhzizznR0hnAL7dbEHzfEtEc6N3PoSvMNcqrUVq +t4kjBRWzqkZ5uJVkzBPERKEBoOI9pWcrqtMTBkMzHJY2Ep7GHTab10e9KC2IFQT6 +FKP/jCYixaIVx3azEfajRJooD8r79FGoagWUfHdHyCFWJb/iLt8z8+S91kelSRMS +yB9M1ypWomzBz1UFXZp1oiNO5o7/dgXW4MgLUfC2obJ9j5xqpc6GkhWMW4ZFwEr/ +VLjuzxG9B8tLfQuhnXKGn1W8+WzZVWCWMD/sLfZfmjKaWlwcXzL51g8E+IEIBJqV +w51aMI6lDkcvAM7gLq1auLZMVXyKWSKw7XMCAwEAAaNQME4wHQYDVR0OBBYEFMz1 +BZnlqxJp2HiJSjHK8IsLrWYbMB8GA1UdIwQYMBaAFMz1BZnlqxJp2HiJSjHK8IsL +rWYbMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAI2iA3w3TK5J15Pu +e4fPFB4jxQqsbUwuyXbCCv/jKLeFNCD4BjM181WZEYjPMumeTBVzU3aF45LWQIG1 +0DJcrCL4mjMz9qgAoGqA7aDDXiJGbukMgYYsn7vrnVmrZH8T3E8ySlltr7+W578k +pJ5FxnbCroQwn0zLyVB3sFbS8E3vpBr3L8oy8PwPHhIScexcNVc3V6/m4vTZsXTH +U+vUm1XhDgpDcFMTg2QQiJbfpOYUkwIgnRDAT7t282t2KQWtnlqc3zwPQ1F/6Cpx +j19JeNsaF1DArkD7YlyKj/GhZLtHwFHG5cxznH0mLDJTW7bQvqqh2iQTeXmBk1lU +mM5lH/s= +-----END CERTIFICATE----- diff --git a/retrieval/testdata/ca.key b/retrieval/testdata/ca.key new file mode 100644 index 000000000..1db260037 --- /dev/null +++ b/retrieval/testdata/ca.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEA6VIFTfJalQQtuHOLPOdHSGcAvt1sQfN8S0Rzo3c+hK8w1yqt +RWq3iSMFFbOqRnm4lWTME8REoQGg4j2lZyuq0xMGQzMcljYSnsYdNpvXR70oLYgV +BPoUo/+MJiLFohXHdrMR9qNEmigPyvv0UahqBZR8d0fIIVYlv+Iu3zPz5L3WR6VJ +ExLIH0zXKlaibMHPVQVdmnWiI07mjv92BdbgyAtR8Lahsn2PnGqlzoaSFYxbhkXA +Sv9UuO7PEb0Hy0t9C6GdcoafVbz5bNlVYJYwP+wt9l+aMppaXBxfMvnWDwT4gQgE +mpXDnVowjqUORy8AzuAurVq4tkxVfIpZIrDtcwIDAQABAoIBAQCcVDd3pYWpyLX1 +m31UnkX1rgYi3Gs3uTOznra4dSIvds6LrG2SUFGPEibLBql1NQNHHdVa/StakaPB +UrqraOe5K0sL5Ygm4S4Ssf1K5JoW2Be+gipLPmBsDcJSnwO6eUs/LfZAQd6qR2Nl +hvGJcQUwne/TYAYox/bdHWh4Zu/odz4NrZKZLbnXkdLLDEhZbjA0HpwJZ7NpMcB7 +Z6NayOm5dAZncfqBjY+3GNL0VjvDjwwYbESM8GkAbojMgcpODGk0h9arRWCP2RqT +SVgmiFI2mVT7sW1XLdVXmyCL2jzak7sktpbLbVgngwOrBmLO/m4NBftzcZrgvxj3 +YakCPH/hAoGBAP1v85pIxqWr5dFdRlOW0MG35ifL+YXpavcs233jGDHYNZefrR5q +Mw8eA20zwj41OdryqGh58nLYm3zYM0vPFrRJrzWYQfcWDmQELAylr9z9vsMj8gRq +IZQD6wzFmLi1PN2QDmovF+2y/CLAq03XK6FQlNsVQxubfjh4hcX5+nXDAoGBAOut +/pQaIBbIhaI8y3KjpizU24jxIkV8R/q1yE5V01YCl2OC5hEd4iZP14YLDRXLSHKT +e/dyJ/OEyTIzUeDg0ZF3ao9ugbWuASgrnrrdPEooi7C9n9PeaLFTK5oVZoVP2A7E +BwhSFW3VdEzQkdJczVE2jOY6JdBKMndjoDQnhT6RAoGBAL4WMO1gdnYeZ0JQJoZd +kPgrOZpR2DaDa3I3F+3k3enM0+2EmzE70E4fYcyPTLqh62H4LS4ngRx4sK7D7j2G +9u2EcsDNEXUE+wgzROK7hxtGysTMeiKrg8Hj6nFq53Bqp1s7SESGS/lCDPD398Rr +hdL5gJyN5waW6uXqJ9Pk+eFHAoGBAKV/YGcV1XTKSPT9ZgxRmM6ghq0qT1umA1Gt +t0QzBp2+Yhqx/+cDKhynMnxhZEXqoyw6HvJLSny5wSMsYJHeratNxRmFizZOQ2e3 +AdbMppqY0EdDUWnRI4lqExM3de+let4bj6irI3smSm3qhIvJOTCPcu/04zrZ74hh +AE2/dtTRAoGBAO6bENEqLgxZOvX5NnbytTuuoEnbceUPiIvc6S/nWJPEoGXVN2EJ +a3OaIOQmknE6bjXIWrHTaXJhwejvPUz9DVa4GxU5aJhs4gpocVGf+owQFvk4nJO8 +JL+QVVdXp3XdrXIGyvXJfy0fXXgJg5czrnDHjSTE8/2POtyuZ6VyBtQc +-----END RSA PRIVATE KEY----- diff --git a/retrieval/testdata/client.cer b/retrieval/testdata/client.cer new file mode 100644 index 000000000..eeeeca940 --- /dev/null +++ b/retrieval/testdata/client.cer @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIERjCCAy6gAwIBAgIBZDANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJYWDEV +MBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkg +THRkMRswGQYDVQQDDBJQcm9tZXRoZXVzIFRlc3QgQ0EwHhcNMTUwODA0MTQ0MTE2 +WhcNNDIxMjIwMTQ0MTE2WjBVMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVs +dCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMREwDwYDVQQDDAh0 +ZXN0dXNlcjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOKBBXx35X9+ +BLGqY/cC2+lQYZzn13Z8ZEDrUKpv5n91QA0B/YZE3gDSnk2yry8dxmp1NJtXm8Wr +rIQSBnsTGOKwyIwR1gcggUYPD9fCyy7T7y7YbzBG8drEcxiK/YIWyio0fpRCfT9b +2+fOEeY+0+tgFV++XjbXVzXRCBMmsZ22cOm4t2t7GHKBZhYoUoPgKjDn+4t/rr0r +1od6yVOocYCo6RruQHsWPHj6QlU8VGutkD7PpvLS+w2l/6JqmZDHlY6o6pDidC8a +kp8i/t3pNBlexk6st/8YZ5S9j6LjqC6bUnerUZB40b6L8OXXwWS3S5y6t07A1QIn +Pv2DZKGbn8Uuj7RvS5OAZdDn1P+M5aVlRLoYbdTHJILrLg+bxyDIokqONbLgj78A +FT6a013eJAZJBkeoaN7Djbf/d5FjRDadH2bX0Uur3APh4cbv+0Fo13CPPSckA9EU +o42qBmKLWys858D8vRKyS/mq/IeRL0AIwKuaEIJtPtiwCTnk6PvFfQvO80z/Eyq+ +uvRBoZbrWHb+3GR8rNzu8Gc1UbTC+jnGYtbQhxx1/7nae52XGRpplnwPO9cb+px2 +Zf802h+lP3SMY/XS+nyTAp/jcy/jOAwrZKY4rgz+5ZmKCI61NZ0iovaK7Jqo9qTM +iSjykZCamFhm4pg8itECD5FhnUetJ6axAgMBAAGjFzAVMBMGA1UdJQQMMAoGCCsG +AQUFBwMCMA0GCSqGSIb3DQEBBQUAA4IBAQDEQyFfY9WAkdnzb+vIlICgfkydceYx +KVJZ2WRMvrn2ZoRoSaK3CfGlz4nrCOgDjQxfX8OpKzudr/ghuBQCbDHHzxRrOen5 +0Zig9Q+pxTZNrtds/SwX2dHJ7PVEwGxXXaKl8S19bNEdO0syFrRJU6I50ZbeEkJe +RI9IEFvBHcuG/GnEfqWj2ozI/+VhIOb4cTItg67ClmIPe8lteT2wj+/aydF9PKgF +QhooCe/G1nok1uiaGjo1HzFEn4HzI3s4mrolc8PpBBVsS+HckCOrHpRPWnYuCFEm +0yzS6tGaMrnITywwB2/uJ2aBAZIx2Go1zFhPf0YvFJc3e2x8cAuqBRLu +-----END CERTIFICATE----- diff --git a/retrieval/testdata/client.key b/retrieval/testdata/client.key new file mode 100644 index 000000000..e584b7ead --- /dev/null +++ b/retrieval/testdata/client.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/retrieval/testdata/server.cer b/retrieval/testdata/server.cer new file mode 100644 index 000000000..8dcb154d7 --- /dev/null +++ b/retrieval/testdata/server.cer @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSzCCAjOgAwIBAgIJAPn0lI/95RQVMA0GCSqGSIb3DQEBBQUAMF8xCzAJBgNV +BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg +Q29tcGFueSBMdGQxGzAZBgNVBAMMElByb21ldGhldXMgVGVzdCBDQTAeFw0xNTA4 +MDQxNDE5MjRaFw00MjEyMjAxNDE5MjRaMFYxCzAJBgNVBAYTAlhYMRUwEwYDVQQH +DAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQxEjAQ +BgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AMQhH0walZlA+Gy5ZB3YzzxZta7mhTX3P+yBeQ6G6yrei4H7gv+MTCJj5qUBc+BS +cta8loKKUQWjoppjyh4tz8awkTD5sEyedE7/G3DS7mLgmx0PslwqrkXFBQhm/C2f +aZfSO69TZ8uu1dgCmmGe9K2XqPnR6fu9egtLpK8RT0s/Cx04bFnaPS0ecyj+3q7A +xzDsH84Z1KPo4LHgqNWlHqFsQPqH+7W9ajhF6lnO4ArEDJ3KuLDlgrENzCsDabls +0U2XsccBJzP+Ls+iQwMfKpx2ISQDHqniopSICw+sPufiAv+OGnnG6rGGWQjUstqf +w4DnU4DZvkrcEWoGa6fq26kCAwEAAaMTMBEwDwYDVR0RBAgwBocEfwAAATANBgkq +hkiG9w0BAQUFAAOCAQEAVPs8IZffawWuRqbXJSvFz7a1q95febWQFjvvMe8ZJeCZ +y1k9laQ5ZLHYuQ6NUWn09UbQNtK3fCLF4sJx5PCPCp1vZWx4nJs8N5mNyqdQ1Zfk +oyoYTOR2izNcIj6ZUFRoOR/7B9hl2JouCXrbExr96oO13xIfsdslScINz1X68oyW +KjU0yUrY+lWG1zEkUGXti9K6ujtXa7YY2n3nK/CvIqny5nVToYUgEMpjUR9S+KgN +JUtawY3VQKyp6ZXlHqa0ihsuvY9Hrlh14h0AsZchPAHUtDFv2nEQob/Kf1XynKw6 +itVKcj/UFpkhsnc/19aP1gWje76fejXl0tzyPXDXFg== +-----END CERTIFICATE----- diff --git a/retrieval/testdata/server.key b/retrieval/testdata/server.key new file mode 100644 index 000000000..2266b0150 --- /dev/null +++ b/retrieval/testdata/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAxCEfTBqVmUD4bLlkHdjPPFm1ruaFNfc/7IF5DobrKt6LgfuC +/4xMImPmpQFz4FJy1ryWgopRBaOimmPKHi3PxrCRMPmwTJ50Tv8bcNLuYuCbHQ+y +XCquRcUFCGb8LZ9pl9I7r1Nny67V2AKaYZ70rZeo+dHp+716C0ukrxFPSz8LHThs +Wdo9LR5zKP7ersDHMOwfzhnUo+jgseCo1aUeoWxA+of7tb1qOEXqWc7gCsQMncq4 +sOWCsQ3MKwNpuWzRTZexxwEnM/4uz6JDAx8qnHYhJAMeqeKilIgLD6w+5+IC/44a +ecbqsYZZCNSy2p/DgOdTgNm+StwRagZrp+rbqQIDAQABAoIBACeOjqNo0TdhtTko +gxrJ+bIwXcZy0/c4cPogeuwFJjU1QWnr8lXcVBazk3dAPcDGoEbTLoARqZm7kTYW +XlOL5dYrEn2QPpCVfNvZ9AzjXhUvO9m2qsCQEyobPJKfQslo14E5c7Q+3DZmgtbY +X47E4pCIgBoyzkBpzM2uaf6tPRLtv8QcLklcf7lP5rd0Zypc325RR6+J5nxfCoFp +fD3sj7t/lJLS8Xb6m4/YFjsVJ2qEAelZ086v8unMBEj324Vv/VqrkPFtFNJKI+Az +Pd9xFDBdsKijBn1Yam9/dj7CiyZYKaVZ9p/w7Oqkpbrt8J8S8OtNHZ4fz9FJgRu9 +uu+VTikCgYEA5ZkDmozDseA/c9JTUGAiPfAt5OrnqlKQNzp2m19GKh+Mlwg4k6O5 +uE+0vaQEfc0cX3o8qntWNsb63XC9h6oHewrdyVFMZNS4nzzmKEvGWt9ON6qfQDUs +1cgZ0Y/uKydDX/3hk/hnJbeRW429rk0/GTuSHHilBzhE0uXJ11xPG48CgYEA2q7a +yqTdqPmZFIAYT9ny099PhnGYE6cJljTUMX9Xhk4POqcigcq9kvNNsly2O1t0Eq0H +2tYo91xTCZc3Cb0N+Vx3meLIljnzhEtwzU9w6W5VGJHWiqovjGwtCdm/W28OlMzY +zM+0gVCJzZLhL0vOwBLwGUJvjgfpvgIb/W+C2UcCgYB5TJ3ayQOath7P0g6yKBfv +ITUd+/zovzXx97Ex5OPs3T4pjO5XEejMt0+F4WF+FR8oUiw65W5nAjkHRMjdI7dQ +Ci2ibpEttDTV7Bass1vYJqHsRvhbs7w8NbtuO9xYcCXoUPkcc+AKzTC+beQIckcj +zZUj9Zk6dz/lLAG3Bc3FgQKBgQC+MmZI6auAU9Y4ZlC+4qi4bfkUzaefMCC+a6RC +iKbvQOUt9j+k81h+fu6MuuYkKh6CP8wdITbwLXRrWwGbjrqgrzO2u/AJ+M07uwGZ +EAb8f+GzROR8JhjE4TEq6B/uvmDIOoI1YFF2Rz4TdjQ0lpJzrAT3czjjJy68+8is +XFhJ8QKBgQCMPpB7taMLQzuilEGabL6Xas9UxryiGoBHk4Umb107GVWgwXxWT6fk +YSlvbMQHCgVeaJe374Bghyw33Z3WilWM1fCWya/CxXlw9wakjQHiqFCIOCxdgosX +Sr35bRFWJMnHXD+jD0Vr8WrtbGzFSZb3ZrjT6WhWRIGCHcaMANN9ew== +-----END RSA PRIVATE KEY----- diff --git a/util/httputil/client.go b/util/httputil/client.go new file mode 100644 index 000000000..8311d184b --- /dev/null +++ b/util/httputil/client.go @@ -0,0 +1,86 @@ +// 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 ( + "net" + "net/http" + "time" +) + +// NewClient returns a http.Client using the specified http.RoundTripper. +func NewClient(rt http.RoundTripper) *http.Client { + return &http.Client{Transport: rt} +} + +// NewDeadlineClient returns a new http.Client which will time out long running +// requests. +func NewDeadlineClient(timeout time.Duration) *http.Client { + return NewClient(NewDeadlineRoundTripper(timeout)) +} + +// NewDeadlineRoundTripper returns a new http.RoundTripper which will time out +// long running requests. +func NewDeadlineRoundTripper(timeout time.Duration) http.RoundTripper { + return &http.Transport{ + // We need to disable keepalive, because we set a deadline on the + // underlying connection. + DisableKeepAlives: true, + Dial: func(netw, addr string) (c net.Conn, err error) { + start := time.Now() + + c, err = net.DialTimeout(netw, addr, timeout) + + if err == nil { + c.SetDeadline(start.Add(timeout)) + } + + return + }, + } +} + +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) +} + +// 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 +} diff --git a/util/httputil/deadline_client.go b/util/httputil/deadline_client.go deleted file mode 100644 index 05379b9c5..000000000 --- a/util/httputil/deadline_client.go +++ /dev/null @@ -1,43 +0,0 @@ -// 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 ( - "net" - "net/http" - "time" -) - -// NewDeadlineClient returns a new http.Client which will time out long running -// requests. -func NewDeadlineClient(timeout time.Duration) *http.Client { - return &http.Client{ - Transport: &http.Transport{ - // We need to disable keepalive, because we set a deadline on the - // underlying connection. - DisableKeepAlives: true, - Dial: func(netw, addr string) (c net.Conn, err error) { - start := time.Now() - - c, err = net.DialTimeout(netw, addr, timeout) - - if err == nil { - c.SetDeadline(start.Add(timeout)) - } - - return - }, - }, - } -}