Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ components and libraries.
* **expfmt**: Decoding and encoding for the exposition format
* **route**: A routing wrapper around [httprouter](https://github.com/julienschmidt/httprouter) using `context.Context`
* **log**: A logging wrapper around [logrus](https://github.com/Sirupsen/logrus)
* **config**: Common configuration structures
30 changes: 30 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2016 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 config

import (
"fmt"
"strings"
)

func checkOverflow(m map[string]interface{}, ctx string) error {
if len(m) > 0 {
var keys []string
for k := range m {
keys = append(keys, k)
}
return fmt.Errorf("unknown fields in %s: %s", ctx, strings.Join(keys, ", "))
}
return nil
}
1 change: 1 addition & 0 deletions config/testdata/tls_config.cert_no_key.bad.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cert_file: somefile
Empty file.
1 change: 1 addition & 0 deletions config/testdata/tls_config.insecure.good.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
insecure_skip_verify: true
1 change: 1 addition & 0 deletions config/testdata/tls_config.invalid_field.bad.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
something_invalid: true
1 change: 1 addition & 0 deletions config/testdata/tls_config.key_no_cert.bad.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
key_file: somefile
79 changes: 79 additions & 0 deletions config/tls_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2016 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 config

import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
)

// 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"`

// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *TLSConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain TLSConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
return checkOverflow(c.XXX, "TLS config")
}

// GenerateConfig produces a tls.Config based on TLS connection options.
// It loads certificate files from disk if they are defined.
func (c *TLSConfig) GenerateConfig() (*tls.Config, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is public so should have a comment describing what it does

tlsConfig := &tls.Config{InsecureSkipVerify: c.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(c.CAFile) > 0 {
caCertPool := x509.NewCertPool()
// Load CA cert.
caCert, err := ioutil.ReadFile(c.CAFile)
if err != nil {
return nil, fmt.Errorf("unable to use specified CA cert %s: %s", c.CAFile, err)
}
caCertPool.AppendCertsFromPEM(caCert)
tlsConfig.RootCAs = caCertPool
}

if len(c.CertFile) > 0 && len(c.KeyFile) == 0 {
return nil, fmt.Errorf("client cert file %q specified without client key file", c.CertFile)
} else if len(c.KeyFile) > 0 && len(c.CertFile) == 0 {
return nil, fmt.Errorf("client key file %q specified without client cert file", c.KeyFile)
} else if len(c.CertFile) > 0 && len(c.KeyFile) > 0 {
cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile)
if err != nil {
return nil, fmt.Errorf("unable to use specified client cert (%s) & key (%s): %s", c.CertFile, c.KeyFile, err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
tlsConfig.BuildNameToCertificate()

return tlsConfig, nil
}
92 changes: 92 additions & 0 deletions config/tls_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright 2016 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 config

import (
"crypto/tls"
"io/ioutil"
"reflect"
"strings"
"testing"

"gopkg.in/yaml.v2"
)

// LoadTLSConfig parses the given YAML file into a tls.Config.
func LoadTLSConfig(filename string) (*tls.Config, error) {
content, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
cfg := &TLSConfig{}
if err = yaml.Unmarshal(content, cfg); err != nil {
return nil, err
}
return cfg.GenerateConfig()
}

var expectedTLSConfigs = []struct {
filename string
config *tls.Config
}{
{
filename: "tls_config.empty.good.yml",
config: &tls.Config{},
}, {
filename: "tls_config.insecure.good.yml",
config: &tls.Config{InsecureSkipVerify: true},
},
}

func TestValidTLSConfig(t *testing.T) {
for _, cfg := range expectedTLSConfigs {
cfg.config.BuildNameToCertificate()
got, err := LoadTLSConfig("testdata/" + cfg.filename)
if err != nil {
t.Errorf("Error parsing %s: %s", cfg.filename, err)
}
if !reflect.DeepEqual(*got, *cfg.config) {
t.Fatalf("%s: unexpected config result: \n\n%s\n expected\n\n%s", cfg.filename, got, cfg.config)
}
}
}

var expectedTLSConfigErrors = []struct {
filename string
errMsg string
}{
{
filename: "tls_config.invalid_field.bad.yml",
errMsg: "unknown fields in",
}, {
filename: "tls_config.cert_no_key.bad.yml",
errMsg: "specified without client key file",
}, {
filename: "tls_config.key_no_cert.bad.yml",
errMsg: "specified without client cert file",
},
}

func TestBadTLSConfigs(t *testing.T) {
for _, ee := range expectedTLSConfigErrors {
_, err := LoadTLSConfig("testdata/" + ee.filename)
if err == nil {
t.Errorf("Expected error parsing %s but got none", ee.filename)
continue
}
if !strings.Contains(err.Error(), ee.errMsg) {
t.Errorf("Expected error for %s to contain %q but got: %s", ee.filename, ee.errMsg, err)
}
}
}