Skip to content

Commit f126ca0

Browse files
committed
feat: adds option to append certificate file to root ca for upstream connections (ory#181)
1 parent e8efa64 commit f126ca0

File tree

6 files changed

+129
-2
lines changed

6 files changed

+129
-2
lines changed

.schema/config.schema.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,6 +1170,22 @@
11701170
"timeout": {
11711171
"$ref": "#/definitions/serverTimeout"
11721172
},
1173+
"upstream": {
1174+
"type": "object",
1175+
"title": "HTTP Upstream",
1176+
"additionalProperties": false,
1177+
"properties": {
1178+
"ca_append_crt_path": {
1179+
"type": "string",
1180+
"default": "",
1181+
"examples": [
1182+
"./self-signed.crt"
1183+
],
1184+
"title": "CA Certificate",
1185+
"description": "The file containing the CA certificates to append to the Root CA when using upstream connections."
1186+
}
1187+
}
1188+
},
11731189
"cors": {
11741190
"$ref": "#/definitions/cors"
11751191
},

docs/docs/reference/configuration.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,6 +1498,28 @@ serve:
14981498
#
14991499
read: 5s
15001500

1501+
## HTTP Upstream ##
1502+
#
1503+
# Control the HTTP upstream.
1504+
#
1505+
upstream:
1506+
## Append Certificate To Root CA ##
1507+
#
1508+
# 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.
1509+
#
1510+
# Default value: ""
1511+
#
1512+
# Examples:
1513+
# - self-signed.crt
1514+
#
1515+
# Set this value using environment variables on
1516+
# - Linux/macOS:
1517+
# $ export SERVE_PROXY_UPSTREAM_CA_APPEND_CRT_PATH=<value>
1518+
# - Windows Command Line (CMD):
1519+
# > set SERVE_PROXY_UPSTREAM_CA_APPEND_CRT_PATH=<value>
1520+
#
1521+
ca_append_crt_path: ""
1522+
15011523
## Cross Origin Resource Sharing (CORS) ##
15021524
#
15031525
# Configure [Cross Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) using the following options.

driver/configuration/provider.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"net/url"
66
"time"
77

8-
"github.com/gobuffalo/packr/v2"
8+
packr "github.com/gobuffalo/packr/v2"
99

1010
"github.com/ory/fosite"
1111
"github.com/ory/x/tracing"
@@ -41,6 +41,7 @@ type Provider interface {
4141
ProxyReadTimeout() time.Duration
4242
ProxyWriteTimeout() time.Duration
4343
ProxyIdleTimeout() time.Duration
44+
ProxyServeUpstreamCaAppendCrtPath() string
4445

4546
APIReadTimeout() time.Duration
4647
APIWriteTimeout() time.Duration

driver/configuration/provider_viper.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const (
4343
ViperKeyProxyIdleTimeout = "serve.proxy.timeout.idle"
4444
ViperKeyProxyServeAddressHost = "serve.proxy.host"
4545
ViperKeyProxyServeAddressPort = "serve.proxy.port"
46+
ViperKeyProxyUpstreamCaAppendCrtPath = "serve.proxy.upstream.ca_append_crt_path"
4647
ViperKeyAPIServeAddressHost = "serve.api.host"
4748
ViperKeyAPIServeAddressPort = "serve.api.port"
4849
ViperKeyAPIReadTimeout = "serve.api.timeout.read"
@@ -177,6 +178,10 @@ func (v *ViperProvider) ProxyServeAddress() string {
177178
)
178179
}
179180

181+
func (v *ViperProvider) ProxyServeUpstreamCaAppendCrtPath() string {
182+
return viperx.GetString(v.l, ViperKeyProxyUpstreamCaAppendCrtPath, "")
183+
}
184+
180185
func (v *ViperProvider) APIReadTimeout() time.Duration {
181186
return viperx.GetDuration(v.l, ViperKeyAPIReadTimeout, time.Second*5)
182187
}

driver/registry_memory.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ package driver
22

33
import (
44
"context"
5+
"crypto/tls"
6+
"crypto/x509"
7+
"io/ioutil"
8+
"net/http"
59
"sync"
610
"time"
711

@@ -102,6 +106,41 @@ func (r *RegistryMemory) RuleMatcher() rule.Matcher {
102106
return r.ruleRepository
103107
}
104108

109+
func (r *RegistryMemory) UpstreamTransport(req *http.Request) (http.RoundTripper, error) {
110+
111+
// Use req to decide the transport per request iff need be.
112+
113+
certFile := r.c.ProxyServeUpstreamCaAppendCrtPath()
114+
if certFile == "" {
115+
return http.DefaultTransport, nil
116+
}
117+
118+
transport := &(*http.DefaultTransport.(*http.Transport)) // shallow copy
119+
120+
// Get the SystemCertPool or continue with an empty pool on error
121+
rootCAs, err := x509.SystemCertPool()
122+
if err != nil {
123+
return nil, err
124+
}
125+
126+
certs, err := ioutil.ReadFile(certFile)
127+
if err != nil {
128+
return nil, err
129+
}
130+
131+
// Append our cert to the system pool
132+
if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
133+
return nil, errors.New("No certs appended, only system certs present, did you specify the correct cert file?")
134+
}
135+
136+
transport.TLSClientConfig = &tls.Config{
137+
InsecureSkipVerify: false,
138+
RootCAs: rootCAs,
139+
}
140+
141+
return transport, nil
142+
}
143+
105144
func NewRegistryMemory() *RegistryMemory {
106145
return &RegistryMemory{}
107146
}

proxy/proxy.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ package proxy
2222

2323
import (
2424
"context"
25+
"crypto/tls"
26+
"crypto/x509"
2527
"io/ioutil"
2628
"net/http"
2729
"net/url"
@@ -41,6 +43,7 @@ type proxyRegistry interface {
4143

4244
ProxyRequestHandler() *RequestHandler
4345
RuleMatcher() rule.Matcher
46+
UpstreamTransport(r *http.Request) (http.RoundTripper, error)
4447
}
4548

4649
func NewProxy(r proxyRegistry) *Proxy {
@@ -88,7 +91,18 @@ func (d *Proxy) RoundTrip(r *http.Request) (*http.Response, error) {
8891
Header: rw.header,
8992
}, nil
9093
} else if err == nil {
91-
res, err := http.DefaultTransport.RoundTrip(r)
94+
95+
transport, err := d.r.UpstreamTransport(r)
96+
if err != nil {
97+
d.r.Logger().
98+
WithError(errors.WithStack(err)).
99+
WithField("granted", false).
100+
WithFields(fields).
101+
Warn("Access request denied because upstream transport creation failed")
102+
return nil, err
103+
}
104+
105+
res, err := transport.RoundTrip(r)
92106
if err != nil {
93107
d.r.Logger().
94108
WithError(errors.WithStack(err)).
@@ -194,3 +208,33 @@ func ConfigureBackendURL(r *http.Request, rl *rule.Rule) error {
194208

195209
return nil
196210
}
211+
212+
// Allow for extending the Root CA chain
213+
// Use to avoid the error: "http: proxy error: x509: certificate signed by unknown authority" for self-signed
214+
// certificates upstream.
215+
func useTransportWithExtendedRootCa(certFile string) (transport *http.Transport, err error) {
216+
transport = &(*http.DefaultTransport.(*http.Transport)) // shallow copy
217+
218+
// Get the SystemCertPool or continue with an empty pool on error
219+
rootCAs, err := x509.SystemCertPool()
220+
if err != nil {
221+
return nil, err
222+
}
223+
224+
certs, err := ioutil.ReadFile(certFile)
225+
if err != nil {
226+
return nil, err
227+
}
228+
229+
// Append our cert to the system pool
230+
if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
231+
return nil, errors.New("No certs appended, only system certs present, did you specifi the correct cert file?")
232+
}
233+
234+
transport.TLSClientConfig = &tls.Config{
235+
InsecureSkipVerify: false,
236+
RootCAs: rootCAs,
237+
}
238+
239+
return transport, nil
240+
}

0 commit comments

Comments
 (0)