Skip to content

Commit

Permalink
Merge pull request #41 from newrelic/LOGGING-2378_fix_https_proxy_sup…
Browse files Browse the repository at this point in the history
…port

LOGGING-2378: Fixes HTTPS proxy support and improves documentation
  • Loading branch information
jsubirat authored May 6, 2020
2 parents 3c42b88 + 3817311 commit b887771
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 7 deletions.
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,24 @@ If you want to bypass the system-wide defined proxy for some reason, you can use

You can also specify a custom proxy to send the logs to (different from the system-wide defined) by using the `proxy` configuration parameter.

HTTPS proxies (having an `https://...` URL) use a certificate to encrypt the connection between the plugin and the proxy. If you are using a self-signed certificate (not trusted by the Certification Authorities defined at your system level), you can:
* Windows: import the self-signed certificate (PEM file) using the MMC tool. You can refer to [this link](https://www.ssls.com/knowledgebase/how-to-import-intermediate-and-root-certificates-via-mmc/), but in Step 2 ensure to import it in your "Trusted Root Certification Authorities" instead of importing it in the "Intermediate Certification Authorities".
* Linux: you can specify the self-signed certificate (PEM file) using either the `caBundleFile` or `caBundleDir` parameters (see next section).

Optionally, you can skip the self-signed certificate verification by setting `validateProxyCerts` to `false`, but please note that this option is not considered safe due to potential Man In The Middle Attacks.

A example setup, which defines an HTTPS proxy and its self-signed certificate, would result in the following configuration:

```
[OUTPUT]
Name newrelic
Match *
licenseKey <NEW_RELIC_LICENSE_KEY>
proxy: https://https-proxy-hostname:3129
# REMOVE following option when using it on Windows (see above)
caBundleFile: /path/to/proxy-certificate-bundle.pem
```

## Configuration Parameters

The plugin supports the following configuration parameters and include either an Insights or License Key:
Expand All @@ -101,9 +119,9 @@ The plugin supports the following configuration parameters and include either an
|maxRecords | The maximum number of records to send at a time | 1024 |
|proxy | Optional proxy to communicate with New Relic, overrides any environment-defined one. Must follow the format `https://user:password@hostname:port`. Can be HTTP or HTTPS. | (none) |
|ignoreSystemProxy | Ignore any proxy defined via the `HTTP_PROXY` or `HTTPS_PROXY` environment variables. Note that if a proxy has been defined using the `proxy` parameter, this one has no effect. | false |
|caBundleFile | Specifies the Certificate Authority certificate to use for validating HTTPS connections against the proxy. Useful when the proxy uses a self-signed certificate. **The certificate file must be in the PEM format**. If not specified, then the operating system's CA list is used. Only used when `validateProxyCerts` is `true`. | (none) |
|caBundleDir | Specifies a folder containing one or more Certificate Authority certificates ot use for validating HTTPS connections against the proxy. Useful when the proxy uses a self-signed certificate. **Only certificate files in the PEM format and \*.pem extension will be considered**. If not specified, then the operating system's CA list is used. Only used when `validateProxyCerts` is `true`. | (none) |
|validateProxyCerts | When using a HTTPS proxy, the proxy certificates are validated by default when establishing a HTTPS connection. To disable the proxy certificate validation, set `validateProxyCerts` to `false` (insecure) | true |
|caBundleFile | **[LINUX HTTPS ONLY]** Specifies the Certificate Authority certificate to use for validating HTTPS connections against the proxy. Useful when the proxy uses a self-signed certificate. **The certificate file must be in the PEM format**. If not specified, then the operating system's CA list is used. Only used when `validateProxyCerts` is `true`. | (none) |
|caBundleDir | **[LINUX HTTPS ONLY]** Specifies a folder containing one or more Certificate Authority certificates ot use for validating HTTPS connections against the proxy. Useful when the proxy uses a self-signed certificate. **Only certificate files in the PEM format and \*.pem extension will be considered**. If not specified, then the operating system's CA list is used. Only used when `validateProxyCerts` is `true`. | (none) |
|validateProxyCerts | **[HTTPS ONLY]** When using a HTTPS proxy, the proxy certificates are validated by default when establishing a HTTPS connection. To disable the proxy certificate validation, set `validateProxyCerts` to `false` (insecure) | true |

For information on how to find your New Relic Insights Insert key, take a look at the
documentation [here](https://docs.newrelic.com/docs/insights/insights-data-sources/custom-data/send-custom-events-event-api#register).
Expand Down
37 changes: 34 additions & 3 deletions nrclient/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,13 @@ func buildHttpTransport(cfg config.ProxyConfig, nrUrl string) (*http.Transport,
return nil, fmt.Errorf("can't determine the proxy URL to be used to contact NR: %v", err)
}

if proxyURL != nil && proxyURL.Scheme == "https" && !cfg.ValidateCerts {
transport.DialTLS = fallbackDialer(transport)
if proxyURL != nil && proxyURL.Scheme == "https" {
if cfg.ValidateCerts {
transport.DialTLS = fullTLSToHTTPConnectFallbackDialer(transport)
} else {
log.Print("[INFO] You are using an HTTPS proxy without certificate verification. It is recommended to enable it for enhanced security")
transport.DialTLS = fallbackDialer(transport)
}
}

return transport, nil
Expand Down Expand Up @@ -109,7 +114,7 @@ func getProxyResolver(ignoreSystemProxy bool, proxy string) (func(*http.Request)
}
}

func resolveProxyURL (proxyResolver func(*http.Request) (*url.URL, error), nrEndpoint string) (*url.URL, error){
func resolveProxyURL(proxyResolver func(*http.Request) (*url.URL, error), nrEndpoint string) (*url.URL, error) {
nrEndpointURL, err := url.Parse(nrEndpoint)
if err != nil {
return nil, fmt.Errorf("can't parse the NR logging endpoint, please contact New Relic: %v", err)
Expand All @@ -121,6 +126,32 @@ func resolveProxyURL (proxyResolver func(*http.Request) (*url.URL, error), nrEnd
return proxyResolver(&nrUrlRequest)
}

// It tries to initiate TLS handshake with proxy out of any HTTP context, some proxies allow this,
// some others don't, also HTTP/S firewalls may block this plain TCP TLS handshake.
// In case this fails, proxy/firewall is configured to only handle HTTP/S traffic,
// therefore an HTTP CONNECT initiator request is required prior to trigger the proxy TLS handshake.
func fullTLSToHTTPConnectFallbackDialer(t *http.Transport) func(network string, addr string) (net.Conn, error) {
return func(network string, addr string) (conn net.Conn, e error) {
log.Printf("[DEBUG] dialing to proxy via TLS")
dialer := tlsDialer(t)
conn, err := dialer(network, addr)
if err == nil {
log.Printf("[DEBUG] usual, secured configuration worked as expected. Defaulting to it")
t.DialTLS = dialer
return conn, nil
}

switch err.(type) {
case tls.RecordHeaderError:
log.Printf("[DEBUG] TLS handshake cannot be established. Retrying with HTTP CONNECT")
t.DialTLS = nonTLSDialer
return t.DialTLS(network, addr)
default:
return conn, err
}
}
}

// fallbackDialer implements the transport.Dialer interface to provide backwards compatibility with Go 1.9 proxy
// implementation.
//
Expand Down
2 changes: 1 addition & 1 deletion version.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package main

const VERSION = "1.2.0"
const VERSION = "1.2.1"

0 comments on commit b887771

Please sign in to comment.