Move TLS options to scrape config

Fixes #1013, fixes #989
This commit is contained in:
Jimmi Dyson 2015-09-07 00:07:44 +01:00
parent 1ef5ed0cf2
commit a1574aa2b3
9 changed files with 127 additions and 96 deletions

View file

@ -108,13 +108,18 @@ func checkConfig(t cli.Term, filename string) ([]string, error) {
return nil, fmt.Errorf("error checking bearer token file %q: %s", scfg.BearerTokenFile, err) return nil, fmt.Errorf("error checking bearer token file %q: %s", scfg.BearerTokenFile, err)
} }
if scfg.ClientCert != nil { if err := check(scfg.TLSConfig.CertFile); err != nil {
if err := check(scfg.ClientCert.Cert); err != nil { return nil, fmt.Errorf("error checking client cert file %q: %s", scfg.TLSConfig.CertFile, err)
return nil, fmt.Errorf("error checking client cert file %q: %s", scfg.ClientCert.Cert, err) }
} if err := check(scfg.TLSConfig.KeyFile); err != nil {
if err := check(scfg.ClientCert.Key); err != nil { return nil, fmt.Errorf("error checking client key file %q: %s", scfg.TLSConfig.KeyFile, err)
return nil, fmt.Errorf("error checking client key file %q: %s", scfg.ClientCert.Key, err) }
}
if len(scfg.TLSConfig.CertFile) > 0 && len(scfg.TLSConfig.KeyFile) == 0 {
return nil, fmt.Errorf("client cert file %s specified without client key file", scfg.TLSConfig.CertFile)
}
if len(scfg.TLSConfig.KeyFile) > 0 && len(scfg.TLSConfig.CertFile) == 0 {
return nil, fmt.Errorf("client key file %s specified without client cert file", scfg.TLSConfig.KeyFile)
} }
} }

View file

@ -187,10 +187,15 @@ func resolveFilepaths(baseDir string, cfg *Config) {
for _, scfg := range cfg.ScrapeConfigs { for _, scfg := range cfg.ScrapeConfigs {
scfg.BearerTokenFile = join(scfg.BearerTokenFile) scfg.BearerTokenFile = join(scfg.BearerTokenFile)
scfg.TLSConfig.CAFile = join(scfg.TLSConfig.CAFile)
scfg.TLSConfig.CertFile = join(scfg.TLSConfig.CertFile)
scfg.TLSConfig.KeyFile = join(scfg.TLSConfig.KeyFile)
if scfg.ClientCert != nil { for _, kcfg := range scfg.KubernetesSDConfigs {
scfg.ClientCert.Cert = join(scfg.ClientCert.Cert) kcfg.BearerTokenFile = join(kcfg.BearerTokenFile)
scfg.ClientCert.Key = join(scfg.ClientCert.Key) kcfg.TLSConfig.CAFile = join(kcfg.TLSConfig.CAFile)
kcfg.TLSConfig.CertFile = join(kcfg.TLSConfig.CertFile)
kcfg.TLSConfig.KeyFile = join(kcfg.TLSConfig.KeyFile)
} }
} }
} }
@ -293,6 +298,18 @@ func (c *GlobalConfig) isZero() bool {
c.EvaluationInterval == 0 c.EvaluationInterval == 0
} }
// TLSConfig configures the options for TLS connections.
type TLSConfig struct {
// The CA cert to use for the targets.
CAFile string `yaml:"ca_file,omitempty"`
// The client cert file for the targets.
CertFile string `yaml:"cert_file,omitempty"`
// The client key file for the targets.
KeyFile string `yaml:"key_file,omitempty"`
// Disable target certificate validation.
InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
}
// ScrapeConfig configures a scraping unit for Prometheus. // ScrapeConfig configures a scraping unit for Prometheus.
type ScrapeConfig struct { type ScrapeConfig struct {
// The job name to which the job label is set by default. // The job name to which the job label is set by default.
@ -315,12 +332,10 @@ type ScrapeConfig struct {
BearerToken string `yaml:"bearer_token,omitempty"` BearerToken string `yaml:"bearer_token,omitempty"`
// The bearer token file for the targets. // The bearer token file for the targets.
BearerTokenFile string `yaml:"bearer_token_file,omitempty"` 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"`
// HTTP proxy server to use to connect to the targets. // HTTP proxy server to use to connect to the targets.
ProxyURL URL `yaml:"proxy_url,omitempty"` ProxyURL URL `yaml:"proxy_url,omitempty"`
// Inlined TLSConfig.
TLSConfig TLSConfig `yaml:"tls_config,omitempty"`
// List of labeled target groups for this job. // List of labeled target groups for this job.
TargetGroups []*TargetGroup `yaml:"target_groups,omitempty"` TargetGroups []*TargetGroup `yaml:"target_groups,omitempty"`
@ -605,18 +620,15 @@ type MarathonSDConfig struct {
// KubernetesSDConfig is the configuration for Kubernetes service discovery. // KubernetesSDConfig is the configuration for Kubernetes service discovery.
type KubernetesSDConfig struct { type KubernetesSDConfig struct {
Masters []URL `yaml:"masters"` Masters []URL `yaml:"masters"`
KubeletPort int `yaml:"kubelet_port,omitempty"` KubeletPort int `yaml:"kubelet_port,omitempty"`
InCluster bool `yaml:"in_cluster,omitempty"` InCluster bool `yaml:"in_cluster,omitempty"`
BearerTokenFile string `yaml:"bearer_token_file,omitempty"` BearerTokenFile string `yaml:"bearer_token_file,omitempty"`
Username string `yaml:"username,omitempty"` Username string `yaml:"username,omitempty"`
Password string `yaml:"password,omitempty"` Password string `yaml:"password,omitempty"`
Insecure bool `yaml:"insecure,omitempty"` RetryInterval Duration `yaml:"retry_interval,omitempty"`
CertFile string `yaml:"cert_file,omitempty"` RequestTimeout Duration `yaml:"request_timeout,omitempty"`
KeyFile string `yaml:"key_file,omitempty"` TLSConfig TLSConfig `yaml:"tls_config,omitempty"`
CAFile string `yaml:"ca_file,omitempty"`
RetryInterval Duration `yaml:"retry_interval,omitempty"`
RequestTimeout Duration `yaml:"request_timeout,omitempty"`
// Catches all undefined fields and must be empty after parsing. // Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"` XXX map[string]interface{} `yaml:",inline"`

View file

@ -180,10 +180,11 @@ var expectedConf = &Config{
MetricsPath: "/metrics", MetricsPath: "/metrics",
Scheme: "http", Scheme: "http",
ClientCert: &ClientCert{ TLSConfig: TLSConfig{
Cert: "testdata/valid_cert_file", CertFile: "testdata/valid_cert_file",
Key: "testdata/valid_key_file", KeyFile: "testdata/valid_key_file",
}, },
BearerToken: "avalidtoken", BearerToken: "avalidtoken",
}, },
{ {

View file

@ -94,9 +94,9 @@ scrape_configs:
- job_name: service-z - job_name: service-z
client_cert: tls_config:
cert: valid_cert_file cert_file: valid_cert_file
key: valid_key_file key_file: valid_key_file
bearer_token: avalidtoken bearer_token: avalidtoken

View file

@ -8,7 +8,9 @@ global:
scrape_configs: scrape_configs:
- job_name: 'kubernetes' - job_name: 'kubernetes'
ca_cert: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs: kubernetes_sd_configs:

View file

@ -14,8 +14,6 @@
package kubernetes package kubernetes
import ( import (
"crypto/tls"
"crypto/x509"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -574,7 +572,7 @@ func (kd *Discovery) updateServiceEndpoints(endpoints *Endpoints, eventType Even
func newKubernetesHTTPClient(conf *config.KubernetesSDConfig) (*http.Client, error) { func newKubernetesHTTPClient(conf *config.KubernetesSDConfig) (*http.Client, error) {
bearerTokenFile := conf.BearerTokenFile bearerTokenFile := conf.BearerTokenFile
caFile := conf.CAFile caFile := conf.TLSConfig.CAFile
if conf.InCluster { if conf.InCluster {
if len(bearerTokenFile) == 0 { if len(bearerTokenFile) == 0 {
bearerTokenFile = serviceAccountToken bearerTokenFile = serviceAccountToken
@ -582,46 +580,31 @@ func newKubernetesHTTPClient(conf *config.KubernetesSDConfig) (*http.Client, err
if len(caFile) == 0 { if len(caFile) == 0 {
// With recent versions, the CA certificate is provided as a token // With recent versions, the CA certificate is provided as a token
// but we need to handle older versions too. In this case, don't // but we need to handle older versions too. In this case, don't
// set the CAFile & the configuration will have to use Insecure. // set the CAFile & the configuration will have to use InsecureSkipVerify.
if _, err := os.Stat(serviceAccountCACert); err == nil { if _, err := os.Stat(serviceAccountCACert); err == nil {
caFile = serviceAccountCACert caFile = serviceAccountCACert
} }
} }
} }
tlsConfig := &tls.Config{InsecureSkipVerify: conf.Insecure} tlsOpts := httputil.TLSOptions{
InsecureSkipVerify: conf.TLSConfig.InsecureSkipVerify,
// Load client cert if specified. CAFile: caFile,
if len(conf.CertFile) > 0 && len(conf.KeyFile) > 0 { CertFile: conf.TLSConfig.CertFile,
cert, err := tls.LoadX509KeyPair(conf.CertFile, conf.KeyFile) KeyFile: conf.TLSConfig.KeyFile,
if err != nil { }
return nil, err tlsConfig, err := httputil.NewTLSConfig(tlsOpts)
} if err != nil {
tlsConfig.Certificates = []tls.Certificate{cert} return nil, err
} }
caCertPool := x509.NewCertPool() var rt http.RoundTripper = &http.Transport{
if len(caFile) > 0 {
// Load CA cert.
caCert, err := ioutil.ReadFile(caFile)
if err != nil {
return nil, err
}
caCertPool.AppendCertsFromPEM(caCert)
}
tlsConfig.RootCAs = caCertPool
tlsConfig.BuildNameToCertificate()
tr := &http.Transport{
Dial: func(netw, addr string) (c net.Conn, err error) { Dial: func(netw, addr string) (c net.Conn, err error) {
c, err = net.DialTimeout(netw, addr, time.Duration(conf.RequestTimeout)) c, err = net.DialTimeout(netw, addr, time.Duration(conf.RequestTimeout))
return return
}, },
TLSClientConfig: tlsConfig,
} }
tr.TLSClientConfig = tlsConfig
var rt http.RoundTripper
rt = tr
bearerToken, err := ioutil.ReadFile(bearerTokenFile) bearerToken, err := ioutil.ReadFile(bearerTokenFile)
if err != nil { if err != nil {

View file

@ -14,8 +14,6 @@
package retrieval package retrieval
import ( import (
"crypto/tls"
"crypto/x509"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -249,33 +247,21 @@ func (t *Target) Update(cfg *config.ScrapeConfig, baseLabels, metaLabels model.L
} }
func newHTTPClient(cfg *config.ScrapeConfig) (*http.Client, error) { 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), cfg.ProxyURL.URL) rt := httputil.NewDeadlineRoundTripper(time.Duration(cfg.ScrapeTimeout), cfg.ProxyURL.URL)
tlsOpts := httputil.TLSOptions{
InsecureSkipVerify: cfg.TLSConfig.InsecureSkipVerify,
CAFile: cfg.TLSConfig.CAFile,
}
if len(cfg.TLSConfig.CertFile) > 0 && len(cfg.TLSConfig.KeyFile) > 0 {
tlsOpts.CertFile = cfg.TLSConfig.CertFile
tlsOpts.KeyFile = cfg.TLSConfig.KeyFile
}
tlsConfig, err := httputil.NewTLSConfig(tlsOpts)
if err != nil {
return nil, err
}
// Get a default roundtripper with the scrape timeout.
tr := rt.(*http.Transport) tr := rt.(*http.Transport)
// Set the TLS config from above // Set the TLS config from above
tr.TLSClientConfig = tlsConfig tr.TLSClientConfig = tlsConfig

View file

@ -563,7 +563,9 @@ func TestNewHTTPCACert(t *testing.T) {
cfg := &config.ScrapeConfig{ cfg := &config.ScrapeConfig{
ScrapeTimeout: config.Duration(1 * time.Second), ScrapeTimeout: config.Duration(1 * time.Second),
CACert: "testdata/ca.cer", TLSConfig: config.TLSConfig{
CAFile: "testdata/ca.cer",
},
} }
c, err := newHTTPClient(cfg) c, err := newHTTPClient(cfg)
if err != nil { if err != nil {
@ -594,10 +596,10 @@ func TestNewHTTPClientCert(t *testing.T) {
cfg := &config.ScrapeConfig{ cfg := &config.ScrapeConfig{
ScrapeTimeout: config.Duration(1 * time.Second), ScrapeTimeout: config.Duration(1 * time.Second),
CACert: "testdata/ca.cer", TLSConfig: config.TLSConfig{
ClientCert: &config.ClientCert{ CAFile: "testdata/ca.cer",
Cert: "testdata/client.cer", CertFile: "testdata/client.cer",
Key: "testdata/client.key", KeyFile: "testdata/client.key",
}, },
} }
c, err := newHTTPClient(cfg) c, err := newHTTPClient(cfg)

View file

@ -14,6 +14,10 @@
package httputil package httputil
import ( import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
@ -108,3 +112,39 @@ func cloneRequest(r *http.Request) *http.Request {
} }
return r2 return r2
} }
type TLSOptions struct {
InsecureSkipVerify bool
CAFile string
CertFile string
KeyFile string
}
func NewTLSConfig(opts TLSOptions) (*tls.Config, error) {
tlsConfig := &tls.Config{InsecureSkipVerify: opts.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(opts.CAFile) > 0 {
caCertPool := x509.NewCertPool()
// Load CA cert.
caCert, err := ioutil.ReadFile(opts.CAFile)
if err != nil {
return nil, fmt.Errorf("unable to use specified CA cert %s: %s", opts.CAFile, err)
}
caCertPool.AppendCertsFromPEM(caCert)
tlsConfig.RootCAs = caCertPool
}
// If a client cert & key is provided then configure TLS config accordingly.
if len(opts.CertFile) > 0 && len(opts.KeyFile) > 0 {
cert, err := tls.LoadX509KeyPair(opts.CertFile, opts.KeyFile)
if err != nil {
return nil, fmt.Errorf("unable to use specified client cert (%s) & key (%s): %s", opts.CertFile, opts.KeyFile, err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
tlsConfig.BuildNameToCertificate()
return tlsConfig, nil
}