git.iko.soy/typeable/oauth2-proxy
on 2bb7ea5 help sign in
285 lines 7.7 kB history raw
1package http
3import (
4 "context"
5 "crypto/tls"
6 "errors"
7 "fmt"
8 "net"
9 "net/http"
10 "strings"
11 "time"
13 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
14 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options/util"
15 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
16 "golang.org/x/sync/errgroup"
19// Server represents an HTTP or HTTPS server.
20type Server interface {
21 // Start blocks and runs the server.
22 Start(ctx context.Context) error
25// Opts contains the information required to set up the server.
26type Opts struct {
27 // Handler is the http.Handler to be used to serve http pages by the server.
28 Handler http.Handler
30 // BindAddress is the address the HTTP server should listen on.
31 BindAddress string
33 // SecureBindAddress is the address the HTTPS server should listen on.
34 SecureBindAddress string
36 // TLS is the TLS configuration for the server.
37 TLS *options.TLS
40// NewServer creates a new Server from the options given.
41func NewServer(opts Opts) (Server, error) {
42 s := &server{
43 handler: opts.Handler,
44 }
45 if err := s.setupListener(opts); err != nil {
46 return nil, fmt.Errorf("error setting up listener: %v", err)
47 }
48 if err := s.setupTLSListener(opts); err != nil {
49 return nil, fmt.Errorf("error setting up TLS listener: %v", err)
50 }
52 return s, nil
55// server is an implementation of the Server interface.
56type server struct {
57 handler http.Handler
59 listener net.Listener
60 tlsListener net.Listener
63// setupListener sets the server listener if the HTTP server is enabled.
64// The HTTP server can be disabled by setting the BindAddress to "-" or by
65// leaving it empty.
66func (s *server) setupListener(opts Opts) error {
67 if opts.BindAddress == "" || opts.BindAddress == "-" {
68 // No HTTP listener required
69 return nil
70 }
72 networkType := getNetworkScheme(opts.BindAddress)
73 listenAddr := getListenAddress(opts.BindAddress)
75 listener, err := net.Listen(networkType, listenAddr)
76 if err != nil {
77 return fmt.Errorf("listen (%s, %s) failed: %v", networkType, listenAddr, err)
78 }
79 s.listener = listener
81 return nil
84func parseCipherSuites(names []string) ([]uint16, error) {
85 cipherNameMap := make(map[string]uint16)
87 for _, cipherSuite := range tls.CipherSuites() {
88 cipherNameMap[cipherSuite.Name] = cipherSuite.ID
89 }
90 for _, cipherSuite := range tls.InsecureCipherSuites() {
91 cipherNameMap[cipherSuite.Name] = cipherSuite.ID
92 }
94 result := make([]uint16, len(names))
95 for i, name := range names {
96 id, present := cipherNameMap[name]
97 if !present {
98 return nil, fmt.Errorf("unknown TLS cipher suite name specified %q", name)
99 }
100 result[i] = id
101 }
102 return result, nil
105// setupTLSListener sets the server TLS listener if the HTTPS server is enabled.
106// The HTTPS server can be disabled by setting the SecureBindAddress to "-" or by
107// leaving it empty.
108func (s *server) setupTLSListener(opts Opts) error {
109 if opts.SecureBindAddress == "" || opts.SecureBindAddress == "-" {
110 // No HTTPS listener required
111 return nil
112 }
114 config := &tls.Config{
115 MinVersion: tls.VersionTLS12, // default, override below
116 MaxVersion: tls.VersionTLS13,
117 NextProtos: []string{"http/1.1"},
118 }
119 if opts.TLS == nil {
120 return errors.New("no TLS config provided")
121 }
122 cert, err := getCertificate(opts.TLS)
123 if err != nil {
124 return fmt.Errorf("could not load certificate: %v", err)
125 }
126 config.Certificates = []tls.Certificate{cert}
128 if len(opts.TLS.CipherSuites) > 0 {
129 cipherSuites, err := parseCipherSuites(opts.TLS.CipherSuites)
130 if err != nil {
131 return fmt.Errorf("could not parse cipher suites: %v", err)
132 }
133 config.CipherSuites = cipherSuites
134 }
136 if len(opts.TLS.MinVersion) > 0 {
137 switch opts.TLS.MinVersion {
138 case "TLS1.2":
139 config.MinVersion = tls.VersionTLS12
140 case "TLS1.3":
141 config.MinVersion = tls.VersionTLS13
142 default:
143 return errors.New("unknown TLS MinVersion config provided")
144 }
145 }
147 listenAddr := getListenAddress(opts.SecureBindAddress)
149 listener, err := net.Listen("tcp", listenAddr)
150 if err != nil {
151 return fmt.Errorf("listen (%s) failed: %v", listenAddr, err)
152 }
154 s.tlsListener = tls.NewListener(tcpKeepAliveListener{listener.(*net.TCPListener)}, config)
155 return nil
158// Start starts the HTTP and HTTPS server if applicable.
159// It will block until the context is cancelled.
160// If any errors occur, only the first error will be returned.
161func (s *server) Start(ctx context.Context) error {
162 g, groupCtx := errgroup.WithContext(ctx)
164 if s.listener != nil {
165 g.Go(func() error {
166 if err := s.startServer(groupCtx, s.listener); err != nil {
167 return fmt.Errorf("error starting insecure server: %v", err)
168 }
169 return nil
170 })
171 }
173 if s.tlsListener != nil {
174 g.Go(func() error {
175 if err := s.startServer(groupCtx, s.tlsListener); err != nil {
176 return fmt.Errorf("error starting secure server: %v", err)
177 }
178 return nil
179 })
180 }
182 return g.Wait()
185// startServer creates and starts a new server with the given listener.
186// When the given context is cancelled the server will be shutdown.
187// If any errors occur, only the first error will be returned.
188func (s *server) startServer(ctx context.Context, listener net.Listener) error {
189 srv := &http.Server{Handler: s.handler, ReadHeaderTimeout: time.Minute}
190 g, groupCtx := errgroup.WithContext(ctx)
192 g.Go(func() error {
193 <-groupCtx.Done()
195 if err := srv.Shutdown(context.Background()); err != nil {
196 return fmt.Errorf("error shutting down server: %v", err)
197 }
198 return nil
199 })
201 g.Go(func() error {
202 if err := srv.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
203 return fmt.Errorf("could not start server: %v", err)
204 }
205 return nil
206 })
208 return g.Wait()
211// getNetworkScheme gets the scheme for the HTTP server.
212func getNetworkScheme(addr string) string {
213 var scheme string
214 i := strings.Index(addr, "://")
215 if i > -1 {
216 scheme = addr[0:i]
217 }
219 switch scheme {
220 case "", "http":
221 return "tcp"
222 default:
223 return scheme
224 }
227// getListenAddress gets the address for the HTTP server.
228func getListenAddress(addr string) string {
229 slice := strings.SplitN(addr, "//", 2)
230 return slice[len(slice)-1]
233// getCertificate loads the certificate data from the TLS config.
234func getCertificate(opts *options.TLS) (tls.Certificate, error) {
235 keyData, err := getSecretValue(opts.Key)
236 if err != nil {
237 return tls.Certificate{}, fmt.Errorf("could not load key data: %v", err)
238 }
240 certData, err := getSecretValue(opts.Cert)
241 if err != nil {
242 return tls.Certificate{}, fmt.Errorf("could not load cert data: %v", err)
243 }
245 cert, err := tls.X509KeyPair(certData, keyData)
246 if err != nil {
247 return tls.Certificate{}, fmt.Errorf("could not parse certificate data: %v", err)
248 }
250 return cert, nil
253// getSecretValue wraps util.GetSecretValue so that we can return an error if no
254// source is provided.
255func getSecretValue(src *options.SecretSource) ([]byte, error) {
256 if src == nil {
257 return nil, errors.New("no configuration provided")
258 }
259 return util.GetSecretValue(src)
262// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
263// connections. It's used by so that dead TCP connections (e.g. closing laptop
264// mid-download) eventually go away.
265type tcpKeepAliveListener struct {
266 *net.TCPListener
269// Accept implements the TCPListener interface.
270// It sets the keep alive period to 3 minutes for each connection.
271func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
272 tc, err := ln.AcceptTCP()
273 if err != nil {
274 return nil, err
275 }
276 err = tc.SetKeepAlive(true)
277 if err != nil {
278 logger.Errorf("Error setting Keep-Alive: %v", err)
279 }
280 err = tc.SetKeepAlivePeriod(3 * time.Minute)
281 if err != nil {
282 logger.Printf("Error setting Keep-Alive period: %v", err)
283 }
284 return tc, nil