Skip to content

Commit 3f017df

Browse files
authored
[Elastic Agent] Allow embedding of certificate (#21179)
* [Elastic Agent] Allow embedding of certificate This PR allow to embed Certificate authorities directly in the yaml configuration. This is useful in the context of fleet where distributing file to the remote host is not possible. The format of the string need to be in PEM. Example: Certificate Authorities ```yaml enabled: true verification_mode: null certificate: null key: null key_passphrase: null certificate_authorities: - | -----BEGIN CERTIFICATE----- MIIDCjCCAfKgAwIBAgITJ706Mu2wJlKckpIvkWxEHvEyijANBgkqhkiG9w0BAQsF ADAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMTkwNzIyMTkyOTA0WhgPMjExOTA2 MjgxOTI5MDRaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBANce58Y/JykI58iyOXpxGfw0/gMvF0hUQAcUrSMxEO6n fZRA49b4OV4SwWmA3395uL2eB2NB8y8qdQ9muXUdPBWE4l9rMZ6gmfu90N5B5uEl 94NcfBfYOKi1fJQ9i7WKhTjlRkMCgBkWPkUokvBZFRt8RtF7zI77BSEorHGQCk9t /D7BS0GJyfVEhftbWcFEAG3VRcoMhF7kUzYwp+qESoriFRYLeDWv68ZOvG7eoWnP PsvZStEVEimjvK5NSESEQa9xWyJOmlOKXhkdymtcUd/nXnx6UTCFgnkgzSdTWV41 CI6B6aJ9svCTI2QuoIq2HxX/ix7OvW1huVmcyHVxyUECAwEAAaNTMFEwHQYDVR0O BBYEFPwN1OceFGm9v6ux8G+DZ3TUDYxqMB8GA1UdIwQYMBaAFPwN1OceFGm9v6ux 8G+DZ3TUDYxqMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAG5D 874A4YI7YUwOVsVAdbWtgp1d0zKcPRR+r2OdSbTAV5/gcS3jgBJ3i1BN34JuDVFw 3DeJSYT3nxy2Y56lLnxDeF8CUTUtVQx3CuGkRg1ouGAHpO/6OqOhwLLorEmxi7tA H2O8mtT0poX5AnOAhzVy7QW0D/k4WaoLyckM5hUa6RtvgvLxOwA0U+VGurCDoctu 8F4QOgTAWyh8EZIwaKCliFRSynDpv3JTUwtfZkxo6K6nce1RhCWFAsMvDZL8Dgc0 yvgJ38BRsFOtkRuAGSf6ZUwTO8JJRRIFnpUzXflAnGivK9M13D5GEQMmIl6U9Pvk sxSmbIUfc2SGJGCJD4I= -----END CERTIFICATE----- cipher_suites: null curve_types: null supported_protocols: null ``` ```Certificate and Key enabled: true verification_mode: null certificate: | -----BEGIN CERTIFICATE----- MIIDCjCCAfKgAwIBAgITJ706Mu2wJlKckpIvkWxEHvEyijANBgkqhkiG9w0BAQsF ADAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMTkwNzIyMTkyOTA0WhgPMjExOTA2 MjgxOTI5MDRaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBANce58Y/JykI58iyOXpxGfw0/gMvF0hUQAcUrSMxEO6n fZRA49b4OV4SwWmA3395uL2eB2NB8y8qdQ9muXUdPBWE4l9rMZ6gmfu90N5B5uEl 94NcfBfYOKi1fJQ9i7WKhTjlRkMCgBkWPkUokvBZFRt8RtF7zI77BSEorHGQCk9t /D7BS0GJyfVEhftbWcFEAG3VRcoMhF7kUzYwp+qESoriFRYLeDWv68ZOvG7eoWnP PsvZStEVEimjvK5NSESEQa9xWyJOmlOKXhkdymtcUd/nXnx6UTCFgnkgzSdTWV41 CI6B6aJ9svCTI2QuoIq2HxX/ix7OvW1huVmcyHVxyUECAwEAAaNTMFEwHQYDVR0O BBYEFPwN1OceFGm9v6ux8G+DZ3TUDYxqMB8GA1UdIwQYMBaAFPwN1OceFGm9v6ux 8G+DZ3TUDYxqMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAG5D 874A4YI7YUwOVsVAdbWtgp1d0zKcPRR+r2OdSbTAV5/gcS3jgBJ3i1BN34JuDVFw 3DeJSYT3nxy2Y56lLnxDeF8CUTUtVQx3CuGkRg1ouGAHpO/6OqOhwLLorEmxi7tA H2O8mtT0poX5AnOAhzVy7QW0D/k4WaoLyckM5hUa6RtvgvLxOwA0U+VGurCDoctu 8F4QOgTAWyh8EZIwaKCliFRSynDpv3JTUwtfZkxo6K6nce1RhCWFAsMvDZL8Dgc0 yvgJ38BRsFOtkRuAGSf6ZUwTO8JJRRIFnpUzXflAnGivK9M13D5GEQMmIl6U9Pvk sxSmbIUfc2SGJGCJD4I= -----END CERTIFICATE----- key: | -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDXHufGPycpCOfI sjl6cRn8NP4DLxdIVEAHFK0jMRDup32UQOPW+DleEsFpgN9/ebi9ngdjQfMvKnUP Zrl1HTwVhOJfazGeoJn7vdDeQebhJfeDXHwX2DiotXyUPYu1ioU45UZDAoAZFj5F KJLwWRUbfEbRe8yO+wUhKKxxkApPbfw+wUtBicn1RIX7W1nBRABt1UXKDIRe5FM2 MKfqhEqK4hUWC3g1r+vGTrxu3qFpzz7L2UrRFRIpo7yuTUhEhEGvcVsiTppTil4Z HcprXFHf5158elEwhYJ5IM0nU1leNQiOgemifbLwkyNkLqCKth8V/4sezr1tYblZ nMh1cclBAgMBAAECggEBAKdP5jyOicqknoG9/G564RcDsDyRt64NuO7I6hBg7SZx Jn7UKWDdFuFP/RYtoabn6QOxkVVlydp5Typ3Xu7zmfOyss479Q/HIXxmmbkD0Kp0 eRm2KN3y0b6FySsS40KDRjKGQCuGGlNotW3crMw6vOvvsLTlcKgUHF054UVCHoK/ Piz7igkDU7NjvJeha53vXL4hIjb10UtJNaGPxIyFLYRZdRPyyBJX7Yt3w8dgz8WM epOPu0dq3bUrY3WQXcxKZo6sQjE1h7kdl4TNji5jaFlvD01Y8LnyG0oThOzf0tve Gaw+kuy17gTGZGMIfGVcdeb+SlioXMAAfOps+mNIwTECgYEA/gTO8W0hgYpOQJzn BpWkic3LAoBXWNpvsQkkC3uba8Fcps7iiEzotXGfwYcb5Ewf5O3Lrz1EwLj7GTW8 VNhB3gb7bGOvuwI/6vYk2/dwo84bwW9qRWP5hqPhNZ2AWl8kxmZgHns6WTTxpkRU zrfZ5eUrBDWjRU2R8uppgRImsxMCgYEA2MxuL/C/Ko0d7XsSX1kM4JHJiGpQDvb5 GUrlKjP/qVyUysNF92B9xAZZHxxfPWpdfGGBynhw7X6s+YeIoxTzFPZVV9hlkpAA 5igma0n8ZpZEqzttjVdpOQZK8o/Oni/Q2S10WGftQOOGw5Is8+LY30XnLvHBJhO7 TKMurJ4KCNsCgYAe5TDSVmaj3dGEtFC5EUxQ4nHVnQyCpxa8npL+vor5wSvmsfUF hO0s3GQE4sz2qHecnXuPldEd66HGwC1m2GKygYDk/v7prO1fQ47aHi9aDQB9N3Li e7Vmtdn3bm+lDjtn0h3Qt0YygWj+wwLZnazn9EaWHXv9OuEMfYxVgYKpdwKBgEze Zy8+WDm5IWRjn8cI5wT1DBT/RPWZYgcyxABrwXmGZwdhp3wnzU/kxFLAl5BKF22T kRZ+D+RVZvVutebE9c937BiilJkb0AXLNJwT9pdVLnHcN2LHHHronUhV7vetkop+ kGMMLlY0lkLfoGq1AxpfSbIea9KZam6o6VKxEnPDAoGAFDCJm+ZtsJK9nE5GEMav NHy+PwkYsHhbrPl4dgStTNXLenJLIJ+Ke0Pcld4ZPfYdSyu/Tv4rNswZBNpNsW9K 0NwJlyMBfayoPNcJKXrH/csJY7hbKviAHr1eYy9/8OL0dHf85FV+9uY5YndLcsDc nygO9KTJuUiBrLr0AHEnqko= -----END PRIVATE KEY----- key_passphrase: null certificate_authorities: cipher_suites: null curve_types: null supported_protocols: null ``` Related to: #19504
1 parent ddfe085 commit 3f017df

4 files changed

Lines changed: 423 additions & 11 deletions

File tree

CHANGELOG.next.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d
2222
- Added `certificate` TLS verification mode to ignore server name mismatch. {issue}12283[12283] {pull}20293[20293]
2323
- Autodiscover doesn't generate any configuration when a variable is missing. Previously it generated an incomplete configuration. {pull}20898[20898]
2424
- Remove redundant `cloudfoundry.*.timestamp` fields. This value is set in `@timestamp`. {pull}21175[21175]
25+
- Allow embedding of CAs, Certificate of private keys for anything that support TLS in ouputs and inputs https://github.com/elastic/beats/pull/21179
2526

2627
*Auditbeat*
2728

libbeat/common/transport/tlscommon/tls.go

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ import (
2424
"encoding/pem"
2525
"errors"
2626
"fmt"
27+
"io"
2728
"io/ioutil"
29+
"os"
30+
"strings"
2831

2932
"github.com/elastic/beats/v7/libbeat/logp"
3033
)
@@ -67,13 +70,19 @@ func LoadCertificate(config *CertificateConfig) (*tls.Certificate, error) {
6770
return &cert, nil
6871
}
6972

70-
// ReadPEMFile reads a PEM format file on disk and decrypt it with the privided password and
71-
// return the raw content.
72-
func ReadPEMFile(log *logp.Logger, path, passphrase string) ([]byte, error) {
73+
// ReadPEMFile reads a PEM formatted string either from disk or passed as a plain text starting with a "-"
74+
// and decrypt it with the provided password and return the raw content.
75+
func ReadPEMFile(log *logp.Logger, s, passphrase string) ([]byte, error) {
7376
pass := []byte(passphrase)
7477
var blocks []*pem.Block
7578

76-
content, err := ioutil.ReadFile(path)
79+
r, err := NewPEMReader(s)
80+
if err != nil {
81+
return nil, err
82+
}
83+
defer r.Close()
84+
85+
content, err := ioutil.ReadAll(r)
7786
if err != nil {
7887
return nil, err
7988
}
@@ -102,7 +111,7 @@ func ReadPEMFile(log *logp.Logger, path, passphrase string) ([]byte, error) {
102111

103112
if err != nil {
104113
log.Errorf("Dropping encrypted pem '%v' block read from %v. %+v",
105-
block.Type, path, err)
114+
block.Type, r, err)
106115
continue
107116
}
108117

@@ -139,20 +148,28 @@ func LoadCertificateAuthorities(CAs []string) (*x509.CertPool, []error) {
139148

140149
log := logp.NewLogger(logSelector)
141150
roots := x509.NewCertPool()
142-
for _, path := range CAs {
143-
pemData, err := ioutil.ReadFile(path)
151+
for _, s := range CAs {
152+
r, err := NewPEMReader(s)
144153
if err != nil {
145154
log.Errorf("Failed reading CA certificate: %+v", err)
146-
errors = append(errors, fmt.Errorf("%v reading %v", err, path))
155+
errors = append(errors, fmt.Errorf("%v reading %v", err, r))
156+
continue
157+
}
158+
defer r.Close()
159+
160+
pemData, err := ioutil.ReadAll(r)
161+
if err != nil {
162+
log.Errorf("Failed reading CA certificate: %+v", err)
163+
errors = append(errors, fmt.Errorf("%v reading %v", err, r))
147164
continue
148165
}
149166

150167
if ok := roots.AppendCertsFromPEM(pemData); !ok {
151-
log.Error("Failed to add CA to the cert pool, CA is not a valid PEM file")
152-
errors = append(errors, fmt.Errorf("%v adding %v to the list of known CAs", ErrNotACertificate, path))
168+
log.Error("Failed to add CA to the cert pool, CA is not a valid PEM document")
169+
errors = append(errors, fmt.Errorf("%v adding %v to the list of known CAs", ErrNotACertificate, r))
153170
continue
154171
}
155-
log.Debugf("tls", "successfully loaded CA certificate: %v", path)
172+
log.Debugf("tls", "successfully loaded CA certificate: %v", r)
156173
}
157174

158175
return roots, errors
@@ -187,3 +204,45 @@ func ResolveTLSVersion(v uint16) string {
187204
func ResolveCipherSuite(cipher uint16) string {
188205
return tlsCipherSuite(cipher).String()
189206
}
207+
208+
// PEMReader allows to read a certificate in PEM format either through the disk or from a string.
209+
type PEMReader struct {
210+
reader io.ReadCloser
211+
debugStr string
212+
}
213+
214+
// NewPEMReader returns a new PEMReader.
215+
func NewPEMReader(certificate string) (*PEMReader, error) {
216+
if IsPEMString(certificate) {
217+
// Take a substring of the certificate so we do not leak the whole certificate or private key in the log.
218+
debugStr := certificate[0:256] + "..."
219+
return &PEMReader{reader: ioutil.NopCloser(strings.NewReader(certificate)), debugStr: debugStr}, nil
220+
}
221+
222+
r, err := os.Open(certificate)
223+
if err != nil {
224+
return nil, err
225+
}
226+
return &PEMReader{reader: r, debugStr: certificate}, nil
227+
}
228+
229+
// Close closes the target io.ReadCloser.
230+
func (p *PEMReader) Close() error {
231+
return p.reader.Close()
232+
}
233+
234+
// Read read bytes from the io.ReadCloser.
235+
func (p *PEMReader) Read(b []byte) (n int, err error) {
236+
return p.reader.Read(b)
237+
}
238+
239+
func (p *PEMReader) String() string {
240+
return p.debugStr
241+
}
242+
243+
// IsPEMString returns true if the provided string match a PEM formatted certificate. try to pem decode to validate.
244+
func IsPEMString(s string) bool {
245+
// Trim the certificates to make sure we tolerate any yaml weirdness, we assume that the string starts
246+
// with "-" and let further validation verifies the PEM format.
247+
return strings.HasPrefix(strings.TrimSpace(s), "-")
248+
}

0 commit comments

Comments
 (0)