From f18a0b7b0744eac3c0696764aa6ccdcc08856b0c Mon Sep 17 00:00:00 2001 From: jet <71936688+jet-go@users.noreply.github.com> Date: Tue, 19 Aug 2025 14:56:16 +0900 Subject: [PATCH] feat: allow disable-keep-alives configuration in upstream (#3156) Signed-off-by: Jan Larwig --- CHANGELOG.md | 1 + docs/docs/configuration/alpha_config.md | 1 + docs/docs/configuration/overview.md | 1 + pkg/apis/options/legacy_options.go | 13 +++++++++---- pkg/apis/options/legacy_options_test.go | 11 +++++++++++ pkg/apis/options/upstreams.go | 4 ++++ pkg/upstream/http.go | 4 ++++ pkg/upstream/http_test.go | 26 +++++++++++++++++++------ 8 files changed, 51 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bcc843b..dce52ba7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - [#2273](https://github.com/oauth2-proxy/oauth2-proxy/pull/2273) feat: add Cidaas provider (@Bibob7, @Teko012) - [#3166](https://github.com/oauth2-proxy/oauth2-proxy/pull/3166) chore(dep): upgrade to latest golang 1.24.6 (@tuunit) +- [#3156](https://github.com/oauth2-proxy/oauth2-proxy/pull/3156) feat: allow disable-keep-alives configuration for upstream (@jet-go) # V7.11.0 diff --git a/docs/docs/configuration/alpha_config.md b/docs/docs/configuration/alpha_config.md index 018a2941..28645ceb 100644 --- a/docs/docs/configuration/alpha_config.md +++ b/docs/docs/configuration/alpha_config.md @@ -551,6 +551,7 @@ Requests will be proxied to this upstream if the path matches the request path. | `passHostHeader` | _bool_ | PassHostHeader determines whether the request host header should be proxied
to the upstream server.
Defaults to true. | | `proxyWebSockets` | _bool_ | ProxyWebSockets enables proxying of websockets to upstream servers
Defaults to true. | | `timeout` | _[Duration](#duration)_ | Timeout is the maximum duration the server will wait for a response from the upstream server.
Defaults to 30 seconds. | +| `disableKeepAlives` | _bool_ | DisableKeepAlives disables HTTP keep-alive connections to the upstream server.
Defaults to false. | ### UpstreamConfig diff --git a/docs/docs/configuration/overview.md b/docs/docs/configuration/overview.md index 7c216dfb..b159df09 100644 --- a/docs/docs/configuration/overview.md +++ b/docs/docs/configuration/overview.md @@ -261,6 +261,7 @@ Provider specific options can be found on their respective subpages. | flag: `--pass-host-header`
toml: `pass_host_header` | bool | pass the request Host Header to upstream | true | | flag: `--proxy-websockets`
toml: `proxy_websockets` | bool | enables WebSocket proxying | true | | flag: `--ssl-upstream-insecure-skip-verify`
toml: `ssl_upstream_insecure_skip_verify` | bool | skip validation of certificates presented when using HTTPS upstreams | false | +| flag: `--disable-keep-alives`
toml: `disable_keep_alives` | bool | disable HTTP keep-alive connections to the upstream server | false | | flag: `--upstream-timeout`
toml: `upstream_timeout` | duration | maximum amount of time the server will wait for a response from the upstream | 30s | | flag: `--upstream`
toml: `upstreams` | string \| list | the http url(s) of the upstream endpoint, file:// paths for static files or `static://` for static response. Routing is based on the path | | diff --git a/pkg/apis/options/legacy_options.go b/pkg/apis/options/legacy_options.go index a2c5f4e3..12975225 100644 --- a/pkg/apis/options/legacy_options.go +++ b/pkg/apis/options/legacy_options.go @@ -31,10 +31,11 @@ type LegacyOptions struct { func NewLegacyOptions() *LegacyOptions { return &LegacyOptions{ LegacyUpstreams: LegacyUpstreams{ - PassHostHeader: true, - ProxyWebSockets: true, - FlushInterval: DefaultUpstreamFlushInterval, - Timeout: DefaultUpstreamTimeout, + PassHostHeader: true, + ProxyWebSockets: true, + FlushInterval: DefaultUpstreamFlushInterval, + Timeout: DefaultUpstreamTimeout, + DisableKeepAlives: false, }, LegacyHeaders: LegacyHeaders{ @@ -105,6 +106,7 @@ type LegacyUpstreams struct { SSLUpstreamInsecureSkipVerify bool `flag:"ssl-upstream-insecure-skip-verify" cfg:"ssl_upstream_insecure_skip_verify"` Upstreams []string `flag:"upstream" cfg:"upstreams"` Timeout time.Duration `flag:"upstream-timeout" cfg:"upstream_timeout"` + DisableKeepAlives bool `flag:"disable-keep-alives" cfg:"disable_keep_alives"` } func legacyUpstreamsFlagSet() *pflag.FlagSet { @@ -116,6 +118,7 @@ func legacyUpstreamsFlagSet() *pflag.FlagSet { flagSet.Bool("ssl-upstream-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS upstreams") flagSet.StringSlice("upstream", []string{}, "the http url(s) of the upstream endpoint, file:// paths for static files or static:// for static response. Routing is based on the path") flagSet.Duration("upstream-timeout", DefaultUpstreamTimeout, "maximum amount of time the server will wait for a response from the upstream") + flagSet.Bool("disable-keep-alives", false, "disable HTTP keep-alive connections to the upstream server") return flagSet } @@ -144,6 +147,7 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) { ProxyWebSockets: &l.ProxyWebSockets, FlushInterval: &flushInterval, Timeout: &timeout, + DisableKeepAlives: l.DisableKeepAlives, } switch u.Scheme { @@ -176,6 +180,7 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) { upstream.ProxyWebSockets = nil upstream.FlushInterval = nil upstream.Timeout = nil + upstream.DisableKeepAlives = false case "unix": upstream.Path = "/" } diff --git a/pkg/apis/options/legacy_options_test.go b/pkg/apis/options/legacy_options_test.go index 84d7661f..9481cf95 100644 --- a/pkg/apis/options/legacy_options_test.go +++ b/pkg/apis/options/legacy_options_test.go @@ -24,6 +24,7 @@ var _ = Describe("Legacy Options", func() { legacyOpts.LegacyUpstreams.SSLUpstreamInsecureSkipVerify = true legacyOpts.LegacyUpstreams.Upstreams = []string{"http://foo.bar/baz", "file:///var/lib/website#/bar", "static://204"} legacyOpts.LegacyProvider.ClientID = "oauth-proxy" + legacyOpts.LegacyUpstreams.DisableKeepAlives = false truth := true staticCode := 204 @@ -38,6 +39,7 @@ var _ = Describe("Legacy Options", func() { PassHostHeader: &truth, ProxyWebSockets: &truth, Timeout: &timeout, + DisableKeepAlives: legacyOpts.LegacyUpstreams.DisableKeepAlives, }, { ID: "/bar", @@ -48,6 +50,7 @@ var _ = Describe("Legacy Options", func() { PassHostHeader: &truth, ProxyWebSockets: &truth, Timeout: &timeout, + DisableKeepAlives: legacyOpts.LegacyUpstreams.DisableKeepAlives, }, { ID: "static://204", @@ -60,6 +63,7 @@ var _ = Describe("Legacy Options", func() { PassHostHeader: nil, ProxyWebSockets: nil, Timeout: nil, + DisableKeepAlives: legacyOpts.LegacyUpstreams.DisableKeepAlives, }, }, } @@ -145,6 +149,7 @@ var _ = Describe("Legacy Options", func() { proxyWebSockets := true flushInterval := Duration(5 * time.Second) timeout := Duration(5 * time.Second) + disableKeepAlives := true // Test cases and expected outcomes validHTTP := "http://foo.bar/baz" @@ -157,6 +162,7 @@ var _ = Describe("Legacy Options", func() { ProxyWebSockets: &proxyWebSockets, FlushInterval: &flushInterval, Timeout: &timeout, + DisableKeepAlives: disableKeepAlives, } // Test cases and expected outcomes @@ -170,6 +176,7 @@ var _ = Describe("Legacy Options", func() { ProxyWebSockets: &proxyWebSockets, FlushInterval: &flushInterval, Timeout: &timeout, + DisableKeepAlives: disableKeepAlives, } validFileWithFragment := "file:///var/lib/website#/bar" @@ -182,6 +189,7 @@ var _ = Describe("Legacy Options", func() { ProxyWebSockets: &proxyWebSockets, FlushInterval: &flushInterval, Timeout: &timeout, + DisableKeepAlives: disableKeepAlives, } validStatic := "static://204" @@ -197,6 +205,7 @@ var _ = Describe("Legacy Options", func() { ProxyWebSockets: nil, FlushInterval: nil, Timeout: nil, + DisableKeepAlives: false, } invalidStatic := "static://abc" @@ -212,6 +221,7 @@ var _ = Describe("Legacy Options", func() { ProxyWebSockets: nil, FlushInterval: nil, Timeout: nil, + DisableKeepAlives: false, } invalidHTTP := ":foo" @@ -226,6 +236,7 @@ var _ = Describe("Legacy Options", func() { ProxyWebSockets: proxyWebSockets, FlushInterval: time.Duration(flushInterval), Timeout: time.Duration(timeout), + DisableKeepAlives: disableKeepAlives, } upstreams, err := legacyUpstreams.convert() diff --git a/pkg/apis/options/upstreams.go b/pkg/apis/options/upstreams.go index 971d151e..b3c7195f 100644 --- a/pkg/apis/options/upstreams.go +++ b/pkg/apis/options/upstreams.go @@ -93,4 +93,8 @@ type Upstream struct { // Timeout is the maximum duration the server will wait for a response from the upstream server. // Defaults to 30 seconds. Timeout *Duration `json:"timeout,omitempty"` + + // DisableKeepAlives disables HTTP keep-alive connections to the upstream server. + // Defaults to false. + DisableKeepAlives bool `json:"disableKeepAlives,omitempty"` } diff --git a/pkg/upstream/http.go b/pkg/upstream/http.go index 56fcc23e..7a0e6e84 100644 --- a/pkg/upstream/http.go +++ b/pkg/upstream/http.go @@ -166,6 +166,10 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr proxy.ErrorHandler = errorHandler } + // Pass on DisableKeepAlives to the transport settings + // to allow for disabling HTTP keep-alive connections + transport.DisableKeepAlives = upstream.DisableKeepAlives + // Apply the customized transport to our proxy before returning it proxy.Transport = transport diff --git a/pkg/upstream/http_test.go b/pkg/upstream/http_test.go index 16f75f2d..31476df0 100644 --- a/pkg/upstream/http_test.go +++ b/pkg/upstream/http_test.go @@ -372,12 +372,13 @@ var _ = Describe("HTTP Upstream Suite", func() { }) type newUpstreamTableInput struct { - proxyWebSockets bool - flushInterval options.Duration - skipVerify bool - sigData *options.SignatureData - errorHandler func(http.ResponseWriter, *http.Request, error) - timeout options.Duration + proxyWebSockets bool + flushInterval options.Duration + skipVerify bool + sigData *options.SignatureData + errorHandler func(http.ResponseWriter, *http.Request, error) + timeout options.Duration + disableKeepAlives bool } DescribeTable("newHTTPUpstreamProxy", @@ -391,6 +392,7 @@ var _ = Describe("HTTP Upstream Suite", func() { InsecureSkipTLSVerify: in.skipVerify, ProxyWebSockets: &in.proxyWebSockets, Timeout: &in.timeout, + DisableKeepAlives: in.disableKeepAlives, } handler := newHTTPUpstreamProxy(upstream, u, in.sigData, in.errorHandler) @@ -412,6 +414,9 @@ var _ = Describe("HTTP Upstream Suite", func() { if in.skipVerify { Expect(transport.TLSClientConfig.InsecureSkipVerify).To(Equal(true)) } + if in.disableKeepAlives { + Expect(transport.DisableKeepAlives).To(Equal(true)) + } }, Entry("with proxy websockets", &newUpstreamTableInput{ proxyWebSockets: true, @@ -463,6 +468,15 @@ var _ = Describe("HTTP Upstream Suite", func() { errorHandler: nil, timeout: options.Duration(5 * time.Second), }), + Entry("with a DisableKeepAlives", &newUpstreamTableInput{ + proxyWebSockets: false, + flushInterval: defaultFlushInterval, + skipVerify: false, + sigData: nil, + errorHandler: nil, + timeout: defaultTimeout, + disableKeepAlives: true, + }), ) Context("with a websocket proxy", func() {