Skip to content

Commit

Permalink
[Heartbeat] Add Additional ECS tls.* fields
Browse files Browse the repository at this point in the history
This patch adds additional [ECS
fields](https://www.elastic.co/guide/en/ecs/current/ecs-tls.html).

Sample output of the `tls.*` fields with this patch is below. Note the
somewhat strange nesting of data in `issuer` and `subject`. This is per
the ECS spec, but a bit awkward. We may want to break this data out into
the more specific ECS `x509` type in the future. For UI work we are likely
fine to parse this on the client and display the CN section in most
cases.

```json
{
  "version": "1.2",
  "version_protocol": "tls"
  "cipher": "ECDHE-RSA-AES-128-GCM-SHA256",
  "server": {
    "subject": "CN=r2.shared.global.fastly.net,O=Fastly\\, Inc.,L=San Francisco,ST=California,C=US",
    "hash": {
      "sha1": "b7b4b89ef0d0caf39d223736f0fdbb03c7b426f1",
      "sha256": "12b00d04db0db8caa302bfde043e88f95baceb91e86ac143e93830b4bbec726d"
    },
    "not_before": "2019-08-16T01:40:25.000Z",
    "not_after": "2019-08-16T01:40:25.000Z",
    "issuer": "CN=GlobalSign CloudSSL CA - SHA256 - G3,O=GlobalSign nv-sa,C=BE"
  },
  "certificate_not_valid_before": "2019-08-16T01:40:25.000Z",
  "certificate_not_valid_after": "2020-07-16T03:15:39.000Z",
  "established": true,
  "rtt": {
    "handshake": {
      "us": 42491
    }
  },
}
```

Work goes towards elastic/uptime#161
  • Loading branch information
andrewvc committed Apr 13, 2020
1 parent fd3a26c commit f48c3a3
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 11 deletions.
32 changes: 30 additions & 2 deletions heartbeat/monitors/active/dialchain/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
package dialchain

import (
"crypto/sha1"
"crypto/sha256"
cryptoTLS "crypto/tls"
"crypto/x509"
"fmt"
Expand Down Expand Up @@ -58,12 +60,26 @@ func TLSLayer(cfg *tlscommon.TLSConfig, to time.Duration) Layer {
if !ok {
panic(fmt.Sprintf("TLS afterDial received a non-tls connection %t. This should never happen", conn))
}
connState := tlsConn.ConnectionState()
event.PutValue("tls.established", true)

// TODO: extract TLS connection parameters from connection object.
timer.stop()
event.PutValue("tls.rtt.handshake", look.RTT(timer.duration()))

addCertMetdata(event.Fields, tlsConn.ConnectionState().PeerCertificates)
versionDetails := tlscommon.TLSVersion(connState.Version).Details()
// The only situation in which versionDetails would be nil is if an unknown TLS version were to be
// encountered. Not filling the fields here makes sense, since there's no standard 'unknown' value.
if versionDetails != nil {
event.PutValue("tls.version_protocol", versionDetails.Protocol)
event.PutValue("tls.version", versionDetails.Version)
}

if connState.NegotiatedProtocol != "" {
event.PutValue("tls.next_protocol", connState.NegotiatedProtocol)
}
event.PutValue("tls.cipher", tlscommon.ResolveCipherSuite(connState.CipherSuite))

addCertMetdata(event.Fields, connState.PeerCertificates)

return conn, nil
}), nil
Expand Down Expand Up @@ -105,9 +121,21 @@ func addCertMetdata(fields common.MapStr, certs []*x509.Certificate) {
}
}

// Legacy non-ECS field
fields.Put("tls.certificate_not_valid_before", chainNotValidBefore)
// New ECS compatible field
fields.Put("tls.server.not_before", chainNotValidBefore)

if chainNotValidAfter != nil {
// Legacy non-ECS field
fields.Put("tls.certificate_not_valid_after", *chainNotValidAfter)
// New ECS compatible field
fields.Put("tls.server.not_after", chainNotValidBefore)
}

hostCert := certs[0]
fields.Put("tls.server.issuer", hostCert.Issuer.String())
fields.Put("tls.server.subject", hostCert.Subject.String())
fields.Put("tls.server.hash.sha1", fmt.Sprintf("%x", sha1.Sum(hostCert.Raw)))
fields.Put("tls.server.hash.sha256", fmt.Sprintf("%x", sha256.Sum256(hostCert.Raw)))
}
12 changes: 10 additions & 2 deletions libbeat/common/transport/tlscommon/versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,20 @@ import "fmt"
type TLSVersion uint16

func (v TLSVersion) String() string {
if s, ok := tlsProtocolVersionsInverse[v]; ok {
return s
if details := v.Details(); details != nil {
return details.Combined
}
return "unknown"
}

func (v TLSVersion) Details() *protocolAndVersion {
if found, ok := tlsInverseLookup[v]; ok {
return &found
}
return nil
}


//Unpack transforms the string into a constant.
func (v *TLSVersion) Unpack(s string) error {
version, found := tlsProtocolVersions[s]
Expand Down
28 changes: 21 additions & 7 deletions libbeat/common/transport/tlscommon/versions_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@

package tlscommon

import "crypto/tls"
import (
"crypto/tls"
)

// Define all the possible TLS version.
const (
Expand Down Expand Up @@ -61,10 +63,22 @@ var tlsProtocolVersions = map[string]TLSVersion{
"TLSv1.3": TLSVersion13,
}

var tlsProtocolVersionsInverse = map[TLSVersion]string{
TLSVersionSSL30: "SSLv3",
TLSVersion10: "TLSv1.0",
TLSVersion11: "TLSv1.1",
TLSVersion12: "TLSv1.2",
TLSVersion13: "TLSv1.3",
// Intended for ECS's tls.version_protocol_field, which does not include
// numeric version and should be lower case
type protocolAndVersion struct {
Version string
Protocol string
Combined string
}

func (pv protocolAndVersion) String() string {
return pv.Combined
}

var tlsInverseLookup = map [TLSVersion]protocolAndVersion{
TLSVersionSSL30: protocolAndVersion{Version: "3.0", Protocol: "ssl", Combined: "SSLv3"},
TLSVersion10: protocolAndVersion{Version: "1.0", Protocol: "tls", Combined: "TLSv1.0"},
TLSVersion11: protocolAndVersion{Version: "1.1", Protocol: "tls", Combined: "TLSv1.1"},
TLSVersion12: protocolAndVersion{Version: "1.2", Protocol: "tls", Combined: "TLSv1.2"},
TLSVersion13: protocolAndVersion{Version: "1.3", Protocol: "tls", Combined: "TLSv1.3"},
}

0 comments on commit f48c3a3

Please sign in to comment.