Merge 9f42454aae into 1f049e5ebd
This commit is contained in:
commit
e146e5a3fd
|
|
@ -289,6 +289,7 @@ func (p *OAuthProxy) setupServer(opts *options.Options) error {
|
||||||
BindAddress: opts.Server.BindAddress,
|
BindAddress: opts.Server.BindAddress,
|
||||||
SecureBindAddress: opts.Server.SecureBindAddress,
|
SecureBindAddress: opts.Server.SecureBindAddress,
|
||||||
TLS: opts.Server.TLS,
|
TLS: opts.Server.TLS,
|
||||||
|
HTTP2: opts.Server.HTTP2,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option: AllowQuerySemicolons
|
// Option: AllowQuerySemicolons
|
||||||
|
|
@ -306,6 +307,7 @@ func (p *OAuthProxy) setupServer(opts *options.Options) error {
|
||||||
BindAddress: opts.MetricsServer.BindAddress,
|
BindAddress: opts.MetricsServer.BindAddress,
|
||||||
SecureBindAddress: opts.MetricsServer.SecureBindAddress,
|
SecureBindAddress: opts.MetricsServer.SecureBindAddress,
|
||||||
TLS: opts.MetricsServer.TLS,
|
TLS: opts.MetricsServer.TLS,
|
||||||
|
HTTP2: opts.MetricsServer.HTTP2,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not build metrics server: %v", err)
|
return fmt.Errorf("could not build metrics server: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -482,6 +482,7 @@ type LegacyServer struct {
|
||||||
TLSKeyFile string `flag:"tls-key-file" cfg:"tls_key_file"`
|
TLSKeyFile string `flag:"tls-key-file" cfg:"tls_key_file"`
|
||||||
TLSMinVersion string `flag:"tls-min-version" cfg:"tls_min_version"`
|
TLSMinVersion string `flag:"tls-min-version" cfg:"tls_min_version"`
|
||||||
TLSCipherSuites []string `flag:"tls-cipher-suite" cfg:"tls_cipher_suites"`
|
TLSCipherSuites []string `flag:"tls-cipher-suite" cfg:"tls_cipher_suites"`
|
||||||
|
ForceHTTP2 bool `flag:"force-http2" cfg:"force_http2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func legacyServerFlagset() *pflag.FlagSet {
|
func legacyServerFlagset() *pflag.FlagSet {
|
||||||
|
|
@ -497,6 +498,7 @@ func legacyServerFlagset() *pflag.FlagSet {
|
||||||
flagSet.String("tls-key-file", "", "path to private key file")
|
flagSet.String("tls-key-file", "", "path to private key file")
|
||||||
flagSet.String("tls-min-version", "", "minimal TLS version for HTTPS clients (either \"TLS1.2\" or \"TLS1.3\")")
|
flagSet.String("tls-min-version", "", "minimal TLS version for HTTPS clients (either \"TLS1.2\" or \"TLS1.3\")")
|
||||||
flagSet.StringSlice("tls-cipher-suite", []string{}, "restricts TLS cipher suites to those listed (e.g. TLS_RSA_WITH_RC4_128_SHA) (may be given multiple times)")
|
flagSet.StringSlice("tls-cipher-suite", []string{}, "restricts TLS cipher suites to those listed (e.g. TLS_RSA_WITH_RC4_128_SHA) (may be given multiple times)")
|
||||||
|
flagSet.Bool("force-http2", false, "enable HTTP/2 support (h2c for HTTP, h2 for HTTPS); required for gRPC proxying")
|
||||||
|
|
||||||
return flagSet
|
return flagSet
|
||||||
}
|
}
|
||||||
|
|
@ -653,6 +655,7 @@ func (l LegacyServer) convert() (Server, Server) {
|
||||||
appServer := Server{
|
appServer := Server{
|
||||||
BindAddress: l.HTTPAddress,
|
BindAddress: l.HTTPAddress,
|
||||||
SecureBindAddress: l.HTTPSAddress,
|
SecureBindAddress: l.HTTPSAddress,
|
||||||
|
HTTP2: l.ForceHTTP2,
|
||||||
}
|
}
|
||||||
if l.TLSKeyFile != "" || l.TLSCertFile != "" {
|
if l.TLSKeyFile != "" || l.TLSCertFile != "" {
|
||||||
appServer.TLS = &TLS{
|
appServer.TLS = &TLS{
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,12 @@ type Server struct {
|
||||||
// TLS contains the information for loading the certificate and key for the
|
// TLS contains the information for loading the certificate and key for the
|
||||||
// secure traffic and further configuration for the TLS server.
|
// secure traffic and further configuration for the TLS server.
|
||||||
TLS *TLS `yaml:"tls,omitempty"`
|
TLS *TLS `yaml:"tls,omitempty"`
|
||||||
|
|
||||||
|
// HTTP2 enables HTTP/2 support on the server.
|
||||||
|
// For the insecure (HTTP) server, this enables h2c (HTTP/2 Cleartext) support,
|
||||||
|
// which is required for gRPC proxying without TLS.
|
||||||
|
// For the secure (HTTPS) server, this adds "h2" to the TLS ALPN negotiation.
|
||||||
|
HTTP2 bool `yaml:"http2,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLS contains the information for loading a TLS certificate and key
|
// TLS contains the information for loading a TLS certificate and key
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"golang.org/x/net/http2/h2c"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||||
|
|
@ -39,6 +41,11 @@ type Opts struct {
|
||||||
// TLS is the TLS configuration for the server.
|
// TLS is the TLS configuration for the server.
|
||||||
TLS *options.TLS
|
TLS *options.TLS
|
||||||
|
|
||||||
|
// HTTP2 enables HTTP/2 support.
|
||||||
|
// For insecure (HTTP) servers, this enables h2c (HTTP/2 Cleartext).
|
||||||
|
// For secure (HTTPS) servers, this adds "h2" to TLS ALPN negotiation.
|
||||||
|
HTTP2 bool
|
||||||
|
|
||||||
// Let testing infrastructure circumvent parsing file descriptors
|
// Let testing infrastructure circumvent parsing file descriptors
|
||||||
fdFiles []*os.File
|
fdFiles []*os.File
|
||||||
}
|
}
|
||||||
|
|
@ -47,6 +54,7 @@ type Opts struct {
|
||||||
func NewServer(opts Opts) (Server, error) {
|
func NewServer(opts Opts) (Server, error) {
|
||||||
s := &server{
|
s := &server{
|
||||||
handler: opts.Handler,
|
handler: opts.Handler,
|
||||||
|
http2: opts.HTTP2,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opts.fdFiles) > 0 {
|
if len(opts.fdFiles) > 0 {
|
||||||
|
|
@ -70,6 +78,9 @@ type server struct {
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
tlsListener net.Listener
|
tlsListener net.Listener
|
||||||
|
|
||||||
|
// http2 enables HTTP/2 support
|
||||||
|
http2 bool
|
||||||
|
|
||||||
// ensure activation.Files are called once
|
// ensure activation.Files are called once
|
||||||
fdFiles []*os.File
|
fdFiles []*os.File
|
||||||
}
|
}
|
||||||
|
|
@ -182,10 +193,15 @@ func (s *server) setupTLSListener(opts Opts) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextProtos := []string{"http/1.1"}
|
||||||
|
if opts.HTTP2 {
|
||||||
|
nextProtos = []string{"h2", "http/1.1"}
|
||||||
|
}
|
||||||
|
|
||||||
config := &tls.Config{
|
config := &tls.Config{
|
||||||
MinVersion: tls.VersionTLS12, // default, override below
|
MinVersion: tls.VersionTLS12, // default, override below
|
||||||
MaxVersion: tls.VersionTLS13,
|
MaxVersion: tls.VersionTLS13,
|
||||||
NextProtos: []string{"http/1.1"},
|
NextProtos: nextProtos,
|
||||||
}
|
}
|
||||||
if opts.TLS == nil {
|
if opts.TLS == nil {
|
||||||
return errors.New("no TLS config provided")
|
return errors.New("no TLS config provided")
|
||||||
|
|
@ -234,7 +250,7 @@ func (s *server) Start(ctx context.Context) error {
|
||||||
|
|
||||||
if s.listener != nil {
|
if s.listener != nil {
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
if err := s.startServer(groupCtx, s.listener); err != nil {
|
if err := s.startServer(groupCtx, s.listener, s.http2, false); err != nil {
|
||||||
return fmt.Errorf("error starting insecure server: %v", err)
|
return fmt.Errorf("error starting insecure server: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -243,7 +259,7 @@ func (s *server) Start(ctx context.Context) error {
|
||||||
|
|
||||||
if s.tlsListener != nil {
|
if s.tlsListener != nil {
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
if err := s.startServer(groupCtx, s.tlsListener); err != nil {
|
if err := s.startServer(groupCtx, s.tlsListener, s.http2, true); err != nil {
|
||||||
return fmt.Errorf("error starting secure server: %v", err)
|
return fmt.Errorf("error starting secure server: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -256,8 +272,27 @@ func (s *server) Start(ctx context.Context) error {
|
||||||
// startServer creates and starts a new server with the given listener.
|
// startServer creates and starts a new server with the given listener.
|
||||||
// When the given context is cancelled the server will be shutdown.
|
// When the given context is cancelled the server will be shutdown.
|
||||||
// If any errors occur, only the first error will be returned.
|
// If any errors occur, only the first error will be returned.
|
||||||
func (s *server) startServer(ctx context.Context, listener net.Listener) error {
|
// If enableHTTP2 is true and isTLS is false, the handler is wrapped
|
||||||
srv := &http.Server{Handler: s.handler, ReadHeaderTimeout: time.Minute}
|
// with h2c to support HTTP/2 Cleartext (required for gRPC without TLS).
|
||||||
|
// If enableHTTP2 is true and isTLS is true, HTTP/2 is configured on the server.
|
||||||
|
func (s *server) startServer(ctx context.Context, listener net.Listener, enableHTTP2 bool, isTLS bool) error {
|
||||||
|
handler := s.handler
|
||||||
|
|
||||||
|
if enableHTTP2 && !isTLS {
|
||||||
|
// Wrap handler with h2c for HTTP/2 Cleartext support
|
||||||
|
h2s := &http2.Server{}
|
||||||
|
handler = h2c.NewHandler(handler, h2s)
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := &http.Server{Handler: handler, ReadHeaderTimeout: time.Minute}
|
||||||
|
|
||||||
|
if enableHTTP2 && isTLS {
|
||||||
|
// Configure HTTP/2 for TLS server
|
||||||
|
if err := http2.ConfigureServer(srv, &http2.Server{}); err != nil {
|
||||||
|
return fmt.Errorf("error configuring HTTP/2: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
g, groupCtx := errgroup.WithContext(ctx)
|
g, groupCtx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package proxyhttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -9,6 +10,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
|
||||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
|
@ -1329,6 +1332,136 @@ var _ = Describe("Server", func() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Context("HTTP/2 support", func() {
|
||||||
|
var srv Server
|
||||||
|
var ctx context.Context
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
ctx, cancel = context.WithCancel(context.Background())
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
cancel()
|
||||||
|
Eventually(Goroutines).ShouldNot(HaveLeaked())
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with h2c (HTTP/2 Cleartext) on an insecure server", func() {
|
||||||
|
var listenAddr string
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
var err error
|
||||||
|
srv, err = NewServer(Opts{
|
||||||
|
Handler: handler,
|
||||||
|
BindAddress: "127.0.0.1:0",
|
||||||
|
HTTP2: true,
|
||||||
|
})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
s, ok := srv.(*server)
|
||||||
|
Expect(ok).To(BeTrue())
|
||||||
|
Expect(s.http2).To(BeTrue())
|
||||||
|
|
||||||
|
listenAddr = s.listener.Addr().String()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Serves HTTP/2 cleartext requests", func() {
|
||||||
|
go func() {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
Expect(srv.Start(ctx)).To(Succeed())
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Use an HTTP/2 cleartext client
|
||||||
|
h2cTransport := &http2.Transport{
|
||||||
|
AllowHTTP: true,
|
||||||
|
DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) {
|
||||||
|
return net.Dial(network, addr)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
h2cClient := &http.Client{
|
||||||
|
Transport: h2cTransport,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h2cClient.Get(fmt.Sprintf("http://%s/", listenAddr))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(resp.StatusCode).To(Equal(http.StatusOK))
|
||||||
|
Expect(resp.ProtoMajor).To(Equal(2))
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(string(body)).To(Equal(hello))
|
||||||
|
|
||||||
|
// Close idle connections to prevent goroutine leaks
|
||||||
|
h2cTransport.CloseIdleConnections()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Still serves HTTP/1.1 requests", func() {
|
||||||
|
go func() {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
Expect(srv.Start(ctx)).To(Succeed())
|
||||||
|
}()
|
||||||
|
|
||||||
|
resp, err := httpGet(ctx, fmt.Sprintf("http://%s/", listenAddr))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(resp.StatusCode).To(Equal(http.StatusOK))
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(string(body)).To(Equal(hello))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with HTTP/2 on a TLS server", func() {
|
||||||
|
var secureListenAddr string
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
var err error
|
||||||
|
srv, err = NewServer(Opts{
|
||||||
|
Handler: handler,
|
||||||
|
SecureBindAddress: "127.0.0.1:0",
|
||||||
|
TLS: &options.TLS{
|
||||||
|
Key: &ipv4KeyDataSource,
|
||||||
|
Cert: &ipv4CertDataSource,
|
||||||
|
},
|
||||||
|
HTTP2: true,
|
||||||
|
})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
s, ok := srv.(*server)
|
||||||
|
Expect(ok).To(BeTrue())
|
||||||
|
|
||||||
|
secureListenAddr = s.tlsListener.Addr().String()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Negotiates HTTP/2 via ALPN", func() {
|
||||||
|
go func() {
|
||||||
|
defer GinkgoRecover()
|
||||||
|
Expect(srv.Start(ctx)).To(Succeed())
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Use an HTTP/2 TLS client
|
||||||
|
h2Transport := transport.Clone()
|
||||||
|
h2Transport.ForceAttemptHTTP2 = true
|
||||||
|
|
||||||
|
h2Client := &http.Client{Transport: h2Transport}
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://%s/", secureListenAddr), nil)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
resp, err := h2Client.Do(req)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(resp.StatusCode).To(Equal(http.StatusOK))
|
||||||
|
Expect(resp.ProtoMajor).To(Equal(2))
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(string(body)).To(Equal(hello))
|
||||||
|
|
||||||
|
// Close idle connections to prevent goroutine leaks
|
||||||
|
h2Transport.CloseIdleConnections()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Context("getNetworkScheme", func() {
|
Context("getNetworkScheme", func() {
|
||||||
DescribeTable("should return the scheme", func(in, expected string) {
|
DescribeTable("should return the scheme", func(in, expected string) {
|
||||||
Expect(getNetworkScheme(in)).To(Equal(expected))
|
Expect(getNetworkScheme(in)).To(Equal(expected))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue