diff --git a/client.go b/client.go index af2c279..573112d 100644 --- a/client.go +++ b/client.go @@ -13,13 +13,11 @@ type Client struct { es *elasticsearch.Client } -func NewClient(addresses []string, insecure bool) (*Client, error) { +func NewClient(addresses []string, tlsClientConfig *tls.Config) (*Client, error) { cfg := elasticsearch.Config{ Addresses: addresses, Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: insecure, - }, + TLSClientConfig: tlsClientConfig, }, } diff --git a/collector.go b/collector.go index c319eec..64654c8 100644 --- a/collector.go +++ b/collector.go @@ -1,6 +1,7 @@ package main import ( + "crypto/tls" "fmt" "regexp" "strings" @@ -21,12 +22,12 @@ type Collector struct { docsCount *prometheus.Desc } -func NewCollector(address, project string, insecure bool) (*Collector, error) { +func NewCollector(address, project string, tlsClientConfig *tls.Config) (*Collector, error) { namespace := "oneday_elasticsearch" labels := []string{"index", "index_group"} labels_group := []string{"index_group"} - client, err := NewClient([]string{address}, insecure) + client, err := NewClient([]string{address}, tlsClientConfig) if err != nil { return nil, fmt.Errorf("error creating the client: %v", err) } diff --git a/main.go b/main.go index 374c652..9c72565 100644 --- a/main.go +++ b/main.go @@ -39,7 +39,13 @@ var ( address = kingpin.Flag("address", "Elasticsearch node to use."). Default("http://localhost:9200").String() - insecure = kingpin.Flag("insecure", "Allow insecure server connections when using SSL."). + cacert = kingpin.Flag("ca-cert", "Path to PEM file that contains trusted Certificate Authorities for the Elasticsearch connection."). + Default("").String() + clientcert = kingpin.Flag("client-cert", "Path to PEM file that contains the corresponding cert for the private key to connect to Elasticsearch."). + Default("").String() + clientkey = kingpin.Flag("client-key", "Path to PEM file that contains the private key for client auth when connecting to Elasticsearch."). + Default("").String() + insecure = kingpin.Flag("insecure", "Skip SSL verification when connecting to Elasticsearch."). Default("false").Bool() projectName = kingpin.Flag("project", "Project name").String() @@ -88,7 +94,9 @@ func main() { log.Info("Starting es-oneday-exporter", version.Info()) log.Info("Build context", version.BuildContext()) - collector, err := NewCollector(*address, *projectName, *insecure) + tlsClientConfig := createTLSConfig(*cacert, *clientcert, *clientkey, *insecure) + + collector, err := NewCollector(*address, *projectName, tlsClientConfig) if err != nil { log.Fatal("error creating new collector instance: ", err) } diff --git a/tls.go b/tls.go new file mode 100644 index 0000000..c096e9a --- /dev/null +++ b/tls.go @@ -0,0 +1,68 @@ +// Copyright 2021 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 main + +import ( + "crypto/tls" + "crypto/x509" + "io/ioutil" +) + +func createTLSConfig(pemFile, pemCertFile, pemPrivateKeyFile string, insecureSkipVerify bool) *tls.Config { + tlsConfig := tls.Config{} + if insecureSkipVerify { + // pem settings are irrelevant if we're skipping verification anyway + tlsConfig.InsecureSkipVerify = true + } + if len(pemFile) > 0 { + rootCerts, err := loadCertificatesFrom(pemFile) + if err != nil { + log.Fatalf("Couldn't load root certificate from %s. Got %s.", pemFile, err) + return nil + } + tlsConfig.RootCAs = rootCerts + } + if len(pemCertFile) > 0 && len(pemPrivateKeyFile) > 0 { + // Load files once to catch configuration error early. + _, err := loadPrivateKeyFrom(pemCertFile, pemPrivateKeyFile) + if err != nil { + log.Fatalf("Couldn't setup client authentication. Got %s.", err) + return nil + } + // Define a function to load certificate and key lazily at TLS handshake to + // ensure that the latest files are used in case they have been rotated. + tlsConfig.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { + return loadPrivateKeyFrom(pemCertFile, pemPrivateKeyFile) + } + } + return &tlsConfig +} + +func loadCertificatesFrom(pemFile string) (*x509.CertPool, error) { + caCert, err := ioutil.ReadFile(pemFile) + if err != nil { + return nil, err + } + certificates := x509.NewCertPool() + certificates.AppendCertsFromPEM(caCert) + return certificates, nil +} + +func loadPrivateKeyFrom(pemCertFile, pemPrivateKeyFile string) (*tls.Certificate, error) { + privateKey, err := tls.LoadX509KeyPair(pemCertFile, pemPrivateKeyFile) + if err != nil { + return nil, err + } + return &privateKey, nil +}