Skip to content

Commit

Permalink
Add custom certificate CA support for s3 (#4946)
Browse files Browse the repository at this point in the history
* Add custom certificate CA support for s3

Signed-off-by: clyang82 <chuyang@redhat.com>

* Update CHANGELOG.md and address review comments

Signed-off-by: clyang82 <chuyang@redhat.com>

* fix storage document

Signed-off-by: clyang82 <chuyang@redhat.com>

* define TLSConfig struct

Signed-off-by: clyang82 <chuyang@redhat.com>
  • Loading branch information
clyang82 authored Dec 18, 2021
1 parent 58c4220 commit b60b286
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ We use *breaking :warning:* to mark changes that are not backward compatible (re
- [#4942](https://github.com/thanos-io/thanos/pull/4942) Tracing: add `traceid_128bit` support for jaeger.
- [#4917](https://github.com/thanos-io/thanos/pull/4917) Query: add initial query pushdown for a subset of aggregations. Can be enabled with `--enable-feature=query-pushdown` on Thanos Query.
- [#4888](https://github.com/thanos-io/thanos/pull/4888) Cache: support redis cache backend.
- [#4946](https://github.com/thanos-io/thanos/pull/4946) Store: Support tls_config configuration for the s3 minio client.

### Fixed

Expand Down
8 changes: 8 additions & 0 deletions docs/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ config:
max_idle_conns: 100
max_idle_conns_per_host: 100
max_conns_per_host: 0
tls_config:
ca_file: ""
cert_file: ""
key_file: ""
server_name: ""
insecure_skip_verify: false
trace:
enable: false
list_objects_version: ""
Expand All @@ -105,6 +111,8 @@ Please refer to the documentation of [the Transport type](https://golang.org/pkg

Set `list_objects_version: "v1"` for S3 compatible APIs that don't support ListObjectsV2 (e.g. some versions of Ceph). Default value (`""`) is equivalent to `"v2"`.
`http_config.tls_config` allows configuring TLS connections. Please refer to the document of [tls_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#tls_config) for detailed information on what each option does.

For debug and testing purposes you can set

* `insecure: true` to switch to plain insecure HTTP instead of HTTPS
Expand Down
100 changes: 96 additions & 4 deletions pkg/objstore/s3/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package s3
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"io/ioutil"
Expand Down Expand Up @@ -117,13 +118,100 @@ type HTTPConfig struct {

// Allow upstream callers to inject a round tripper
Transport http.RoundTripper `yaml:"-"`

TLSConfig TLSConfig `yaml:"tls_config"`
}

// NewTLSConfig creates a new tls.Config from the given TLSConfig.
func NewTLSConfig(cfg *TLSConfig) (*tls.Config, error) {
tlsConfig := &tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify}

// If a CA cert is provided then let's read it in.
if len(cfg.CAFile) > 0 {
b, err := readCAFile(cfg.CAFile)
if err != nil {
return nil, err
}
if !updateRootCA(tlsConfig, b) {
return nil, fmt.Errorf("unable to use specified CA cert %s", cfg.CAFile)
}
}

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 {
return nil, fmt.Errorf("client cert file %q specified without client key file", cfg.CertFile)
} else if len(cfg.KeyFile) > 0 && len(cfg.CertFile) == 0 {
return nil, fmt.Errorf("client key file %q specified without client cert file", cfg.KeyFile)
} else if len(cfg.CertFile) > 0 && len(cfg.KeyFile) > 0 {
// Verify that client cert and key are valid.
if _, err := cfg.getClientCertificate(nil); err != nil {
return nil, err
}
tlsConfig.GetClientCertificate = cfg.getClientCertificate
}

return tlsConfig, nil
}

// readCAFile reads the CA cert file from disk.
func readCAFile(f string) ([]byte, error) {
data, err := ioutil.ReadFile(f)
if err != nil {
return nil, fmt.Errorf("unable to load specified CA cert %s: %s", f, err)
}
return data, nil
}

// updateRootCA parses the given byte slice as a series of PEM encoded certificates and updates tls.Config.RootCAs.
func updateRootCA(cfg *tls.Config, b []byte) bool {
caCertPool := x509.NewCertPool()
if !caCertPool.AppendCertsFromPEM(b) {
return false
}
cfg.RootCAs = caCertPool
return true
}

// getClientCertificate reads the pair of client cert and key from disk and returns a tls.Certificate.
func (c *TLSConfig) getClientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
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)
}
return &cert, nil
}

// TLSConfig configures the options for TLS connections.
type TLSConfig struct {
// The CA cert to use for the targets.
CAFile string `yaml:"ca_file"`
// The client cert file for the targets.
CertFile string `yaml:"cert_file"`
// The client key file for the targets.
KeyFile string `yaml:"key_file"`
// Used to verify the hostname for the targets.
ServerName string `yaml:"server_name"`
// Disable target certificate validation.
InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
}

// DefaultTransport - this default transport is based on the Minio
// DefaultTransport up until the following commit:
// https://github.com/minio/minio-go/commit/008c7aa71fc17e11bf980c209a4f8c4d687fc884
// The values have since diverged.
func DefaultTransport(config Config) *http.Transport {
func DefaultTransport(config Config) (*http.Transport, error) {
tlsConfig, err := NewTLSConfig(&config.HTTPConfig.TLSConfig)
if err != nil {
return nil, err
}

if config.HTTPConfig.InsecureSkipVerify {
tlsConfig.InsecureSkipVerify = true
}

return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Expand All @@ -148,8 +236,8 @@ func DefaultTransport(config Config) *http.Transport {
//
// Refer: https://golang.org/src/net/http/transport.go?h=roundTrip#L1843.
DisableCompression: true,
TLSClientConfig: &tls.Config{InsecureSkipVerify: config.HTTPConfig.InsecureSkipVerify},
}
TLSClientConfig: tlsConfig,
}, nil
}

// Bucket implements the store.Bucket interface against s3-compatible APIs.
Expand Down Expand Up @@ -241,7 +329,11 @@ func NewBucketWithConfig(logger log.Logger, config Config, component string) (*B
if config.HTTPConfig.Transport != nil {
rt = config.HTTPConfig.Transport
} else {
rt = DefaultTransport(config)
var err error
rt, err = DefaultTransport(config)
if err != nil {
return nil, err
}
}

client, err := minio.New(config.Endpoint, &minio.Options{
Expand Down
36 changes: 36 additions & 0 deletions pkg/objstore/s3/s3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,42 @@ http_config:
}
}

func TestParseConfig_CustomHTTPConfigWithTLS(t *testing.T) {
input := []byte(`bucket: abcd
insecure: false
http_config:
tls_config:
ca_file: /certs/ca.crt
cert_file: /certs/cert.crt
key_file: /certs/key.key
server_name: server
insecure_skip_verify: false
`)
cfg, err := parseConfig(input)
testutil.Ok(t, err)

testutil.Equals(t, "/certs/ca.crt", cfg.HTTPConfig.TLSConfig.CAFile)
testutil.Equals(t, "/certs/cert.crt", cfg.HTTPConfig.TLSConfig.CertFile)
testutil.Equals(t, "/certs/key.key", cfg.HTTPConfig.TLSConfig.KeyFile)
testutil.Equals(t, "server", cfg.HTTPConfig.TLSConfig.ServerName)
testutil.Equals(t, false, cfg.HTTPConfig.TLSConfig.InsecureSkipVerify)
}

func TestParseConfig_CustomLegacyInsecureSkipVerify(t *testing.T) {
input := []byte(`bucket: abcd
insecure: false
http_config:
insecure_skip_verify: true
tls_config:
insecure_skip_verify: false
`)
cfg, err := parseConfig(input)
testutil.Ok(t, err)
transport, err := DefaultTransport(cfg)
testutil.Ok(t, err)
testutil.Equals(t, true, transport.TLSClientConfig.InsecureSkipVerify)
}

func TestValidate_OK(t *testing.T) {
input := []byte(`bucket: "bucket-name"
endpoint: "s3-endpoint"
Expand Down

0 comments on commit b60b286

Please sign in to comment.