Repository: pusher/oauth2_proxy (go)

Issue:
Skip validation of upstream certificate
<!--- Provide a general summary of the issue in the Title above -->

Even with `-ssl-insecure-skip-verify` it is not possible to use a self-signed certificate for the upstream.

## Expected Behavior

<!--- If you're describing a bug, tell us what should happen -->
<!--- If you're suggesting a change/improvement, tell us how it should work -->

Upstream with self-signed certificate (such as default kubernetes dashboard deployment) should work if oauth2-proxy is configured with `-ssl-insecure-skip-verify`.

## Current Behavior

<!--- If describing a bug, tell us what happens instead of the expected behavior -->
<!--- If suggesting a change/improvement, explain the difference from current behavior -->

Oauth2-proxy still tries to verify the certificate and fails with the following error message:

```
reverseproxy.go:395: http: proxy error: x509: certificate has expired or is not yet valid
```

## Possible Solution

<!--- Not obligatory, but suggest a fix/reason for the bug, -->
<!--- or ideas how to implement the addition or change -->

This issue was reported [here](https://github.com/bitly/oauth2_proxy/issues/403) in the bitly/oauth2_proxy repository and has a pull request with a proposed solution [here](https://github.com/bitly/oauth2_proxy/pull/651).

## Steps to Reproduce (for bugs)

<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->

I have followed [this guide](https://blog.heptio.com/on-securing-the-kubernetes-dashboard-16b09b1b7aca) but modified it a bit.

1. Install [minikube](https://kubernetes.io/docs/setup/minikube/), kubectl and [helm](https://github.com/helm/helm#install)
2. Clone [this repo](https://github.com/lentzi90/oauth2-proxy-experiment)
3. Checkout the branch `self-signed-dashboard`
4. Follow the quick start
5. Check the logs of the oauth2-proxy pod

## Context

<!--- How has this issue affected you? What are you trying to accomplish? -->
<!--- Providing context helps us come up with a solution that is most useful in the real world -->

The default dashboard deployment uses self-signed certificates and this is better than no TLS at all.
I think this is a common use case, especially in a kubernetes environment.

## Your Environment

<!--- Include as many relevant details about the environment you experienced the bug in -->

Versions used:

- Kubernetes v1.13.3
- Minikube v0.34.1
- Oauth2-proxy chart 0.9.1
- Oauth2-proxy 3.1.0

Pull request:
New flag "-ssl-upstream-insecure-skip-validation"
<!--- Provide a general summary of your changes in the Title above -->

## New flag to skip SSL validation for upstreams with self generated / invalid SSL certificates.

<!--- Describe your changes in detail -->

## Motivation and Context

Upstream with self-signed certificate (such as default kubernetes dashboard deployment) should work if oauth2-proxy.

Fixes #75 

## How Has This Been Tested?

Setup an oauth2_proxy for a Kubernetes dashboard as described in Issue #75, produced the errors as mentioned without the new flag, works setting the flag.

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] My change requires a change to the documentation or CHANGELOG.
- [x] I have updated the documentation/CHANGELOG accordingly.
- [x] I have created a feature (non-master) branch for my PR.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8606b6b4..81ee496d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -36,6 +36,7 @@
 
 ## Changes since v3.2.0
 
+- [#234](https://github.com/pusher/oauth2_proxy/pull/234) Added option `-ssl-upstream-insecure-skip-validation` to skip validation of upstream SSL certificates (@jansinger)
 - [#224](https://github.com/pusher/oauth2_proxy/pull/224) Check Google group membership using hasMember to support nested groups and external users (@jpalpant)
 - [#231](https://github.com/pusher/oauth2_proxy/pull/231) Add optional group membership and email domain checks to the GitLab provider (@Overv)
 - [#178](https://github.com/pusher/outh2_proxy/pull/178) Add Silence Ping Logging and Exclude Logging Paths flags (@kskewes)
diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md
index c9a70b14..5bef512a 100644
--- a/docs/configuration/configuration.md
+++ b/docs/configuration/configuration.md
@@ -99,7 +99,8 @@ Usage of oauth2_proxy:
   -skip-jwt-bearer-tokens: will skip requests that have verified JWT bearer tokens
   -skip-oidc-discovery: bypass OIDC endpoint discovery. login-url, redeem-url and oidc-jwks-url must be configured in this case
   -skip-provider-button: will skip sign-in-page to directly reach the next step: oauth/start
-  -ssl-insecure-skip-verify: skip validation of certificates presented when using HTTPS
+  -ssl-insecure-skip-verify: skip validation of certificates presented when using HTTPS providers
+  -ssl-upstream-insecure-skip-verify: skip validation of certificates presented when using HTTPS upstreams
   -standard-logging: Log standard runtime information (default true)
   -standard-logging-format string: Template for standard log lines (see "Logging Configuration" paragraph below)
   -tls-cert-file string: path to certificate file
diff --git a/main.go b/main.go
index 6db6025b..823a16d3 100644
--- a/main.go
+++ b/main.go
@@ -47,7 +47,8 @@ func main() {
 	flagSet.Var(&skipAuthRegex, "skip-auth-regex", "bypass authentication for requests path's that match (may be given multiple times)")
 	flagSet.Bool("skip-provider-button", false, "will skip sign-in-page to directly reach the next step: oauth/start")
 	flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests")
-	flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS")
+	flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS providers")
+	flagSet.Bool("ssl-upstream-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS upstreams")
 	flagSet.Duration("flush-interval", time.Duration(1)*time.Second, "period between response flushing when streaming responses")
 	flagSet.Bool("skip-jwt-bearer-tokens", false, "will skip requests that have verified JWT bearer tokens (default false)")
 	flagSet.Var(&jwtIssuers, "extra-jwt-issuers", "if skip-jwt-bearer-tokens is set, a list of extra JWT issuer=audience pairs (where the issuer URL has a .well-known/openid-configuration or a .well-known/jwks.json)")
diff --git a/oauthproxy.go b/oauthproxy.go
index 5ef7a390..365a14e0 100644
--- a/oauthproxy.go
+++ b/oauthproxy.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"context"
+	"crypto/tls"
 	b64 "encoding/base64"
 	"errors"
 	"fmt"
@@ -128,9 +129,14 @@ func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 // NewReverseProxy creates a new reverse proxy for proxying requests to upstream
 // servers
-func NewReverseProxy(target *url.URL, flushInterval time.Duration) (proxy *httputil.ReverseProxy) {
+func NewReverseProxy(target *url.URL, opts *Options) (proxy *httputil.ReverseProxy) {
 	proxy = httputil.NewSingleHostReverseProxy(target)
-	proxy.FlushInterval = flushInterval
+	proxy.FlushInterval = opts.FlushInterval
+	if opts.SSLUpstreamInsecureSkipVerify {
+		proxy.Transport = &http.Transport{
+			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+		}
+	}
 	return proxy
 }
 
@@ -163,7 +169,7 @@ func NewFileServer(path string, filesystemPath string) (proxy http.Handler) {
 // NewWebSocketOrRestReverseProxy creates a reverse proxy for REST or websocket based on url
 func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.HmacAuth) http.Handler {
 	u.Path = ""
-	proxy := NewReverseProxy(u, opts.FlushInterval)
+	proxy := NewReverseProxy(u, opts)
 	if !opts.PassHostHeader {
 		setProxyUpstreamHostHeader(proxy, u)
 	} else {
diff --git a/options.go b/options.go
index 6969faa1..03f5ff38 100644
--- a/options.go
+++ b/options.go
@@ -62,22 +62,23 @@ type Options struct {
 	// Embed SessionOptions
 	options.SessionOptions
 
-	Upstreams             []string      `flag:"upstream" cfg:"upstreams" env:"OAUTH2_PROXY_UPSTREAMS"`
-	SkipAuthRegex         []string      `flag:"skip-auth-regex" cfg:"skip_auth_regex" env:"OAUTH2_PROXY_SKIP_AUTH_REGEX"`
-	SkipJwtBearerTokens   bool          `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens" env:"OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS"`
-	ExtraJwtIssuers       []string      `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers" env:"OAUTH2_PROXY_EXTRA_JWT_ISSUERS"`
-	PassBasicAuth         bool          `flag:"pass-basic-auth" cfg:"pass_basic_auth" env:"OAUTH2_PROXY_PASS_BASIC_AUTH"`
-	BasicAuthPassword     string        `flag:"basic-auth-password" cfg:"basic_auth_password" env:"OAUTH2_PROXY_BASIC_AUTH_PASSWORD"`
-	PassAccessToken       bool          `flag:"pass-access-token" cfg:"pass_access_token" env:"OAUTH2_PROXY_PASS_ACCESS_TOKEN"`
-	PassHostHeader        bool          `flag:"pass-host-header" cfg:"pass_host_header" env:"OAUTH2_PROXY_PASS_HOST_HEADER"`
-	SkipProviderButton    bool          `flag:"skip-provider-button" cfg:"skip_provider_button" env:"OAUTH2_PROXY_SKIP_PROVIDER_BUTTON"`
-	PassUserHeaders       bool          `flag:"pass-user-headers" cfg:"pass_user_headers" env:"OAUTH2_PROXY_PASS_USER_HEADERS"`
-	SSLInsecureSkipVerify bool          `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify" env:"OAUTH2_PROXY_SSL_INSECURE_SKIP_VERIFY"`
-	SetXAuthRequest       bool          `flag:"set-xauthrequest" cfg:"set_xauthrequest" env:"OAUTH2_PROXY_SET_XAUTHREQUEST"`
-	SetAuthorization      bool          `flag:"set-authorization-header" cfg:"set_authorization_header" env:"OAUTH2_PROXY_SET_AUTHORIZATION_HEADER"`
-	PassAuthorization     bool          `flag:"pass-authorization-header" cfg:"pass_authorization_header" env:"OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER"`
-	SkipAuthPreflight     bool          `flag:"skip-auth-preflight" cfg:"skip_auth_preflight" env:"OAUTH2_PROXY_SKIP_AUTH_PREFLIGHT"`
-	FlushInterval         time.Duration `flag:"flush-interval" cfg:"flush_interval" env:"OAUTH2_PROXY_FLUSH_INTERVAL"`
+	Upstreams                     []string      `flag:"upstream" cfg:"upstreams" env:"OAUTH2_PROXY_UPSTREAMS"`
+	SkipAuthRegex                 []string      `flag:"skip-auth-regex" cfg:"skip_auth_regex" env:"OAUTH2_PROXY_SKIP_AUTH_REGEX"`
+	SkipJwtBearerTokens           bool          `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens" env:"OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS"`
+	ExtraJwtIssuers               []string      `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers" env:"OAUTH2_PROXY_EXTRA_JWT_ISSUERS"`
+	PassBasicAuth                 bool          `flag:"pass-basic-auth" cfg:"pass_basic_auth" env:"OAUTH2_PROXY_PASS_BASIC_AUTH"`
+	BasicAuthPassword             string        `flag:"basic-auth-password" cfg:"basic_auth_password" env:"OAUTH2_PROXY_BASIC_AUTH_PASSWORD"`
+	PassAccessToken               bool          `flag:"pass-access-token" cfg:"pass_access_token" env:"OAUTH2_PROXY_PASS_ACCESS_TOKEN"`
+	PassHostHeader                bool          `flag:"pass-host-header" cfg:"pass_host_header" env:"OAUTH2_PROXY_PASS_HOST_HEADER"`
+	SkipProviderButton            bool          `flag:"skip-provider-button" cfg:"skip_provider_button" env:"OAUTH2_PROXY_SKIP_PROVIDER_BUTTON"`
+	PassUserHeaders               bool          `flag:"pass-user-headers" cfg:"pass_user_headers" env:"OAUTH2_PROXY_PASS_USER_HEADERS"`
+	SSLInsecureSkipVerify         bool          `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify" env:"OAUTH2_PROXY_SSL_INSECURE_SKIP_VERIFY"`
+	SSLUpstreamInsecureSkipVerify bool          `flag:"ssl-upstream-insecure-skip-verify" cfg:"ssl_upstream_insecure_skip_verify" env:"OAUTH2_PROXY_SSL_UPSTREAM_INSECURE_SKIP_VERIFY"`
+	SetXAuthRequest               bool          `flag:"set-xauthrequest" cfg:"set_xauthrequest" env:"OAUTH2_PROXY_SET_XAUTHREQUEST"`
+	SetAuthorization              bool          `flag:"set-authorization-header" cfg:"set_authorization_header" env:"OAUTH2_PROXY_SET_AUTHORIZATION_HEADER"`
+	PassAuthorization             bool          `flag:"pass-authorization-header" cfg:"pass_authorization_header" env:"OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER"`
+	SkipAuthPreflight             bool          `flag:"skip-auth-preflight" cfg:"skip_auth_preflight" env:"OAUTH2_PROXY_SKIP_AUTH_PREFLIGHT"`
+	FlushInterval                 time.Duration `flag:"flush-interval" cfg:"flush_interval" env:"OAUTH2_PROXY_FLUSH_INTERVAL"`
 
 	// These options allow for other providers besides Google, with
 	// potential overrides.


Test patch:
diff --git a/oauthproxy_test.go b/oauthproxy_test.go
index c3d422c4..8dd3adfb 100644
--- a/oauthproxy_test.go
+++ b/oauthproxy_test.go
@@ -122,7 +122,7 @@ func TestNewReverseProxy(t *testing.T) {
 	backendHost := net.JoinHostPort(backendHostname, backendPort)
 	proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
 
-	proxyHandler := NewReverseProxy(proxyURL, time.Second)
+	proxyHandler := NewReverseProxy(proxyURL, &Options{FlushInterval: time.Second})
 	setProxyUpstreamHostHeader(proxyHandler, proxyURL)
 	frontend := httptest.NewServer(proxyHandler)
 	defer frontend.Close()
@@ -144,7 +144,7 @@ func TestEncodedSlashes(t *testing.T) {
 	defer backend.Close()
 
 	b, _ := url.Parse(backend.URL)
-	proxyHandler := NewReverseProxy(b, time.Second)
+	proxyHandler := NewReverseProxy(b, &Options{FlushInterval: time.Second})
 	setProxyDirector(proxyHandler)
 	frontend := httptest.NewServer(proxyHandler)
 	defer frontend.Close()