From f126ca0eb12a0b1deaadb6285daad5705fe9b3ad Mon Sep 17 00:00:00 2001 From: Marc <7955883+wraix@users.noreply.github.com> Date: Fri, 30 Apr 2021 23:15:54 +0200 Subject: [PATCH] feat: adds option to append certificate file to root ca for upstream connections (#181) --- .schema/config.schema.json | 16 +++++++++ docs/docs/reference/configuration.md | 22 ++++++++++++ driver/configuration/provider.go | 3 +- driver/configuration/provider_viper.go | 5 +++ driver/registry_memory.go | 39 ++++++++++++++++++++++ proxy/proxy.go | 46 +++++++++++++++++++++++++- 6 files changed, 129 insertions(+), 2 deletions(-) diff --git a/.schema/config.schema.json b/.schema/config.schema.json index 59c1460ba7..3fe3f420f9 100644 --- a/.schema/config.schema.json +++ b/.schema/config.schema.json @@ -1170,6 +1170,22 @@ "timeout": { "$ref": "#/definitions/serverTimeout" }, + "upstream": { + "type": "object", + "title": "HTTP Upstream", + "additionalProperties": false, + "properties": { + "ca_append_crt_path": { + "type": "string", + "default": "", + "examples": [ + "./self-signed.crt" + ], + "title": "CA Certificate", + "description": "The file containing the CA certificates to append to the Root CA when using upstream connections." + } + } + }, "cors": { "$ref": "#/definitions/cors" }, diff --git a/docs/docs/reference/configuration.md b/docs/docs/reference/configuration.md index 2a948cd4fd..cf1b69a1c2 100644 --- a/docs/docs/reference/configuration.md +++ b/docs/docs/reference/configuration.md @@ -1498,6 +1498,28 @@ serve: # read: 5s + ## HTTP Upstream ## + # + # Control the HTTP upstream. + # + upstream: + ## Append Certificate To Root CA ## + # + # The path to a certificate file to append to the Root Certificate Authority for the upstream connection. Use this to accept self-signed certificates on the upstream only, keeping the host system certificate authority unaltered. + # + # Default value: "" + # + # Examples: + # - self-signed.crt + # + # Set this value using environment variables on + # - Linux/macOS: + # $ export SERVE_PROXY_UPSTREAM_CA_APPEND_CRT_PATH= + # - Windows Command Line (CMD): + # > set SERVE_PROXY_UPSTREAM_CA_APPEND_CRT_PATH= + # + ca_append_crt_path: "" + ## Cross Origin Resource Sharing (CORS) ## # # Configure [Cross Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) using the following options. diff --git a/driver/configuration/provider.go b/driver/configuration/provider.go index 7da85efa0b..2d8f611c6a 100644 --- a/driver/configuration/provider.go +++ b/driver/configuration/provider.go @@ -5,7 +5,7 @@ import ( "net/url" "time" - "github.com/gobuffalo/packr/v2" + packr "github.com/gobuffalo/packr/v2" "github.com/ory/fosite" "github.com/ory/x/tracing" @@ -41,6 +41,7 @@ type Provider interface { ProxyReadTimeout() time.Duration ProxyWriteTimeout() time.Duration ProxyIdleTimeout() time.Duration + ProxyServeUpstreamCaAppendCrtPath() string APIReadTimeout() time.Duration APIWriteTimeout() time.Duration diff --git a/driver/configuration/provider_viper.go b/driver/configuration/provider_viper.go index 1f61d5fa0e..2ecc1f89e8 100644 --- a/driver/configuration/provider_viper.go +++ b/driver/configuration/provider_viper.go @@ -43,6 +43,7 @@ const ( ViperKeyProxyIdleTimeout = "serve.proxy.timeout.idle" ViperKeyProxyServeAddressHost = "serve.proxy.host" ViperKeyProxyServeAddressPort = "serve.proxy.port" + ViperKeyProxyUpstreamCaAppendCrtPath = "serve.proxy.upstream.ca_append_crt_path" ViperKeyAPIServeAddressHost = "serve.api.host" ViperKeyAPIServeAddressPort = "serve.api.port" ViperKeyAPIReadTimeout = "serve.api.timeout.read" @@ -177,6 +178,10 @@ func (v *ViperProvider) ProxyServeAddress() string { ) } +func (v *ViperProvider) ProxyServeUpstreamCaAppendCrtPath() string { + return viperx.GetString(v.l, ViperKeyProxyUpstreamCaAppendCrtPath, "") +} + func (v *ViperProvider) APIReadTimeout() time.Duration { return viperx.GetDuration(v.l, ViperKeyAPIReadTimeout, time.Second*5) } diff --git a/driver/registry_memory.go b/driver/registry_memory.go index 0ccdada2e1..9b819561d6 100644 --- a/driver/registry_memory.go +++ b/driver/registry_memory.go @@ -2,6 +2,10 @@ package driver import ( "context" + "crypto/tls" + "crypto/x509" + "io/ioutil" + "net/http" "sync" "time" @@ -102,6 +106,41 @@ func (r *RegistryMemory) RuleMatcher() rule.Matcher { return r.ruleRepository } +func (r *RegistryMemory) UpstreamTransport(req *http.Request) (http.RoundTripper, error) { + + // Use req to decide the transport per request iff need be. + + certFile := r.c.ProxyServeUpstreamCaAppendCrtPath() + if certFile == "" { + return http.DefaultTransport, nil + } + + transport := &(*http.DefaultTransport.(*http.Transport)) // shallow copy + + // Get the SystemCertPool or continue with an empty pool on error + rootCAs, err := x509.SystemCertPool() + if err != nil { + return nil, err + } + + certs, err := ioutil.ReadFile(certFile) + if err != nil { + return nil, err + } + + // Append our cert to the system pool + if ok := rootCAs.AppendCertsFromPEM(certs); !ok { + return nil, errors.New("No certs appended, only system certs present, did you specify the correct cert file?") + } + + transport.TLSClientConfig = &tls.Config{ + InsecureSkipVerify: false, + RootCAs: rootCAs, + } + + return transport, nil +} + func NewRegistryMemory() *RegistryMemory { return &RegistryMemory{} } diff --git a/proxy/proxy.go b/proxy/proxy.go index a3ed07963c..baa5ad0e9f 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -22,6 +22,8 @@ package proxy import ( "context" + "crypto/tls" + "crypto/x509" "io/ioutil" "net/http" "net/url" @@ -41,6 +43,7 @@ type proxyRegistry interface { ProxyRequestHandler() *RequestHandler RuleMatcher() rule.Matcher + UpstreamTransport(r *http.Request) (http.RoundTripper, error) } func NewProxy(r proxyRegistry) *Proxy { @@ -88,7 +91,18 @@ func (d *Proxy) RoundTrip(r *http.Request) (*http.Response, error) { Header: rw.header, }, nil } else if err == nil { - res, err := http.DefaultTransport.RoundTrip(r) + + transport, err := d.r.UpstreamTransport(r) + if err != nil { + d.r.Logger(). + WithError(errors.WithStack(err)). + WithField("granted", false). + WithFields(fields). + Warn("Access request denied because upstream transport creation failed") + return nil, err + } + + res, err := transport.RoundTrip(r) if err != nil { d.r.Logger(). WithError(errors.WithStack(err)). @@ -194,3 +208,33 @@ func ConfigureBackendURL(r *http.Request, rl *rule.Rule) error { return nil } + +// Allow for extending the Root CA chain +// Use to avoid the error: "http: proxy error: x509: certificate signed by unknown authority" for self-signed +// certificates upstream. +func useTransportWithExtendedRootCa(certFile string) (transport *http.Transport, err error) { + transport = &(*http.DefaultTransport.(*http.Transport)) // shallow copy + + // Get the SystemCertPool or continue with an empty pool on error + rootCAs, err := x509.SystemCertPool() + if err != nil { + return nil, err + } + + certs, err := ioutil.ReadFile(certFile) + if err != nil { + return nil, err + } + + // Append our cert to the system pool + if ok := rootCAs.AppendCertsFromPEM(certs); !ok { + return nil, errors.New("No certs appended, only system certs present, did you specifi the correct cert file?") + } + + transport.TLSClientConfig = &tls.Config{ + InsecureSkipVerify: false, + RootCAs: rootCAs, + } + + return transport, nil +}