Skip to content

Commit

Permalink
[Agent] Allow CA cert pinning on the Elasticsearch output or any code…
Browse files Browse the repository at this point in the history
… that user tlscommon.TLSConfig builder. (elastic#16019)

* Add a sha256 pin for the CA Certificate

When multiples CA are presents on the system we cannot ensure that a
specific one was used to validates the chains exposer by the server.
This PRs adds a `ca_sha256` option to the `tlscommon.TLSConfig` that is used by all
the code that has to create a TCP client with TLS support.

When the option is set, it will hook a new callback in the validation
chains that will inspect the verified and validated chains by Go to
ensure that a lets a certificate in the chains match the provided
sha256.

Usage example for the Elasticsearch output.

```
output.elasticsearch:
  hosts: [127.0.0.1:9200]
  ssl.ca_sha256: <base64_encoded_sha1>
```

You can generate the pin using the **openssl** binary with the
following command:

```
openssl x509 -in ca.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
```

OpenSSL's [documentation](https://www.openssl.org/docs/manmaster/man1/dgst.html)

You will need to start Elasticsearch with the following options

```yaml
xpack.security.enabled: true
indices.id_field_data.enabled: true
xpack.license.self_generated.type: trial
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.key: /etc/pki/localhost/localhost.key"
xpack.security.http.ssl.certificate: /etc/pki/localhost/localhost.crt"
xpack.security.http.ssl.certificate_authorities: /etc/pki/ca/ca.crt"
```

This pull request also include a new service in the docker-compose.yml
that will start a new Elasticsearch server with TLS and security
configured.

(cherry picked from commit 0d9f03e)
  • Loading branch information
ph committed Feb 27, 2020
1 parent 17b1cb3 commit db40fac
Show file tree
Hide file tree
Showing 28 changed files with 889 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d
- Fix k8s pods labels broken schema. {pull}16480[16480]
- Fix k8s pods annotations broken schema. {pull}16554[16554]
- Upgrade go-ucfg to latest v0.8.3. {pull}16450{16450}
- Add `ssl.ca_sha256` option to the supported TLS option, this allow to check that a specific certificate is used as part of the verified chain. {issue}15717[15717]

*Auditbeat*

Expand Down
12 changes: 12 additions & 0 deletions auditbeat/auditbeat.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,12 @@ output.elasticsearch:
# never, once, and freely. Default is never.
#ssl.renegotiation: never

# Configure a pin that can be used to do extra validation of the verified certificate chain,
# this allow you to ensure that a specific certificate is used to validate the chain of trust.
#
# The pin is a base64 encoded string of the SHA-256 fingerprint.
#ssl.ca_sha256

#----------------------------- Logstash output ---------------------------------
#output.logstash:
# Boolean flag to enable or disable the output module.
Expand Down Expand Up @@ -603,6 +609,12 @@ output.elasticsearch:
# never, once, and freely. Default is never.
#ssl.renegotiation: never

# Configure a pin that can be used to do extra validation of the verified certificate chain,
# this allow you to ensure that a specific certificate is used to validate the chain of trust.
#
# The pin is a base64 encoded string of the SHA-256 fingerprint.
#ssl.ca_sha256

# The number of times to retry publishing an event after a publishing failure.
# After the specified number of retries, the events are typically dropped.
# Some Beats, such as Filebeat and Winlogbeat, ignore the max_retries setting
Expand Down
12 changes: 12 additions & 0 deletions filebeat/filebeat.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1215,6 +1215,12 @@ output.elasticsearch:
# never, once, and freely. Default is never.
#ssl.renegotiation: never

# Configure a pin that can be used to do extra validation of the verified certificate chain,
# this allow you to ensure that a specific certificate is used to validate the chain of trust.
#
# The pin is a base64 encoded string of the SHA-256 fingerprint.
#ssl.ca_sha256

#----------------------------- Logstash output ---------------------------------
#output.logstash:
# Boolean flag to enable or disable the output module.
Expand Down Expand Up @@ -1308,6 +1314,12 @@ output.elasticsearch:
# never, once, and freely. Default is never.
#ssl.renegotiation: never

# Configure a pin that can be used to do extra validation of the verified certificate chain,
# this allow you to ensure that a specific certificate is used to validate the chain of trust.
#
# The pin is a base64 encoded string of the SHA-256 fingerprint.
#ssl.ca_sha256

# The number of times to retry publishing an event after a publishing failure.
# After the specified number of retries, the events are typically dropped.
# Some Beats, such as Filebeat and Winlogbeat, ignore the max_retries setting
Expand Down
12 changes: 12 additions & 0 deletions heartbeat/heartbeat.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,12 @@ output.elasticsearch:
# never, once, and freely. Default is never.
#ssl.renegotiation: never

# Configure a pin that can be used to do extra validation of the verified certificate chain,
# this allow you to ensure that a specific certificate is used to validate the chain of trust.
#
# The pin is a base64 encoded string of the SHA-256 fingerprint.
#ssl.ca_sha256

#----------------------------- Logstash output ---------------------------------
#output.logstash:
# Boolean flag to enable or disable the output module.
Expand Down Expand Up @@ -747,6 +753,12 @@ output.elasticsearch:
# never, once, and freely. Default is never.
#ssl.renegotiation: never

# Configure a pin that can be used to do extra validation of the verified certificate chain,
# this allow you to ensure that a specific certificate is used to validate the chain of trust.
#
# The pin is a base64 encoded string of the SHA-256 fingerprint.
#ssl.ca_sha256

# The number of times to retry publishing an event after a publishing failure.
# After the specified number of retries, the events are typically dropped.
# Some Beats, such as Filebeat and Winlogbeat, ignore the max_retries setting
Expand Down
12 changes: 12 additions & 0 deletions journalbeat/journalbeat.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,12 @@ output.elasticsearch:
# never, once, and freely. Default is never.
#ssl.renegotiation: never

# Configure a pin that can be used to do extra validation of the verified certificate chain,
# this allow you to ensure that a specific certificate is used to validate the chain of trust.
#
# The pin is a base64 encoded string of the SHA-256 fingerprint.
#ssl.ca_sha256

#----------------------------- Logstash output ---------------------------------
#output.logstash:
# Boolean flag to enable or disable the output module.
Expand Down Expand Up @@ -541,6 +547,12 @@ output.elasticsearch:
# never, once, and freely. Default is never.
#ssl.renegotiation: never

# Configure a pin that can be used to do extra validation of the verified certificate chain,
# this allow you to ensure that a specific certificate is used to validate the chain of trust.
#
# The pin is a base64 encoded string of the SHA-256 fingerprint.
#ssl.ca_sha256

# The number of times to retry publishing an event after a publishing failure.
# After the specified number of retries, the events are typically dropped.
# Some Beats, such as Filebeat and Winlogbeat, ignore the max_retries setting
Expand Down
12 changes: 12 additions & 0 deletions libbeat/_meta/config.reference.yml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,12 @@ output.elasticsearch:
# Configure what types of renegotiation are supported. Valid options are
# never, once, and freely. Default is never.
#ssl.renegotiation: never

# Configure a pin that can be used to do extra validation of the verified certificate chain,
# this allow you to ensure that a specific certificate is used to validate the chain of trust.
#
# The pin is a base64 encoded string of the SHA-256 fingerprint.
#ssl.ca_sha256
{{if not .ExcludeLogstash}}
#----------------------------- Logstash output ---------------------------------
#output.logstash:
Expand Down Expand Up @@ -484,6 +490,12 @@ output.elasticsearch:
# never, once, and freely. Default is never.
#ssl.renegotiation: never

# Configure a pin that can be used to do extra validation of the verified certificate chain,
# this allow you to ensure that a specific certificate is used to validate the chain of trust.
#
# The pin is a base64 encoded string of the SHA-256 fingerprint.
#ssl.ca_sha256

# The number of times to retry publishing an event after a publishing failure.
# After the specified number of retries, the events are typically dropped.
# Some Beats, such as Filebeat and Winlogbeat, ignore the max_retries setting
Expand Down
73 changes: 73 additions & 0 deletions libbeat/common/transport/tlscommon/ca_pinning.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 tlscommon

import (
"crypto/sha256"
"crypto/x509"
"encoding/base64"

"github.com/pkg/errors"
)

// ErrCAPinMissmatch is returned when no pin is matched in the verified chain.
var ErrCAPinMissmatch = errors.New("provided CA certificate pins doesn't match any of the certificate authorities used to validate the certificate")

type pins []string

func (p pins) Matches(candidate string) bool {
for _, pin := range p {
if pin == candidate {
return true
}
}
return false
}

// verifyPeerCertFunc is a callback defined on the tls.Config struct that will called when a
// TLS connection is used.
type verifyPeerCertFunc func([][]byte, [][]*x509.Certificate) error

// MakeCAPinCallback loops through the verified chains and will try to match the certificates pin.
//
// NOTE: Defining a PIN to check certificates is not a replacement for the normal TLS validations it's
// an additional validation. In fact if you set `InsecureSkipVerify` to true and a PIN, the
// verifiedChains variable will be empty and the added validation will fail.
func MakeCAPinCallback(hashes pins) func([][]byte, [][]*x509.Certificate) error {
return func(_ [][]byte, verifiedChains [][]*x509.Certificate) error {
// The chain of trust has been already established before the call to the VerifyPeerCertificate
// function, after we go through the chain to make sure we have at least a certificate certificate
// that match the provided pin.
for _, chain := range verifiedChains {
for _, certificate := range chain {
h := Fingerprint(certificate)
if hashes.Matches(h) {
return nil
}
}
}

return ErrCAPinMissmatch
}
}

// Fingerprint takes a certificate and create a hash of the DER encoded public key.
func Fingerprint(certificate *x509.Certificate) string {
hash := sha256.Sum256(certificate.RawSubjectPublicKeyInfo)
return base64.StdEncoding.EncodeToString(hash[:])
}
Loading

0 comments on commit db40fac

Please sign in to comment.