Skip to content

Commit 3763a1d

Browse files
author
Julien Pivotto
authored
TLS config: Enable selection of min TLS version (#375)
* TLS config: Enable selection of min TLS version go1.18 changes the default minimum TLS version to 1.2. Let's make the default minimum version configurable, while following go default. The allowed values (TLS10, ..) come from the exporter-toolkit: https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md TLSVersion is exported so the exporter toolkit can reuse them later. Signed-off-by: Julien Pivotto <[email protected]>
1 parent 0c7319a commit 3763a1d

7 files changed

+122
-26
lines changed

config/http_config.go

+81-20
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,87 @@ import (
3636
"gopkg.in/yaml.v2"
3737
)
3838

39-
// DefaultHTTPClientConfig is the default HTTP client configuration.
40-
var DefaultHTTPClientConfig = HTTPClientConfig{
41-
FollowRedirects: true,
42-
EnableHTTP2: true,
43-
}
39+
var (
40+
// DefaultHTTPClientConfig is the default HTTP client configuration.
41+
DefaultHTTPClientConfig = HTTPClientConfig{
42+
FollowRedirects: true,
43+
EnableHTTP2: true,
44+
}
4445

45-
// defaultHTTPClientOptions holds the default HTTP client options.
46-
var defaultHTTPClientOptions = httpClientOptions{
47-
keepAlivesEnabled: true,
48-
http2Enabled: true,
49-
// 5 minutes is typically above the maximum sane scrape interval. So we can
50-
// use keepalive for all configurations.
51-
idleConnTimeout: 5 * time.Minute,
52-
}
46+
// defaultHTTPClientOptions holds the default HTTP client options.
47+
defaultHTTPClientOptions = httpClientOptions{
48+
keepAlivesEnabled: true,
49+
http2Enabled: true,
50+
// 5 minutes is typically above the maximum sane scrape interval. So we can
51+
// use keepalive for all configurations.
52+
idleConnTimeout: 5 * time.Minute,
53+
}
54+
)
5355

5456
type closeIdler interface {
5557
CloseIdleConnections()
5658
}
5759

60+
type TLSVersion uint16
61+
62+
var TLSVersions = map[string]TLSVersion{
63+
"TLS13": (TLSVersion)(tls.VersionTLS13),
64+
"TLS12": (TLSVersion)(tls.VersionTLS12),
65+
"TLS11": (TLSVersion)(tls.VersionTLS11),
66+
"TLS10": (TLSVersion)(tls.VersionTLS10),
67+
}
68+
69+
func (tv *TLSVersion) UnmarshalYAML(unmarshal func(interface{}) error) error {
70+
var s string
71+
err := unmarshal((*string)(&s))
72+
if err != nil {
73+
return err
74+
}
75+
if v, ok := TLSVersions[s]; ok {
76+
*tv = v
77+
return nil
78+
}
79+
return fmt.Errorf("unknown TLS version: %s", s)
80+
}
81+
82+
func (tv *TLSVersion) MarshalYAML() (interface{}, error) {
83+
if tv != nil || *tv == 0 {
84+
return []byte("null"), nil
85+
}
86+
for s, v := range TLSVersions {
87+
if *tv == v {
88+
return s, nil
89+
}
90+
}
91+
return nil, fmt.Errorf("unknown TLS version: %d", tv)
92+
}
93+
94+
// MarshalJSON implements the json.Unmarshaler interface for TLSVersion.
95+
func (tv *TLSVersion) UnmarshalJSON(data []byte) error {
96+
var s string
97+
if err := json.Unmarshal(data, &s); err != nil {
98+
return err
99+
}
100+
if v, ok := TLSVersions[s]; ok {
101+
*tv = v
102+
return nil
103+
}
104+
return fmt.Errorf("unknown TLS version: %s", s)
105+
}
106+
107+
// MarshalJSON implements the json.Marshaler interface for TLSVersion.
108+
func (tv *TLSVersion) MarshalJSON() ([]byte, error) {
109+
if tv != nil || *tv == 0 {
110+
return []byte("null"), nil
111+
}
112+
for s, v := range TLSVersions {
113+
if *tv == v {
114+
return []byte(s), nil
115+
}
116+
}
117+
return nil, fmt.Errorf("unknown TLS version: %d", tv)
118+
}
119+
58120
// BasicAuth contains basic HTTP authentication credentials.
59121
type BasicAuth struct {
60122
Username string `yaml:"username" json:"username"`
@@ -669,7 +731,10 @@ func cloneRequest(r *http.Request) *http.Request {
669731

670732
// NewTLSConfig creates a new tls.Config from the given TLSConfig.
671733
func NewTLSConfig(cfg *TLSConfig) (*tls.Config, error) {
672-
tlsConfig := &tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify}
734+
tlsConfig := &tls.Config{
735+
InsecureSkipVerify: cfg.InsecureSkipVerify,
736+
MinVersion: uint16(cfg.MinVersion),
737+
}
673738

674739
// If a CA cert is provided then let's read it in so we can validate the
675740
// scrape target's certificate properly.
@@ -714,6 +779,8 @@ type TLSConfig struct {
714779
ServerName string `yaml:"server_name,omitempty" json:"server_name,omitempty"`
715780
// Disable target certificate validation.
716781
InsecureSkipVerify bool `yaml:"insecure_skip_verify" json:"insecure_skip_verify"`
782+
// Minimum TLS version.
783+
MinVersion TLSVersion `yaml:"min_version,omitempty" json:"min_version,omitempty"`
717784
}
718785

719786
// SetDirectory joins any relative file paths with dir.
@@ -726,12 +793,6 @@ func (c *TLSConfig) SetDirectory(dir string) {
726793
c.KeyFile = JoinDir(dir, c.KeyFile)
727794
}
728795

729-
// UnmarshalYAML implements the yaml.Unmarshaler interface.
730-
func (c *TLSConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
731-
type plain TLSConfig
732-
return unmarshal((*plain)(c))
733-
}
734-
735796
// getClientCertificate reads the pair of client cert and key from disk and returns a tls.Certificate.
736797
func (c *TLSConfig) getClientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
737798
cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile)

config/http_config_test.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,8 @@ func TestTLSConfig(t *testing.T) {
627627
CertFile: ClientCertificatePath,
628628
KeyFile: ClientKeyNoPassPath,
629629
ServerName: "localhost",
630-
InsecureSkipVerify: false}
630+
InsecureSkipVerify: false,
631+
}
631632

632633
tlsCAChain, err := ioutil.ReadFile(TLSCAChainPath)
633634
if err != nil {
@@ -640,7 +641,8 @@ func TestTLSConfig(t *testing.T) {
640641
expectedTLSConfig := &tls.Config{
641642
RootCAs: rootCAs,
642643
ServerName: configTLSConfig.ServerName,
643-
InsecureSkipVerify: configTLSConfig.InsecureSkipVerify}
644+
InsecureSkipVerify: configTLSConfig.InsecureSkipVerify,
645+
}
644646

645647
tlsConfig, err := NewTLSConfig(&configTLSConfig)
646648
if err != nil {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"insecure_skip_verify": true}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"min_version": "TLS11"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
min_version: TLS11

config/tls_config_test.go

+33-4
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,39 @@
1414
package config
1515

1616
import (
17+
"bytes"
1718
"crypto/tls"
19+
"fmt"
1820
"io/ioutil"
21+
"path/filepath"
1922
"reflect"
2023
"testing"
2124

25+
"encoding/json"
26+
2227
"gopkg.in/yaml.v2"
2328
)
2429

25-
// LoadTLSConfig parses the given YAML file into a tls.Config.
30+
// LoadTLSConfig parses the given file into a tls.Config.
2631
func LoadTLSConfig(filename string) (*tls.Config, error) {
2732
content, err := ioutil.ReadFile(filename)
2833
if err != nil {
2934
return nil, err
3035
}
3136
cfg := TLSConfig{}
32-
if err = yaml.UnmarshalStrict(content, &cfg); err != nil {
33-
return nil, err
37+
switch filepath.Ext(filename) {
38+
case ".yml":
39+
if err = yaml.UnmarshalStrict(content, &cfg); err != nil {
40+
return nil, err
41+
}
42+
case ".json":
43+
decoder := json.NewDecoder(bytes.NewReader(content))
44+
decoder.DisallowUnknownFields()
45+
if err = decoder.Decode(&cfg); err != nil {
46+
return nil, err
47+
}
48+
default:
49+
return nil, fmt.Errorf("Unknown extension: %s", filepath.Ext(filename))
3450
}
3551
return NewTLSConfig(&cfg)
3652
}
@@ -39,20 +55,33 @@ var expectedTLSConfigs = []struct {
3955
filename string
4056
config *tls.Config
4157
}{
58+
{
59+
filename: "tls_config.empty.good.json",
60+
config: &tls.Config{},
61+
}, {
62+
filename: "tls_config.insecure.good.json",
63+
config: &tls.Config{InsecureSkipVerify: true},
64+
}, {
65+
filename: "tls_config.tlsversion.good.json",
66+
config: &tls.Config{MinVersion: tls.VersionTLS11},
67+
},
4268
{
4369
filename: "tls_config.empty.good.yml",
4470
config: &tls.Config{},
4571
}, {
4672
filename: "tls_config.insecure.good.yml",
4773
config: &tls.Config{InsecureSkipVerify: true},
74+
}, {
75+
filename: "tls_config.tlsversion.good.yml",
76+
config: &tls.Config{MinVersion: tls.VersionTLS11},
4877
},
4978
}
5079

5180
func TestValidTLSConfig(t *testing.T) {
5281
for _, cfg := range expectedTLSConfigs {
5382
got, err := LoadTLSConfig("testdata/" + cfg.filename)
5483
if err != nil {
55-
t.Errorf("Error parsing %s: %s", cfg.filename, err)
84+
t.Fatalf("Error parsing %s: %s", cfg.filename, err)
5685
}
5786
// non-nil functions are never equal.
5887
got.GetClientCertificate = nil

0 commit comments

Comments
 (0)