diff --git a/cmd/kv.go b/cmd/kv.go index 031e56b..5bca9f8 100644 --- a/cmd/kv.go +++ b/cmd/kv.go @@ -1,6 +1,11 @@ package cmd import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "os" "time" "github.com/abronan/valkeyrie/store" @@ -24,10 +29,12 @@ func init() { kvCmd.PersistentFlags().String("password", "", "Password for connection.") kvCmd.PersistentFlags().String("username", "", "Username for connection.") - // FIXME review TLS parts - // kvCmd.PersistentFlags().Bool("tls.enable", false, "Enable TLS encryption.") - // kvCmd.PersistentFlags().Bool("tls.insecureskipverify", false, "Trust unverified certificates if TLS is enabled.") - // kvCmd.PersistentFlags().String("tls.ca-cert-file", "", "Root CA file for certificate verification if TLS is enabled.") + kvCmd.PersistentFlags().Bool("tls", false, "Enable TLS encryption.") + kvCmd.PersistentFlags().String("tls.ca", "", "Root CA for certificate verification if TLS is enabled") + kvCmd.PersistentFlags().Bool("tls.ca.optional", false, "") + kvCmd.PersistentFlags().String("tls.cert", "", "TLS cert") + kvCmd.PersistentFlags().String("tls.key", "", "TLS key") + kvCmd.PersistentFlags().Bool("tls.insecureskipverify", false, "Trust unverified certificates if TLS is enabled.") } func getKvConfig(cmd *cobra.Command) (*kv.Config, error) { @@ -41,6 +48,11 @@ func getKvConfig(cmd *cobra.Command) (*kv.Config, error) { return nil, err } + tlsConfig, err := createTLSConfig(cmd) + if err != nil { + return nil, err + } + return &kv.Config{ Endpoints: endpoints, Prefix: cmd.Flag("prefix").Value.String(), @@ -48,6 +60,115 @@ func getKvConfig(cmd *cobra.Command) (*kv.Config, error) { ConnectionTimeout: time.Duration(connectionTimeout) * time.Second, Username: cmd.Flag("password").Value.String(), Password: cmd.Flag("username").Value.String(), + TLS: tlsConfig, }, }, nil } + +func createTLSConfig(cmd *cobra.Command) (*tls.Config, error) { + enable, _ := cmd.Flags().GetBool("tls") + if !enable { + return nil, nil + } + + ca := cmd.Flag("tls.ca").Value.String() + caPool, err := getCertPool(ca) + if err != nil { + return nil, err + } + + caOptional, _ := cmd.Flags().GetBool("tls.ca.optional") + clientAuth := getClientAuth(ca, caOptional) + + insecureSkipVerify, _ := cmd.Flags().GetBool("tls.insecureskipverify") + privateKey := cmd.Flag("tls.key").Value.String() + certContent := cmd.Flag("tls.cert").Value.String() + + if !insecureSkipVerify && (len(certContent) == 0 || len(privateKey) == 0) { + return nil, fmt.Errorf("TLS Certificate or Key file must be set when TLS configuration is created") + } + + cert, err := getCertificate(privateKey, certContent) + if err != nil { + return nil, fmt.Errorf("failed to load TLS keypair: %s", err) + } + + return &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: caPool, + InsecureSkipVerify: insecureSkipVerify, + ClientAuth: clientAuth, + }, nil +} + +func getCertPool(ca string) (*x509.CertPool, error) { + caPool := x509.NewCertPool() + + if ca != "" { + caContent, err := getCAContent(ca) + if err != nil { + return nil, fmt.Errorf("failed to read CA. %s", err) + } + + if !caPool.AppendCertsFromPEM(caContent) { + return nil, fmt.Errorf("failed to parse CA") + } + } + + return caPool, nil +} + +func getCAContent(ca string) ([]byte, error) { + if _, errCA := os.Stat(ca); errCA != nil { + return []byte(ca), nil + } + + caContent, err := ioutil.ReadFile(ca) + if err != nil { + return nil, err + } + return caContent, nil +} + +func getClientAuth(ca string, caOptional bool) tls.ClientAuthType { + if ca == "" { + return tls.NoClientCert + } + + if caOptional { + return tls.VerifyClientCertIfGiven + } + return tls.RequireAndVerifyClientCert +} + +func getCertificate(privateKey, certContent string) (tls.Certificate, error) { + if certContent == "" || privateKey == "" { + return tls.Certificate{}, nil + } + + _, errKeyIsFile := os.Stat(privateKey) + _, errCertIsFile := os.Stat(certContent) + + if errCertIsFile == nil && os.IsNotExist(errKeyIsFile) { + return tls.Certificate{}, fmt.Errorf("tls cert is a file, but tls key is not") + } + + if os.IsNotExist(errCertIsFile) && errKeyIsFile == nil { + return tls.Certificate{}, fmt.Errorf("TLS key is a file, but tls cert is not") + } + + // string + if os.IsNotExist(errCertIsFile) && os.IsNotExist(errKeyIsFile) { + return tls.X509KeyPair([]byte(certContent), []byte(privateKey)) + } + + // files + if errCertIsFile == nil && errKeyIsFile == nil { + return tls.LoadX509KeyPair(certContent, privateKey) + } + + if errCertIsFile != nil { + return tls.Certificate{}, errCertIsFile + } + return tls.Certificate{}, errKeyIsFile +} diff --git a/docs/traefik-certs-dumper_kv.md b/docs/traefik-certs-dumper_kv.md index ff66cee..edf097b 100644 --- a/docs/traefik-certs-dumper_kv.md +++ b/docs/traefik-certs-dumper_kv.md @@ -14,6 +14,12 @@ Dump the content of a KV store. -h, --help help for kv --password string Password for connection. --prefix string Prefix used for KV store. (default "traefik") + --tls Enable TLS encryption. + --tls.ca string Root CA for certificate verification if TLS is enabled + --tls.ca.optional + --tls.cert string TLS cert + --tls.insecureskipverify Trust unverified certificates if TLS is enabled. + --tls.key string TLS key --username string Username for connection. ``` diff --git a/docs/traefik-certs-dumper_kv_boltdb.md b/docs/traefik-certs-dumper_kv_boltdb.md index bfc8cfe..43a9506 100644 --- a/docs/traefik-certs-dumper_kv_boltdb.md +++ b/docs/traefik-certs-dumper_kv_boltdb.md @@ -33,6 +33,12 @@ traefik-certs-dumper kv boltdb [flags] --key-name string The file name (without extension) of the generated private keys. (default "privatekey") --password string Password for connection. --prefix string Prefix used for KV store. (default "traefik") + --tls Enable TLS encryption. + --tls.ca string Root CA for certificate verification if TLS is enabled + --tls.ca.optional + --tls.cert string TLS cert + --tls.insecureskipverify Trust unverified certificates if TLS is enabled. + --tls.key string TLS key --username string Username for connection. --watch Enable watching changes. ``` diff --git a/docs/traefik-certs-dumper_kv_consul.md b/docs/traefik-certs-dumper_kv_consul.md index b0a5e4c..4072d2c 100644 --- a/docs/traefik-certs-dumper_kv_consul.md +++ b/docs/traefik-certs-dumper_kv_consul.md @@ -32,6 +32,12 @@ traefik-certs-dumper kv consul [flags] --key-name string The file name (without extension) of the generated private keys. (default "privatekey") --password string Password for connection. --prefix string Prefix used for KV store. (default "traefik") + --tls Enable TLS encryption. + --tls.ca string Root CA for certificate verification if TLS is enabled + --tls.ca.optional + --tls.cert string TLS cert + --tls.insecureskipverify Trust unverified certificates if TLS is enabled. + --tls.key string TLS key --username string Username for connection. --watch Enable watching changes. ``` diff --git a/docs/traefik-certs-dumper_kv_etcd.md b/docs/traefik-certs-dumper_kv_etcd.md index 236d1b9..7c16cf0 100644 --- a/docs/traefik-certs-dumper_kv_etcd.md +++ b/docs/traefik-certs-dumper_kv_etcd.md @@ -32,6 +32,12 @@ traefik-certs-dumper kv etcd [flags] --key-name string The file name (without extension) of the generated private keys. (default "privatekey") --password string Password for connection. --prefix string Prefix used for KV store. (default "traefik") + --tls Enable TLS encryption. + --tls.ca string Root CA for certificate verification if TLS is enabled + --tls.ca.optional + --tls.cert string TLS cert + --tls.insecureskipverify Trust unverified certificates if TLS is enabled. + --tls.key string TLS key --username string Username for connection. --watch Enable watching changes. ``` diff --git a/docs/traefik-certs-dumper_kv_zookeeper.md b/docs/traefik-certs-dumper_kv_zookeeper.md index f2837f6..3326a85 100644 --- a/docs/traefik-certs-dumper_kv_zookeeper.md +++ b/docs/traefik-certs-dumper_kv_zookeeper.md @@ -31,6 +31,12 @@ traefik-certs-dumper kv zookeeper [flags] --key-name string The file name (without extension) of the generated private keys. (default "privatekey") --password string Password for connection. --prefix string Prefix used for KV store. (default "traefik") + --tls Enable TLS encryption. + --tls.ca string Root CA for certificate verification if TLS is enabled + --tls.ca.optional + --tls.cert string TLS cert + --tls.insecureskipverify Trust unverified certificates if TLS is enabled. + --tls.key string TLS key --username string Username for connection. --watch Enable watching changes. ```