Skip to content

Commit 06367d0

Browse files
Merge pull request #258 from kwilczynski/feature/backport-upstream-timeout
APPSRE-7970: Add support for configuring upstream timeout
2 parents bb6c702 + 5ca22d1 commit 06367d0

File tree

5 files changed

+96
-31
lines changed

5 files changed

+96
-31
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ Usage of oauth-proxy:
265265
-tls-cert string: path to certificate file
266266
-tls-key string: path to private key file
267267
-upstream value: the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path
268+
-upstream-timeout duration: maximum amount of time the server will wait for a response from the upstream (default 30s)
268269
-validate-url string: Access token validation endpoint
269270
-version: print version string
270271
```

main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ func main() {
103103
flagSet.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)")
104104
flagSet.Var(upstreamCAs, "upstream-ca", "paths to CA roots for the Upstream (target) Server (may be given multiple times, defaults to system trust store).")
105105

106+
flagSet.Duration("upstream-timeout", DefaultUpstreamTimeout, "maximum amount of time the server will wait for a response from the upstream")
107+
106108
providerOpenShift := openshift.New()
107109
providerOpenShift.Bind(flagSet)
108110

oauthproxy.go

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,27 +109,34 @@ func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
109109

110110
}
111111

112-
func NewReverseProxy(target *url.URL, upstreamFlush time.Duration, rootCAs []string) (*httputil.ReverseProxy, error) {
112+
func NewReverseProxy(target *url.URL, opts *Options) (*httputil.ReverseProxy, error) {
113113
proxy := httputil.NewSingleHostReverseProxy(target)
114-
proxy.FlushInterval = upstreamFlush
114+
proxy.FlushInterval = opts.UpstreamFlush
115115

116-
transport := &http.Transport{
117-
MaxIdleConnsPerHost: 500,
118-
IdleConnTimeout: 1 * time.Minute,
119-
}
120-
if len(rootCAs) > 0 {
121-
pool, err := util.GetCertPool(rootCAs, false)
116+
// Inherit default transport options from Go's stdlib
117+
transport := http.DefaultTransport.(*http.Transport).Clone()
118+
119+
transport.MaxIdleConnsPerHost = 500
120+
transport.IdleConnTimeout = 1 * time.Minute
121+
122+
// Change default duration for waiting for an upstream response
123+
transport.ResponseHeaderTimeout = opts.Timeout
124+
125+
if len(opts.UpstreamCAs) > 0 {
126+
pool, err := util.GetCertPool(opts.UpstreamCAs, false)
122127
if err != nil {
123128
return nil, err
124129
}
125130
transport.TLSClientConfig = oscrypto.SecureTLSConfig(&tls.Config{RootCAs: pool})
126131
}
127132
if err := http2.ConfigureTransport(transport); err != nil {
128-
if len(rootCAs) > 0 {
133+
if len(opts.UpstreamCAs) > 0 {
129134
return nil, err
130135
}
131136
log.Printf("WARN: Failed to configure http2 transport: %v", err)
132137
}
138+
139+
// Apply the customized transport to our proxy before returning it
133140
proxy.Transport = transport
134141

135142
return proxy, nil
@@ -160,7 +167,7 @@ func NewFileServer(path string, filesystemPath string) (proxy http.Handler) {
160167

161168
func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.HmacAuth) (restProxy http.Handler) {
162169
u.Path = ""
163-
proxy, err := NewReverseProxy(u, opts.UpstreamFlush, opts.UpstreamCAs)
170+
proxy, err := NewReverseProxy(u, opts)
164171
if err != nil {
165172
log.Fatal("Failed to initialize Reverse Proxy: ", err)
166173
}

oauthproxy_test.go

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
"github.com/18F/hmacauth"
1919
"github.com/bmizerany/assert"
20+
"github.com/stretchr/testify/require"
2021

2122
"github.com/openshift/oauth-proxy/providers"
2223
"golang.org/x/net/websocket"
@@ -105,55 +106,99 @@ func TestWebSocketProxy(t *testing.T) {
105106
}
106107

107108
func TestNewReverseProxy(t *testing.T) {
109+
opts := NewOptions()
110+
opts.UpstreamFlush = 5 * time.Millisecond
111+
108112
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
109113
w.WriteHeader(200)
110114
hostname, _, _ := net.SplitHostPort(r.Host)
111115
w.Write([]byte(hostname))
112116
}))
113117
defer backend.Close()
114118

115-
backendURL, _ := url.Parse(backend.URL)
116-
backendHostname, backendPort, _ := net.SplitHostPort(backendURL.Host)
117-
backendHost := net.JoinHostPort(backendHostname, backendPort)
118-
proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
119+
proxyURL, err := url.Parse(backend.URL)
120+
require.NoError(t, err)
121+
122+
backendHost, _, err := net.SplitHostPort(proxyURL.Host)
123+
require.NoError(t, err)
124+
125+
proxyHandler, err := NewReverseProxy(proxyURL, opts)
126+
require.NoError(t, err)
119127

120-
proxyHandler, _ := NewReverseProxy(proxyURL, 5*time.Millisecond, nil)
121128
setProxyUpstreamHostHeader(proxyHandler, proxyURL)
122129
frontend := httptest.NewServer(proxyHandler)
123130
defer frontend.Close()
124131

125-
getReq, _ := http.NewRequest("GET", frontend.URL, nil)
126-
res, _ := http.DefaultClient.Do(getReq)
127-
bodyBytes, _ := ioutil.ReadAll(res.Body)
128-
if g, e := string(bodyBytes), backendHostname; g != e {
129-
t.Errorf("got body %q; expected %q", g, e)
130-
}
132+
getReq, err := http.NewRequest("GET", frontend.URL, nil)
133+
require.NoError(t, err)
134+
135+
res, err := http.DefaultClient.Do(getReq)
136+
require.NoError(t, err)
137+
138+
bodyBytes, err := ioutil.ReadAll(res.Body)
139+
require.NoError(t, err)
140+
141+
assert.Equal(t, backendHost, string(bodyBytes))
131142
}
132143

133144
func TestEncodedSlashes(t *testing.T) {
145+
opts := NewOptions()
146+
opts.UpstreamFlush = 5 * time.Millisecond
147+
134148
var seen string
135149
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
136150
w.WriteHeader(200)
137151
seen = r.RequestURI
138152
}))
139153
defer backend.Close()
140154

141-
b, _ := url.Parse(backend.URL)
142-
proxyHandler, _ := NewReverseProxy(b, 5*time.Millisecond, nil)
155+
b, err := url.Parse(backend.URL)
156+
require.NoError(t, err)
157+
158+
proxyHandler, err := NewReverseProxy(b, opts)
159+
require.NoError(t, err)
160+
143161
setProxyDirector(proxyHandler)
144162
frontend := httptest.NewServer(proxyHandler)
145163
defer frontend.Close()
146164

147-
f, _ := url.Parse(frontend.URL)
165+
f, err := url.Parse(frontend.URL)
166+
require.NoError(t, err)
167+
148168
encodedPath := "/a%2Fb/?c=1"
149169
getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: f.Host, Opaque: encodedPath}}
150-
_, err := http.DefaultClient.Do(getReq)
151-
if err != nil {
152-
t.Fatalf("err %s", err)
153-
}
154-
if seen != encodedPath {
155-
t.Errorf("got bad request %q expected %q", seen, encodedPath)
156-
}
170+
_, err = http.DefaultClient.Do(getReq)
171+
require.NoError(t, err)
172+
173+
assert.Equal(t, encodedPath, seen)
174+
}
175+
176+
func TestNewReverseProxyWithTimeOut(t *testing.T) {
177+
opts := NewOptions()
178+
opts.Timeout = 1 * time.Millisecond
179+
180+
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
181+
time.Sleep(5 * time.Millisecond)
182+
}))
183+
defer backend.Close()
184+
185+
proxyURL, err := url.Parse(backend.URL)
186+
require.NoError(t, err)
187+
188+
proxyHandler, err := NewReverseProxy(proxyURL, opts)
189+
require.NoError(t, err)
190+
191+
setProxyUpstreamHostHeader(proxyHandler, proxyURL)
192+
frontend := httptest.NewServer(proxyHandler)
193+
defer frontend.Close()
194+
195+
getReq, err := http.NewRequest("GET", frontend.URL, nil)
196+
require.NoError(t, err)
197+
198+
res, err := http.DefaultClient.Do(getReq)
199+
require.NoError(t, err)
200+
201+
assert.Equal(t, "502 Bad Gateway", res.Status)
157202
}
158203

159204
func TestRobotsTxt(t *testing.T) {

options.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ import (
2020
"github.com/openshift/oauth-proxy/providers/openshift"
2121
)
2222

23+
const (
24+
// DefaultUpstreamTimeout is the maximum duration a network dial to a upstream server for a response.
25+
DefaultUpstreamTimeout = 30 * time.Second
26+
)
27+
2328
// Configuration Options that can be set by Command Line Flag, or Config File
2429
type Options struct {
2530
ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy-prefix"`
@@ -101,6 +106,10 @@ type Options struct {
101106
// provider to destroy their single sign-on session.
102107
LogoutRedirectURL string `flag:"logout-url" cfg:"logout_url"`
103108

109+
// Timeout is the maximum duration the server will wait for a response from the upstream server.
110+
// Defaults to 30 seconds.
111+
Timeout time.Duration `flag:"upstream-timeout" cfg:"upstream_timeout"`
112+
104113
// internal values that are set after config validation
105114
redirectURL *url.URL
106115
proxyURLs []*url.URL
@@ -137,6 +146,7 @@ func NewOptions() *Options {
137146
PassHostHeader: true,
138147
ApprovalPrompt: "force",
139148
RequestLogging: true,
149+
Timeout: DefaultUpstreamTimeout,
140150
}
141151
}
142152

0 commit comments

Comments
 (0)