Merge 9f42454aae into 65037b086c
This commit is contained in:
commit
a2c501cf34
|
|
@ -289,6 +289,7 @@ func (p *OAuthProxy) setupServer(opts *options.Options) error {
|
|||
BindAddress: opts.Server.BindAddress,
|
||||
SecureBindAddress: opts.Server.SecureBindAddress,
|
||||
TLS: opts.Server.TLS,
|
||||
HTTP2: opts.Server.HTTP2,
|
||||
}
|
||||
|
||||
// Option: AllowQuerySemicolons
|
||||
|
|
@ -306,6 +307,7 @@ func (p *OAuthProxy) setupServer(opts *options.Options) error {
|
|||
BindAddress: opts.MetricsServer.BindAddress,
|
||||
SecureBindAddress: opts.MetricsServer.SecureBindAddress,
|
||||
TLS: opts.MetricsServer.TLS,
|
||||
HTTP2: opts.MetricsServer.HTTP2,
|
||||
})
|
||||
if err != nil {
|
||||
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"`
|
||||
TLSMinVersion string `flag:"tls-min-version" cfg:"tls_min_version"`
|
||||
TLSCipherSuites []string `flag:"tls-cipher-suite" cfg:"tls_cipher_suites"`
|
||||
ForceHTTP2 bool `flag:"force-http2" cfg:"force_http2"`
|
||||
}
|
||||
|
||||
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-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.Bool("force-http2", false, "enable HTTP/2 support (h2c for HTTP, h2 for HTTPS); required for gRPC proxying")
|
||||
|
||||
return flagSet
|
||||
}
|
||||
|
|
@ -653,6 +655,7 @@ func (l LegacyServer) convert() (Server, Server) {
|
|||
appServer := Server{
|
||||
BindAddress: l.HTTPAddress,
|
||||
SecureBindAddress: l.HTTPSAddress,
|
||||
HTTP2: l.ForceHTTP2,
|
||||
}
|
||||
if l.TLSKeyFile != "" || l.TLSCertFile != "" {
|
||||
appServer.TLS = &TLS{
|
||||
|
|
|
|||
|
|
@ -22,6 +22,12 @@ type Server struct {
|
|||
// TLS contains the information for loading the certificate and key for the
|
||||
// secure traffic and further configuration for the TLS server.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"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 *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
|
||||
fdFiles []*os.File
|
||||
}
|
||||
|
|
@ -47,6 +54,7 @@ type Opts struct {
|
|||
func NewServer(opts Opts) (Server, error) {
|
||||
s := &server{
|
||||
handler: opts.Handler,
|
||||
http2: opts.HTTP2,
|
||||
}
|
||||
|
||||
if len(opts.fdFiles) > 0 {
|
||||
|
|
@ -70,6 +78,9 @@ type server struct {
|
|||
listener net.Listener
|
||||
tlsListener net.Listener
|
||||
|
||||
// http2 enables HTTP/2 support
|
||||
http2 bool
|
||||
|
||||
// ensure activation.Files are called once
|
||||
fdFiles []*os.File
|
||||
}
|
||||
|
|
@ -182,10 +193,15 @@ func (s *server) setupTLSListener(opts Opts) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
nextProtos := []string{"http/1.1"}
|
||||
if opts.HTTP2 {
|
||||
nextProtos = []string{"h2", "http/1.1"}
|
||||
}
|
||||
|
||||
config := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12, // default, override below
|
||||
MaxVersion: tls.VersionTLS13,
|
||||
NextProtos: []string{"http/1.1"},
|
||||
NextProtos: nextProtos,
|
||||
}
|
||||
if opts.TLS == nil {
|
||||
return errors.New("no TLS config provided")
|
||||
|
|
@ -234,7 +250,7 @@ func (s *server) Start(ctx context.Context) error {
|
|||
|
||||
if s.listener != nil {
|
||||
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 nil
|
||||
|
|
@ -243,7 +259,7 @@ func (s *server) Start(ctx context.Context) error {
|
|||
|
||||
if s.tlsListener != nil {
|
||||
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 nil
|
||||
|
|
@ -256,8 +272,27 @@ func (s *server) Start(ctx context.Context) error {
|
|||
// startServer creates and starts a new server with the given listener.
|
||||
// When the given context is cancelled the server will be shutdown.
|
||||
// If any errors occur, only the first error will be returned.
|
||||
func (s *server) startServer(ctx context.Context, listener net.Listener) error {
|
||||
srv := &http.Server{Handler: s.handler, ReadHeaderTimeout: time.Minute}
|
||||
// If enableHTTP2 is true and isTLS is false, the handler is wrapped
|
||||
// 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.Go(func() error {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package proxyhttp
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -9,6 +10,8 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "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() {
|
||||
DescribeTable("should return the scheme", func(in, expected string) {
|
||||
Expect(getNetworkScheme(in)).To(Equal(expected))
|
||||
|
|
|
|||
Loading…
Reference in New Issue