Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cherry-pick #22190 to 7.x: [Heartbeat] Add tls fields when connecting through proxy #22881

Merged
merged 1 commit into from
Dec 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d
- Fixed excessive memory usage introduced in 7.5 due to over-allocating memory for HTTP checks. {pull}15639[15639]
- Fixed scheduler shutdown issues which would in rare situations cause a panic due to semaphore misuse. {pull}16397[16397]
- Fixed TCP TLS checks to properly validate hostnames, this broke in 7.x and only worked for IP SANs. {pull}17549[17549]
- Fixed missing `tls` fields when connecting to https via proxy. {issue}15797[15797] {pull}22190[22190]

*Heartbeat*

Expand Down
7 changes: 6 additions & 1 deletion heartbeat/monitors/active/dialchain/tlsmeta/tlsmeta.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,14 @@ import (
"github.com/elastic/beats/v7/libbeat/common/transport/tlscommon"
)

// UnknownTLSHandshakeDuration to be used in AddTLSMetadata when the duration of the TLS handshake can't be determined.
const UnknownTLSHandshakeDuration = time.Duration(-1)

func AddTLSMetadata(fields common.MapStr, connState cryptoTLS.ConnectionState, duration time.Duration) {
fields.Put("tls.established", true)
fields.Put("tls.rtt.handshake", look.RTT(duration))
if duration != UnknownTLSHandshakeDuration {
fields.Put("tls.rtt.handshake", look.RTT(duration))
}
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.
Expand Down
74 changes: 74 additions & 0 deletions heartbeat/monitors/active/http/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
Expand All @@ -29,6 +30,7 @@ import (
"os"
"path"
"reflect"
"sync"
"testing"
"time"

Expand Down Expand Up @@ -367,6 +369,24 @@ func runHTTPSServerCheck(
time.Sleep(time.Millisecond * 500)
}

// When connecting through a proxy, the following fields are missing.
if _, isProxy := reqExtraConfig["proxy_url"]; isProxy {
missing := map[string]interface{}{
"http.rtt.response_header.us": time.Duration(0),
"http.rtt.content.us": time.Duration(0),
"monitor.ip": "127.0.0.1",
"tcp.rtt.connect.us": time.Duration(0),
"http.rtt.validate.us": time.Duration(0),
"http.rtt.write_request.us": time.Duration(0),
"tls.rtt.handshake.us": time.Duration(0),
}
for k, v := range missing {
if found, err := event.Fields.HasKey(k); !found || err != nil {
event.Fields.Put(k, v)
}
}
}

testslike.Test(
t,
lookslike.Strict(lookslike.Compose(
Expand Down Expand Up @@ -611,3 +631,57 @@ func TestNewRoundTripper(t *testing.T) {
}

}

func TestProxy(t *testing.T) {
server := httptest.NewTLSServer(hbtest.HelloWorldHandler(http.StatusOK))
proxy := httptest.NewServer(http.HandlerFunc(httpConnectTunnel))
runHTTPSServerCheck(t, server, map[string]interface{}{
"proxy_url": proxy.URL,
})
}

func TestTLSProxy(t *testing.T) {
server := httptest.NewTLSServer(hbtest.HelloWorldHandler(http.StatusOK))
proxy := httptest.NewTLSServer(http.HandlerFunc(httpConnectTunnel))
runHTTPSServerCheck(t, server, map[string]interface{}{
"proxy_url": proxy.URL,
})
}

func httpConnectTunnel(writer http.ResponseWriter, request *http.Request) {
// This method is adapted from code by Michał Łowicki @mlowicki (CC BY 4.0)
// See https://medium.com/@mlowicki/http-s-proxy-in-golang-in-less-than-100-lines-of-code-6a51c2f2c38c
if request.Method != http.MethodConnect {
http.Error(writer, "Only CONNECT method is supported", http.StatusMethodNotAllowed)
return
}
destConn, err := net.DialTimeout("tcp", request.Host, 10*time.Second)
if err != nil {
http.Error(writer, err.Error(), http.StatusServiceUnavailable)
return
}
writer.WriteHeader(http.StatusOK)
hijacker, ok := writer.(http.Hijacker)
if !ok {
http.Error(writer, "Hijacking not supported", http.StatusInternalServerError)
return
}
clientConn, clientReadWriter, err := hijacker.Hijack()
if err != nil {
http.Error(writer, err.Error(), http.StatusServiceUnavailable)
}
defer destConn.Close()
defer clientConn.Close()

var wg sync.WaitGroup
wg.Add(2)
go func() {
io.Copy(destConn, clientReadWriter)
wg.Done()
}()
go func() {
io.Copy(clientConn, destConn)
wg.Done()
}()
wg.Wait()
}
8 changes: 8 additions & 0 deletions heartbeat/monitors/active/http/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,14 @@ func execPing(
// Mark the end time as now, since we've finished downloading
end = time.Now()

// Enrich event with TLS information when available. This is useful when connecting to an HTTPS server through
// a proxy.
if resp.TLS != nil {
tlsFields := common.MapStr{}
tlsmeta.AddTLSMetadata(tlsFields, *resp.TLS, tlsmeta.UnknownTLSHandshakeDuration)
eventext.MergeEventFields(event, tlsFields)
}

// Add total HTTP RTT
eventext.MergeEventFields(event, common.MapStr{"http": common.MapStr{
"rtt": common.MapStr{
Expand Down