git.iko.soy/typeable/oauth2-proxy
on 7b41c8e help sign in
308 lines 8.3 kB history raw
1package http
3import (
4 "context"
5 "crypto/tls"
6 "errors"
7 "fmt"
8 "net"
9 "net/http"
10 "os"
11 "strings"
12 "time"
14 "golang.org/x/sync/errgroup"
16 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
17 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options/util"
18 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
21// Server represents an HTTP or HTTPS server.
22type Server interface {
23 // Start blocks and runs the server.
24 Start(ctx context.Context) error
27// Opts contains the information required to set up the server.
28type Opts struct {
29 // Handler is the http.Handler to be used to serve http pages by the server.
30 Handler http.Handler
32 // BindAddress is the address the HTTP server should listen on.
33 BindAddress string
35 // SecureBindAddress is the address the HTTPS server should listen on.
36 SecureBindAddress string
38 // TLS is the TLS configuration for the server.
39 TLS *options.TLS
41 // Let testing infrastructure circumvent parsing file descriptors
42 fdFiles []*os.File
45// NewServer creates a new Server from the options given.
46func NewServer(opts Opts) (Server, error) {
47 s := &server{
48 handler: opts.Handler,
49 }
51 if len(opts.fdFiles) > 0 {
52 s.fdFiles = opts.fdFiles
53 }
55 if err := s.setupListener(opts); err != nil {
56 return nil, fmt.Errorf("error setting up listener: %v", err)
57 }
58 if err := s.setupTLSListener(opts); err != nil {
59 return nil, fmt.Errorf("error setting up TLS listener: %v", err)
60 }
62 return s, nil
65// server is an implementation of the Server interface.
66type server struct {
67 handler http.Handler
69 listener net.Listener
70 tlsListener net.Listener
72 // ensure activation.Files are called once
73 fdFiles []*os.File
76// setupListener sets the server listener if the HTTP server is enabled.
77// The HTTP server can be disabled by setting the BindAddress to "-" or by
78// leaving it empty.
79func (s *server) setupListener(opts Opts) error {
80 if opts.BindAddress == "" || opts.BindAddress == "-" {
81 // No HTTP listener required
82 return nil
83 }
85 // Use fd: as a prefix for systemd socket activation, it's generic
86 // enough and short.
87 // The most common usage would be --http-address fd:3.
88 // This causes oauth2-proxy to just assume that the third fd passed
89 // to the program is indeed a net.Listener and starts using it
90 // without setting up a new listener.
91 if strings.HasPrefix(strings.ToLower(opts.BindAddress), "fd:") {
92 return s.checkSystemdSocketSupport(opts)
93 }
95 networkType := getNetworkScheme(opts.BindAddress)
96 listenAddr := getListenAddress(opts.BindAddress)
98 listener, err := net.Listen(networkType, listenAddr)
99 if err != nil {
100 return fmt.Errorf("listen (%s, %s) failed: %w", networkType, listenAddr, err)
101 }
102 s.listener = listener
104 return nil
107func parseCipherSuites(names []string) ([]uint16, error) {
108 cipherNameMap := make(map[string]uint16)
110 for _, cipherSuite := range tls.CipherSuites() {
111 cipherNameMap[cipherSuite.Name] = cipherSuite.ID
112 }
113 for _, cipherSuite := range tls.InsecureCipherSuites() {
114 cipherNameMap[cipherSuite.Name] = cipherSuite.ID
115 }
117 result := make([]uint16, len(names))
118 for i, name := range names {
119 id, present := cipherNameMap[name]
120 if !present {
121 return nil, fmt.Errorf("unknown TLS cipher suite name specified %q", name)
122 }
123 result[i] = id
124 }
125 return result, nil
128// setupTLSListener sets the server TLS listener if the HTTPS server is enabled.
129// The HTTPS server can be disabled by setting the SecureBindAddress to "-" or by
130// leaving it empty.
131func (s *server) setupTLSListener(opts Opts) error {
132 if opts.SecureBindAddress == "" || opts.SecureBindAddress == "-" {
133 // No HTTPS listener required
134 return nil
135 }
137 config := &tls.Config{
138 MinVersion: tls.VersionTLS12, // default, override below
139 MaxVersion: tls.VersionTLS13,
140 NextProtos: []string{"http/1.1"},
141 }
142 if opts.TLS == nil {
143 return errors.New("no TLS config provided")
144 }
145 cert, err := getCertificate(opts.TLS)
146 if err != nil {
147 return fmt.Errorf("could not load certificate: %v", err)
148 }
149 config.Certificates = []tls.Certificate{cert}
151 if len(opts.TLS.CipherSuites) > 0 {
152 cipherSuites, err := parseCipherSuites(opts.TLS.CipherSuites)
153 if err != nil {
154 return fmt.Errorf("could not parse cipher suites: %v", err)
155 }
156 config.CipherSuites = cipherSuites
157 }
159 if len(opts.TLS.MinVersion) > 0 {
160 switch opts.TLS.MinVersion {
161 case "TLS1.2":
162 config.MinVersion = tls.VersionTLS12
163 case "TLS1.3":
164 config.MinVersion = tls.VersionTLS13
165 default:
166 return errors.New("unknown TLS MinVersion config provided")
167 }
168 }
170 listenAddr := getListenAddress(opts.SecureBindAddress)
172 listener, err := net.Listen("tcp", listenAddr)
173 if err != nil {
174 return fmt.Errorf("listen (%s) failed: %v", listenAddr, err)
175 }
177 s.tlsListener = tls.NewListener(tcpKeepAliveListener{listener.(*net.TCPListener)}, config)
178 return nil
181// Start starts the HTTP and HTTPS server if applicable.
182// It will block until the context is cancelled.
183// If any errors occur, only the first error will be returned.
184func (s *server) Start(ctx context.Context) error {
185 g, groupCtx := errgroup.WithContext(ctx)
187 if s.listener != nil {
188 g.Go(func() error {
189 if err := s.startServer(groupCtx, s.listener); err != nil {
190 return fmt.Errorf("error starting insecure server: %v", err)
191 }
192 return nil
193 })
194 }
196 if s.tlsListener != nil {
197 g.Go(func() error {
198 if err := s.startServer(groupCtx, s.tlsListener); err != nil {
199 return fmt.Errorf("error starting secure server: %v", err)
200 }
201 return nil
202 })
203 }
205 return g.Wait()
208// startServer creates and starts a new server with the given listener.
209// When the given context is cancelled the server will be shutdown.
210// If any errors occur, only the first error will be returned.
211func (s *server) startServer(ctx context.Context, listener net.Listener) error {
212 srv := &http.Server{Handler: s.handler, ReadHeaderTimeout: time.Minute}
213 g, groupCtx := errgroup.WithContext(ctx)
215 g.Go(func() error {
216 <-groupCtx.Done()
218 if err := srv.Shutdown(context.Background()); err != nil {
219 return fmt.Errorf("error shutting down server: %v", err)
220 }
221 return nil
222 })
224 g.Go(func() error {
225 if err := srv.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
226 return fmt.Errorf("could not start server: %v", err)
227 }
228 return nil
229 })
231 return g.Wait()
234// getNetworkScheme gets the scheme for the HTTP server.
235func getNetworkScheme(addr string) string {
236 var scheme string
237 i := strings.Index(addr, "://")
238 if i > -1 {
239 scheme = addr[0:i]
240 }
242 switch scheme {
243 case "", "http":
244 return "tcp"
245 default:
246 return scheme
247 }
250// getListenAddress gets the address for the HTTP server.
251func getListenAddress(addr string) string {
252 slice := strings.SplitN(addr, "//", 2)
253 return slice[len(slice)-1]
256// getCertificate loads the certificate data from the TLS config.
257func getCertificate(opts *options.TLS) (tls.Certificate, error) {
258 keyData, err := getSecretValue(opts.Key)
259 if err != nil {
260 return tls.Certificate{}, fmt.Errorf("could not load key data: %v", err)
261 }
263 certData, err := getSecretValue(opts.Cert)
264 if err != nil {
265 return tls.Certificate{}, fmt.Errorf("could not load cert data: %v", err)
266 }
268 cert, err := tls.X509KeyPair(certData, keyData)
269 if err != nil {
270 return tls.Certificate{}, fmt.Errorf("could not parse certificate data: %v", err)
271 }
273 return cert, nil
276// getSecretValue wraps util.GetSecretValue so that we can return an error if no
277// source is provided.
278func getSecretValue(src *options.SecretSource) ([]byte, error) {
279 if src == nil {
280 return nil, errors.New("no configuration provided")
281 }
282 return util.GetSecretValue(src)
285// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
286// connections. It's used by so that dead TCP connections (e.g. closing laptop
287// mid-download) eventually go away.
288type tcpKeepAliveListener struct {
289 *net.TCPListener
292// Accept implements the TCPListener interface.
293// It sets the keep alive period to 3 minutes for each connection.
294func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
295 tc, err := ln.AcceptTCP()
296 if err != nil {
297 return nil, err
298 }
299 err = tc.SetKeepAlive(true)
300 if err != nil {
301 logger.Errorf("Error setting Keep-Alive: %v", err)
302 }
303 err = tc.SetKeepAlivePeriod(3 * time.Minute)
304 if err != nil {
305 logger.Printf("Error setting Keep-Alive period: %v", err)
306 }
307 return tc, nil