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

feat: Extended Root CA for upstream connections (#181) #706

Closed
wants to merge 12 commits into from
Closed
16 changes: 16 additions & 0 deletions .schema/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
22 changes: 22 additions & 0 deletions docs/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<value>
# - Windows Command Line (CMD):
# > set SERVE_PROXY_UPSTREAM_CA_APPEND_CRT_PATH=<value>
#
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.
Expand Down
3 changes: 2 additions & 1 deletion driver/configuration/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -41,6 +41,7 @@ type Provider interface {
ProxyReadTimeout() time.Duration
ProxyWriteTimeout() time.Duration
ProxyIdleTimeout() time.Duration
ProxyServeUpstreamCaAppendCrtPath() string

APIReadTimeout() time.Duration
APIWriteTimeout() time.Duration
Expand Down
5 changes: 5 additions & 0 deletions driver/configuration/provider_viper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
Expand Down
39 changes: 39 additions & 0 deletions driver/registry_memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package driver

import (
"context"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"net/http"
"sync"
"time"

Expand Down Expand Up @@ -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{}
}
Expand Down
46 changes: 45 additions & 1 deletion proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ package proxy

import (
"context"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"net/http"
"net/url"
Expand All @@ -41,6 +43,7 @@ type proxyRegistry interface {

ProxyRequestHandler() *RequestHandler
RuleMatcher() rule.Matcher
UpstreamTransport(r *http.Request) (http.RoundTripper, error)
}

func NewProxy(r proxyRegistry) *Proxy {
Expand Down Expand Up @@ -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)).
Expand Down Expand Up @@ -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
}