Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/tlsenc'
Browse files Browse the repository at this point in the history
  • Loading branch information
bustedware committed Oct 26, 2023
2 parents 64ae51e + 6a6115b commit 1161eaa
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 73 deletions.
10 changes: 10 additions & 0 deletions docs/TLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@ For client TLS support we have the following options:

## The public and private keypairs for the client encoded in PEM format. May
## contain intermediate certificates.
## Configuration for separate cert and key files
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
# passphrase for encrypted private key, if it is in PKCS#8 format. Encrypted PKCS#1 private keys are not supported.
# tls_key_pwd = "changeme"
## Configuration for single file containing both cert and key
# tls_cert_key = "/etc/telegraf/client.pem"
## For encrypted key files, supply a password for decryption
# tls_key_password = "***"
## Skip TLS verification.
# insecure_skip_verify = false
## Send the specified TLS server name via SNI.
Expand All @@ -47,10 +52,15 @@ The server TLS configuration provides support for TLS mutual authentication:
# tls_allowed_dns_names = ["client.example.org"]

## Add service certificate and key.
## Configuration for separate cert and key files
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
# passphrase for encrypted private key, if it is in PKCS#8 format. Encrypted PKCS#1 private keys are not supported.
# tls_key_pwd = "changeme"
## Configuration for single file containing both cert and key
# tls_cert_key = "/etc/telegraf/client.pem"
## For encrypted key files, supply a password for decryption
# tls_key_password = "***"
```

#### Advanced Configuration
Expand Down
70 changes: 26 additions & 44 deletions plugins/common/tls/config.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
package tls

import (
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"os"
"strings"

"github.com/influxdata/telegraf/internal/choice"
"github.com/youmark/pkcs8"
)

const TLSMinVersionDefault = tls.VersionTLS12
Expand All @@ -21,7 +17,9 @@ type ClientConfig struct {
TLSCA string `toml:"tls_ca"`
TLSCert string `toml:"tls_cert"`
TLSKey string `toml:"tls_key"`
TLSKeyPwd string `toml:"tls_key_pwd"`
TLSCertAndKey string `toml:"tls_cert_key"`
TLSKeyPwd string `toml:"tls_key_pwd" deprecated:"1.22.0;use 'tls_key_password' instead"`
TLSKeyPassword string `toml:"tls_key_password"`
TLSMinVersion string `toml:"tls_min_version"`
InsecureSkipVerify bool `toml:"insecure_skip_verify"`
ServerName string `toml:"tls_server_name"`
Expand All @@ -37,7 +35,9 @@ type ClientConfig struct {
type ServerConfig struct {
TLSCert string `toml:"tls_cert"`
TLSKey string `toml:"tls_key"`
TLSKeyPwd string `toml:"tls_key_pwd"`
TLSCertAndKey string `toml:"tls_cert_key"`
TLSKeyPwd string `toml:"tls_key_pwd" deprecated:"1.22.0;use 'tls_key_password' instead"`
TLSKeyPassword string `toml:"tls_key_password"`
TLSAllowedCACerts []string `toml:"tls_allowed_cacerts"`
TLSCipherSuites []string `toml:"tls_cipher_suites"`
TLSMinVersion string `toml:"tls_min_version"`
Expand Down Expand Up @@ -72,7 +72,7 @@ func (c *ClientConfig) TLSConfig() (*tls.Config, error) {
// * disabled security,
// * an SNI server name, or
// * empty/never renegotiation method
empty := c.TLSCA == "" && c.TLSKey == "" && c.TLSCert == ""
empty := c.TLSCA == "" && c.TLSKey == "" && c.TLSCert == "" && c.TLSCertAndKey == ""
empty = empty && !c.InsecureSkipVerify && c.ServerName == ""
empty = empty && (c.RenegotiationMethod == "" || c.RenegotiationMethod == "never")

Expand Down Expand Up @@ -111,6 +111,11 @@ func (c *ClientConfig) TLSConfig() (*tls.Config, error) {
tlsConfig.RootCAs = pool
}

if c.TLSCertAndKey != "" {
c.TLSCert = c.TLSCertAndKey
c.TLSKey = c.TLSCertAndKey
}

if c.TLSCert != "" && c.TLSKey != "" {
err := loadCertificate(tlsConfig, c.TLSCert, c.TLSKey, c.TLSKeyPwd)
if err != nil {
Expand Down Expand Up @@ -141,7 +146,7 @@ func (c *ClientConfig) TLSConfig() (*tls.Config, error) {
// TLSConfig returns a tls.Config, may be nil without error if TLS is not
// configured.
func (c *ServerConfig) TLSConfig() (*tls.Config, error) {
if c.TLSCert == "" && c.TLSKey == "" && len(c.TLSAllowedCACerts) == 0 {
if c.TLSCert == "" && c.TLSKey == "" && c.TLSCertAndKey == "" && len(c.TLSAllowedCACerts) == 0 {
return nil, nil
}

Expand All @@ -156,6 +161,11 @@ func (c *ServerConfig) TLSConfig() (*tls.Config, error) {
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
}

if c.TLSCertAndKey != "" {
c.TLSCert = c.TLSCertAndKey
c.TLSKey = c.TLSCertAndKey
}

if c.TLSCert != "" && c.TLSKey != "" {
err := loadCertificate(tlsConfig, c.TLSCert, c.TLSKey, c.TLSKeyPwd)
if err != nil {
Expand Down Expand Up @@ -222,44 +232,16 @@ func makeCertPool(certFiles []string) (*x509.CertPool, error) {
}

func loadCertificate(config *tls.Config, certFile, keyFile, privateKeyPassphrase string) error {
certBytes, err := os.ReadFile(certFile)
if err != nil {
return fmt.Errorf("could not load certificate %q: %w", certFile, err)
}

keyBytes, err := os.ReadFile(keyFile)
if err != nil {
return fmt.Errorf("could not load private key %q: %w", keyFile, err)
}

keyPEMBlock, _ := pem.Decode(keyBytes)
if keyPEMBlock == nil {
return errors.New("failed to decode private key: no PEM data found")
}

var cert tls.Certificate
if keyPEMBlock.Type == "ENCRYPTED PRIVATE KEY" {
if privateKeyPassphrase == "" {
return errors.New("missing password for PKCS#8 encrypted private key")
}
var decryptedKey *rsa.PrivateKey
decryptedKey, err = pkcs8.ParsePKCS8PrivateKeyRSA(keyPEMBlock.Bytes, []byte(privateKeyPassphrase))
if err != nil {
return fmt.Errorf("failed to parse encrypted PKCS#8 private key: %w", err)
}
cert, err = tls.X509KeyPair(certBytes, pem.EncodeToMemory(&pem.Block{Type: keyPEMBlock.Type, Bytes: x509.MarshalPKCS1PrivateKey(decryptedKey)}))
if err != nil {
return fmt.Errorf("failed to load cert/key pair: %w", err)
}
} else if keyPEMBlock.Headers["Proc-Type"] == "4,ENCRYPTED" {
// The key is an encrypted private key with the DEK-Info header.
// This is currently unsupported because of the deprecation of x509.IsEncryptedPEMBlock and x509.DecryptPEMBlock.
return fmt.Errorf("password-protected keys in pkcs#1 format are not supported")
var err error
if privateKeyPassphrase != "" {
cert, err = tls.X509KeyPair([]byte(ReadCertificate(certFile)), []byte(ReadKey(keyFile, privateKeyPassphrase)))
} else {
cert, err = tls.X509KeyPair(certBytes, keyBytes)
if err != nil {
return fmt.Errorf("failed to load cert/key pair: %w", err)
}
cert, err = tls.LoadX509KeyPair(certFile, keyFile)
}
if err != nil {
return fmt.Errorf(
"could not load keypair %s:%s: %v", certFile, keyFile, err)
}
config.Certificates = []tls.Certificate{cert}
return nil
Expand Down
21 changes: 15 additions & 6 deletions plugins/common/tls/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ func TestClientConfig(t *testing.T) {
{
name: "success with tls key password set",
client: tls.ClientConfig{
TLSCA: pki.CACertPath(),
TLSCert: pki.ClientCertPath(),
TLSKey: pki.ClientKeyPath(),
TLSKeyPwd: "",
TLSCA: pki.CACertPath(),
TLSCert: pki.ClientCertPath(),
TLSKey: pki.ClientKeyPath(),
TLSKeyPassword: "",
},
},
{
Expand Down Expand Up @@ -93,7 +93,6 @@ func TestClientConfig(t *testing.T) {
expNil: true,
expErr: true,
},

{
name: "invalid ca",
client: tls.ClientConfig{
Expand Down Expand Up @@ -203,7 +202,17 @@ func TestServerConfig(t *testing.T) {
server: tls.ServerConfig{
TLSCert: pki.ServerCertPath(),
TLSKey: pki.ServerKeyPath(),
TLSKeyPwd: "",
TLSKeyPassword: "",
TLSAllowedCACerts: []string{pki.CACertPath()},
TLSCipherSuites: []string{pki.CipherSuite()},
TLSMinVersion: pki.TLSMinVersion(),
TLSMaxVersion: pki.TLSMaxVersion(),
},
},
{
name: "success with cert and key",
server: tls.ServerConfig{
TLSCertAndKey: pki.ServerCertAndKeyPath(),
TLSAllowedCACerts: []string{pki.CACertPath()},
TLSCipherSuites: []string{pki.CipherSuite()},
TLSMinVersion: pki.TLSMinVersion(),
Expand Down
62 changes: 62 additions & 0 deletions plugins/common/tls/utils.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package tls

import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"os"
"sort"
"strings"

"github.com/youmark/pkcs8"
)

// ParseCiphers returns a `[]uint16` by received `[]string` key that represents ciphers from crypto/tls.
Expand Down Expand Up @@ -36,3 +43,58 @@ func ParseTLSVersion(version string) (uint16, error) {
sort.Strings(available)
return 0, fmt.Errorf("unsupported version %q (available: %s)", version, strings.Join(available, ","))
}

func readFile(filename string) []byte {
octets, err := os.ReadFile(filename)
if err != nil {
panic(fmt.Sprintf("reading %q: %v", filename, err))
}
return octets
}

func ReadCertificate(filename string) string {
octets := readFile(filename)
return string(octets)
}

func ReadKey(filename string, password string) string {
keyBytes := readFile(filename)
currentBlock, remainingBlocks := pem.Decode(keyBytes)
if currentBlock == nil {
panic(errors.New("failed to decode private key: no PEM data found"))
}
var allBlocks string
for {
if currentBlock.Type == "ENCRYPTED PRIVATE KEY" {
if password == "" {
panic(errors.New("missing password for PKCS#8 encrypted private key"))
}
var decryptedKey *rsa.PrivateKey
decryptedKey, err := pkcs8.ParsePKCS8PrivateKeyRSA(currentBlock.Bytes, []byte(password))
if err != nil {
panic(fmt.Errorf("failed to parse encrypted PKCS#8 private key: %w", err))
}
pemBlock := string(pem.EncodeToMemory(&pem.Block{Type: currentBlock.Type, Bytes: x509.MarshalPKCS1PrivateKey(decryptedKey)}))
allBlocks += pemBlock
} else if currentBlock.Headers["Proc-Type"] == "4,ENCRYPTED" {
decryptedKeyDER, err := x509.DecryptPEMBlock(currentBlock, []byte(password))
if err != nil {
panic(fmt.Errorf("failed to parse encrypted private key %w", err))
}
decryptedKey, err := x509.ParsePKCS1PrivateKey(decryptedKeyDER)
if err != nil {
panic(fmt.Errorf("unable to convert from DER to PEM format: %w", err))
}
pemBlock := string(pem.EncodeToMemory(&pem.Block{Type: currentBlock.Type, Bytes: x509.MarshalPKCS1PrivateKey(decryptedKey)}))
allBlocks += pemBlock
} else {
pemBlock := string(pem.EncodeToMemory(&pem.Block{Type: currentBlock.Type, Bytes: currentBlock.Bytes}))
allBlocks += pemBlock
}
currentBlock, remainingBlocks = pem.Decode(remainingBlocks)
if currentBlock == nil {
break
}
}
return allBlocks
}
2 changes: 1 addition & 1 deletion plugins/outputs/mongodb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ to use them.
# authentication = "X509"
# tls_ca = "ca.pem"
# tls_key = "client.pem"
# # tls_key_pwd = "changeme" # required for encrypted tls_key
# # tls_key_password = "changeme" # required for encrypted tls_key
# insecure_skip_verify = false

# database to store measurements and time series collections
Expand Down
3 changes: 3 additions & 0 deletions plugins/outputs/mongodb/mongodb.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ func (s *MongoDB) Init() error {
if s.TLSKeyPwd != "" {
q.Set("sslClientCertificateKeyPassword", s.TLSKeyPwd)
}
if s.TLSKeyPassword != "" {
q.Set("sslClientCertificateKeyPassword", s.TLSKeyPassword)
}
newConnectionString.RawQuery = q.Encode()
s.Dsn = newConnectionString.String()
// always auth source $external
Expand Down
31 changes: 31 additions & 0 deletions testutil/pki/serverenc.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIB+TCCAWKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQDDBBUZWxl
Z3JhZiBUZXN0IENBMB4XDTE4MDUwMzAxMDUyOVoXDTI4MDQzMDAxMDUyOVowHTEb
MBkGA1UEAwwSc2VydmVyLmxvY2FsZG9tYWluMIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDTBmLJ0pBFUxnPkkx38sBnOKvs+OinVqxTnVcc1iCyQJQleB37uY6D
L55mSsPvnad/oDpyGpHt4RVtrhmyC6ptSrWLyk7mraeAo30Cooqr5tA9A+6yj0ij
ySLlYimTMQy8tbnVNWLwKbxgT9N4NlUzwyqxLWUMfRzLfmefqzk5bQIDAQABo0sw
STAJBgNVHRMEAjAAMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATALBgNVHQ8E
BAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADgYEATNnM
ol0s29lJ+WkP+HUFtKaXxQ+kXLADqfhsk2G1/kZAVRHsYUDlJ+GkHnWIHlg/ggIP
JS+z44iwMPOtzJQI7MvAFYVKpYAEdIFTjXf6GafLjUfoXYi0vwHoVJHtQu3Kpm9L
Ugm02h0ycIadN8RdWAAFUf6XpVKUJa0YYLuyaXY=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,D380549EA9DF81C470A9DB93CBB21B66

tVa+a2cCBl7rggevzh7zp8i0YJgoe5yCQe76jZ3/LA5E/Th7KWc+aRm7H1NClhVc
5Ny4ChXyiyL8nmCWaaVl7s2AMckdQOEsTRb7FR3hq8tmfQIr2Vy/yaBeCaU7+cCK
R4KofgNM5W83IjIuKhXf0nE1vMAMpzftLCThtpDyAyZ1AHau5XllmRsqvSM0UR5p
N0OVPnTZJkggIb/alaGFGE+6frTkI0RQmeHc5CfUeO8PxBdWZUeWx+vrnAn3LuyF
j5G9gMnEKDWKonyd114JcrDjobm9pErCFGTwiGPulRVvMKXLYhzvfnYxxLZT4Jf+
vOZ8SUufliWbXcoPRd3KXQeTDPgkb9VtJgqL2PuOF3/H4X0InjF/qDnf4gFGoELY
Ctbce9EvcQh0+zYI1PJIqhy1zqxNpBnI9QtCjjwPXixQYy8BSgd5IUaXWkZ8g38E
7saH/8nCvAI0EuELb0p/3+Vr6/4HvuBaL7X8yff5RUHIXPafuqnF5X2MkXFr9DUw
7tVRispVadPxQ+NP62LYaJsgycGlYHGvWAYrlWylL8mH3dGGsxGm3qnQCDPsWhkW
+DRFFTPLkgSIj+HEjATIk8IkLS+5I5/TXw8h3WuhghOXED+7BDSPCRk8zY/8Sfmz
C63vWy5FWOrrbYqwfnfn91IQdaxy03IDaUD1xMQK1rCkZJlY10eE/Zwz5Ne0hexa
whM2gjfp7l9Ktvw2jY7R+YXvKusY8FLytZoq+gkqr5HnjkWQVgHipHcBukf6Yq2U
WWr61Bgx5jHjLYGBu/s8RY7u1qZ+uPbsOXFTIqGyvmHRUqVxnt01+KtB7s3AD0HH
-----END RSA PRIVATE KEY-----
18 changes: 18 additions & 0 deletions testutil/pki/serverenckey.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,D380549EA9DF81C470A9DB93CBB21B66

tVa+a2cCBl7rggevzh7zp8i0YJgoe5yCQe76jZ3/LA5E/Th7KWc+aRm7H1NClhVc
5Ny4ChXyiyL8nmCWaaVl7s2AMckdQOEsTRb7FR3hq8tmfQIr2Vy/yaBeCaU7+cCK
R4KofgNM5W83IjIuKhXf0nE1vMAMpzftLCThtpDyAyZ1AHau5XllmRsqvSM0UR5p
N0OVPnTZJkggIb/alaGFGE+6frTkI0RQmeHc5CfUeO8PxBdWZUeWx+vrnAn3LuyF
j5G9gMnEKDWKonyd114JcrDjobm9pErCFGTwiGPulRVvMKXLYhzvfnYxxLZT4Jf+
vOZ8SUufliWbXcoPRd3KXQeTDPgkb9VtJgqL2PuOF3/H4X0InjF/qDnf4gFGoELY
Ctbce9EvcQh0+zYI1PJIqhy1zqxNpBnI9QtCjjwPXixQYy8BSgd5IUaXWkZ8g38E
7saH/8nCvAI0EuELb0p/3+Vr6/4HvuBaL7X8yff5RUHIXPafuqnF5X2MkXFr9DUw
7tVRispVadPxQ+NP62LYaJsgycGlYHGvWAYrlWylL8mH3dGGsxGm3qnQCDPsWhkW
+DRFFTPLkgSIj+HEjATIk8IkLS+5I5/TXw8h3WuhghOXED+7BDSPCRk8zY/8Sfmz
C63vWy5FWOrrbYqwfnfn91IQdaxy03IDaUD1xMQK1rCkZJlY10eE/Zwz5Ne0hexa
whM2gjfp7l9Ktvw2jY7R+YXvKusY8FLytZoq+gkqr5HnjkWQVgHipHcBukf6Yq2U
WWr61Bgx5jHjLYGBu/s8RY7u1qZ+uPbsOXFTIqGyvmHRUqVxnt01+KtB7s3AD0HH
-----END RSA PRIVATE KEY-----
8 changes: 6 additions & 2 deletions testutil/pki/tls-certs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,24 @@ openssl req -x509 -config ./openssl.conf -days 3650 -newkey rsa:2048 -out ./cert
openssl genrsa -out ./private/serverkey.pem 2048 &&
openssl req -new -key ./private/serverkey.pem -out ./certs/servercsr.pem -outform PEM -subj "/CN=$(cat /proc/sys/kernel/hostname)/O=server/" &&
openssl ca -config ./openssl.conf -in ./certs/servercsr.pem -out ./certs/servercert.pem -notext -batch -extensions server_ca_extensions &&
openssl ca -config ./openssl.conf -in ./certs/servercsr.pem -out ./certs/servercertexp.pem -startdate "$(date +%y%m%d%H%M00 --date='-5 minutes')Z" -enddate "$(date +%y%m%d%H%M00 --date='5 minutes')Z" -notext -batch -extensions server_ca_extensions &&
openssl ca -config ./openssl.conf -in ./certs/servercsr.pem -out ./certs/servercertexp.pem -startdate $(date +%y%m%d%H%M00 --date='-5 minutes')'Z' -enddate $(date +%y%m%d%H%M00 --date='5 minutes')'Z' -notext -batch -extensions server_ca_extensions &&
cp ./private/serverkey.pem ./private/serverkeyenc.pem &&
ssh-keygen -p -f ./private/serverkeyenc.pem -m PEM -N 'changeme' &&

# Create client and client encrypted keypair
openssl genrsa -out ./private/clientkey.pem 2048 &&
openssl req -new -key ./private/clientkey.pem -out ./certs/clientcsr.pem -outform PEM -subj "/CN=$(cat /proc/sys/kernel/hostname)/O=client/" &&
openssl ca -config ./openssl.conf -in ./certs/clientcsr.pem -out ./certs/clientcert.pem -notext -batch -extensions client_ca_extensions &&
cp ./private/clientkey.pem ./private/clientenckey.pem &&
ssh-keygen -p -f ./private/clientenckey.pem -m PEM -N 'changeme' &&

# Generate a pkcs#8 encrypted private key using pkcs#5 v2.0 algorithm
openssl pkcs8 -topk8 -v2 des3 -in ./private/clientkey.pem -out ./private/clientenckey.pkcs8.pem -passout pass:changeme &&
openssl pkcs8 -topk8 -in clientenckey.pem -passin pass:changeme -nocrypt -out clientkey.pkcs8.pem &&
ssh-keygen -p -f ./private/clientenckey.pem -m PEM -N 'changeme' &&

# Combine crt and key to create pem formatted keyfile
cat ./certs/clientcert.pem ./private/clientkey.pem > ./private/client.pem &&
cat ./certs/clientcert.pem ./private/clientkeyenc.pem > ./private/clientenc.pem &&
cat ./certs/clientcert.pem ./private/clientenckey.pem > ./private/clientenc.pem &&
cat ./certs/servercert.pem ./private/serverkey.pem > ./private/server.pem &&
cat ./certs/servercertexp.pem ./private/serverkey.pem > ./private/serverexp.pem
Loading

0 comments on commit 1161eaa

Please sign in to comment.