Skip to content

Commit e9e8090

Browse files
authored
Merge pull request #159 from buzzfeed/sso-proxy-transport-reset-deadline
sso_proxy: add reset deadline for http transports
2 parents 683727d + 43b4535 commit e9e8090

File tree

4 files changed

+57
-21
lines changed

4 files changed

+57
-21
lines changed

internal/proxy/oauthproxy.go

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"reflect"
1414
"regexp"
1515
"strings"
16+
"sync"
1617
"time"
1718

1819
"github.com/buzzfeed/sso/internal/pkg/aead"
@@ -145,14 +146,23 @@ func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
145146
}
146147

147148
// upstreamTransport is used to ensure that upstreams cannot override the
148-
// security headers applied by sso_proxy
149+
// security headers applied by sso_proxy. Also includes functionality to rotate
150+
// http.Transport objects to ensure tcp connection reset deadlines are not exceeded
149151
type upstreamTransport struct {
150-
transport *http.Transport
152+
mux sync.Mutex
153+
154+
deadAfter time.Time
155+
resetDeadline time.Duration
156+
157+
transport *http.Transport
158+
insecureSkipVerify bool
151159
}
152160

153161
// RoundTrip round trips the request and deletes security headers before returning the response.
154162
func (t *upstreamTransport) RoundTrip(req *http.Request) (*http.Response, error) {
155-
resp, err := t.transport.RoundTrip(req)
163+
transport := t.getTransport()
164+
165+
resp, err := transport.RoundTrip(req)
156166
if err != nil {
157167
logger := log.NewLogEntry()
158168
logger.Error(err, "error in upstreamTransport RoundTrip")
@@ -164,9 +174,14 @@ func (t *upstreamTransport) RoundTrip(req *http.Request) (*http.Response, error)
164174
return resp, err
165175
}
166176

167-
func newUpstreamTransport(insecureSkipVerify bool) *upstreamTransport {
168-
return &upstreamTransport{
169-
transport: &http.Transport{
177+
// getTransport gets either a cached http transport or allocates a new one
178+
func (t *upstreamTransport) getTransport() *http.Transport {
179+
t.mux.Lock()
180+
defer t.mux.Unlock()
181+
182+
if t.transport == nil || time.Now().After(t.deadAfter) {
183+
t.deadAfter = time.Now().Add(t.resetDeadline)
184+
t.transport = &http.Transport{
170185
Proxy: http.ProxyFromEnvironment,
171186
DialContext: (&net.Dialer{
172187
Timeout: 30 * time.Second,
@@ -176,17 +191,22 @@ func newUpstreamTransport(insecureSkipVerify bool) *upstreamTransport {
176191
MaxIdleConns: 100,
177192
IdleConnTimeout: 90 * time.Second,
178193
TLSHandshakeTimeout: 10 * time.Second,
179-
TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureSkipVerify},
194+
TLSClientConfig: &tls.Config{InsecureSkipVerify: t.insecureSkipVerify},
180195
ExpectContinueTimeout: 1 * time.Second,
181-
},
196+
}
182197
}
198+
199+
return t.transport
183200
}
184201

185202
// NewReverseProxy creates a reverse proxy to a specified url.
186203
// It adds an X-Forwarded-Host header that is the request's host.
187204
func NewReverseProxy(to *url.URL, config *UpstreamConfig) *httputil.ReverseProxy {
188205
proxy := httputil.NewSingleHostReverseProxy(to)
189-
proxy.Transport = newUpstreamTransport(config.TLSSkipVerify)
206+
proxy.Transport = &upstreamTransport{
207+
resetDeadline: config.ResetDeadline,
208+
insecureSkipVerify: config.TLSSkipVerify,
209+
}
190210
director := proxy.Director
191211
proxy.Director = func(req *http.Request) {
192212
req.Header.Add("X-Forwarded-Host", req.Host)
@@ -203,7 +223,10 @@ func NewReverseProxy(to *url.URL, config *UpstreamConfig) *httputil.ReverseProxy
203223
// It adds an X-Forwarded-Host header to the the upstream's request.
204224
func NewRewriteReverseProxy(route *RewriteRoute, config *UpstreamConfig) *httputil.ReverseProxy {
205225
proxy := &httputil.ReverseProxy{}
206-
proxy.Transport = newUpstreamTransport(config.TLSSkipVerify)
226+
proxy.Transport = &upstreamTransport{
227+
resetDeadline: config.ResetDeadline,
228+
insecureSkipVerify: config.TLSSkipVerify,
229+
}
207230
proxy.Director = func(req *http.Request) {
208231
// we do this to rewrite requests
209232
rewritten := route.FromRegex.ReplaceAllString(req.Host, route.ToTemplate.Opaque)

internal/proxy/oauthproxy_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,10 @@ func TestRoundTrip(t *testing.T) {
454454
for _, tc := range testCases {
455455
t.Run(tc.name, func(t *testing.T) {
456456
req := httptest.NewRequest("GET", tc.url, nil)
457-
ut := newUpstreamTransport(false)
457+
ut := &upstreamTransport{
458+
insecureSkipVerify: false,
459+
resetDeadline: time.Duration(1) * time.Minute,
460+
}
458461
resp, err := ut.RoundTrip(req)
459462
if err == nil && tc.expectedError {
460463
t.Errorf("expected error but error was nil")

internal/proxy/options.go

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
// ClientID - the OAuth Client ID: ie: "123456.apps.googleusercontent.com"
3232
// ClientSecret - The OAuth Client Secret
3333
// DefaultUpstreamTimeout - the default time period to wait for a response from an upstream
34+
// DefaultUpstreamTCPResetDeadline - the default time period to wait for a response from an upstream
3435
// TCPWriteTimeout - http server tcp write timeout
3536
// TCPReadTimeout - http server tcp read timeout
3637
// CookieName - name of the cookie
@@ -66,7 +67,8 @@ type Options struct {
6667
ClientID string `envconfig:"CLIENT_ID"`
6768
ClientSecret string `envconfig:"CLIENT_SECRET"`
6869

69-
DefaultUpstreamTimeout time.Duration `envconfig:"DEFAULT_UPSTREAM_TIMEOUT" default:"10s"`
70+
DefaultUpstreamTimeout time.Duration `envconfig:"DEFAULT_UPSTREAM_TIMEOUT" default:"10s"`
71+
DefaultUpstreamTCPResetDeadline time.Duration `envconfig:"DEFAULT_UPSTREAM_TCP_RESET_DEADLINE" default:"60s"`
7072

7173
TCPWriteTimeout time.Duration `envconfig:"TCP_WRITE_TIMEOUT" default:"30s"`
7274
TCPReadTimeout time.Duration `envconfig:"TCP_READ_TIMEOUT" default:"30s"`
@@ -110,15 +112,19 @@ type Options struct {
110112
// NewOptions returns a new options struct
111113
func NewOptions() *Options {
112114
return &Options{
113-
CookieName: "_sso_proxy",
114-
CookieSecure: true,
115-
CookieHTTPOnly: true,
116-
CookieExpire: time.Duration(168) * time.Hour,
117-
SkipAuthPreflight: false,
118-
RequestLogging: true,
119-
DefaultUpstreamTimeout: time.Duration(1) * time.Second,
120-
DefaultAllowedGroups: []string{},
121-
PassAccessToken: false,
115+
CookieName: "_sso_proxy",
116+
CookieSecure: true,
117+
CookieHTTPOnly: true,
118+
CookieExpire: time.Duration(168) * time.Hour,
119+
120+
SkipAuthPreflight: false,
121+
RequestLogging: true,
122+
123+
DefaultUpstreamTimeout: time.Duration(1) * time.Second,
124+
DefaultUpstreamTCPResetDeadline: time.Duration(1) * time.Minute,
125+
126+
DefaultAllowedGroups: []string{},
127+
PassAccessToken: false,
122128
}
123129
}
124130

@@ -185,6 +191,7 @@ func (o *Options) Validate() error {
185191
defaultUpstreamOptionsConfig := &OptionsConfig{
186192
AllowedGroups: o.DefaultAllowedGroups,
187193
Timeout: o.DefaultUpstreamTimeout,
194+
ResetDeadline: o.DefaultUpstreamTCPResetDeadline,
188195
}
189196

190197
o.upstreamConfigs, err = loadServiceConfigs(rawBytes, o.Cluster, o.Scheme, templateVars, defaultUpstreamOptionsConfig)

internal/proxy/proxy_config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ type UpstreamConfig struct {
5656
PreserveHost bool
5757
HMACAuth hmacauth.HmacAuth
5858
Timeout time.Duration
59+
ResetDeadline time.Duration
5960
FlushInterval time.Duration
6061
HeaderOverrides map[string]string
6162
SkipRequestSigning bool
@@ -85,6 +86,7 @@ type OptionsConfig struct {
8586
TLSSkipVerify bool `yaml:"tls_skip_verify"`
8687
PreserveHost bool `yaml:"preserve_host"`
8788
Timeout time.Duration `yaml:"timeout"`
89+
ResetDeadline time.Duration `yaml:"reset_deadline"`
8890
FlushInterval time.Duration `yaml:"flush_interval"`
8991
SkipRequestSigning bool `yaml:"skip_request_signing"`
9092
}
@@ -382,6 +384,7 @@ func parseOptionsConfig(proxy *UpstreamConfig, defaultOpts *OptionsConfig) error
382384

383385
proxy.AllowedGroups = dst.AllowedGroups
384386
proxy.Timeout = dst.Timeout
387+
proxy.ResetDeadline = dst.ResetDeadline
385388
proxy.FlushInterval = dst.FlushInterval
386389
proxy.HeaderOverrides = dst.HeaderOverrides
387390
proxy.TLSSkipVerify = dst.TLSSkipVerify

0 commit comments

Comments
 (0)