git.iko.soy/typeable/oauth2-proxy
on master help sign in
Integrate new server implementation into main OAuth2 Proxy
commit8d2fc409d8490bc563375f2ba8c9bc66f9024f5b authorJoel Speed <joel.speed@hotmail.co.uk> dateSun 14 Feb 2021, 17:08 (6 years ago) parent2c54ee7
6 files changed+70−235
http.go−136 http_test.go−39 main.go+3−53 main_test.go+4−3 oauthproxy.go+62−4 oauthproxy_test.go+1
http.godeleted−136
@@ -1,136 +0,0 @@
1−package main
2−
3−import (
4− "context"
5− "crypto/tls"
6− "errors"
7− "net"
8− "net/http"
9− "strings"
10− "time"
11−
12− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
13− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
14−)
15−
16−// Server represents an HTTP server
17−type Server struct {
18− Handler http.Handler
19− Opts *options.Options
20− stop chan struct{} // channel for waiting shutdown
21−}
22−
23−// ListenAndServe will serve traffic on HTTP or HTTPS depending on TLS options
24−func (s *Server) ListenAndServe() {
25− if s.Opts.TLSKeyFile != "" || s.Opts.TLSCertFile != "" {
26− s.ServeHTTPS()
27− } else {
28− s.ServeHTTP()
29− }
30−}
31−
32−// ServeHTTP constructs a net.Listener and starts handling HTTP requests
33−func (s *Server) ServeHTTP() {
34− HTTPAddress := s.Opts.HTTPAddress
35− var scheme string
36−
37− i := strings.Index(HTTPAddress, "://")
38− if i > -1 {
39− scheme = HTTPAddress[0:i]
40− }
41−
42− var networkType string
43− switch scheme {
44− case "", "http":
45− networkType = "tcp"
46− default:
47− networkType = scheme
48− }
49−
50− slice := strings.SplitN(HTTPAddress, "//", 2)
51− listenAddr := slice[len(slice)-1]
52−
53− listener, err := net.Listen(networkType, listenAddr)
54− if err != nil {
55− logger.Fatalf("FATAL: listen (%s, %s) failed - %s", networkType, listenAddr, err)
56− }
57− logger.Printf("HTTP: listening on %s", listenAddr)
58− s.serve(listener)
59− logger.Printf("HTTP: closing %s", listener.Addr())
60−}
61−
62−// ServeHTTPS constructs a net.Listener and starts handling HTTPS requests
63−func (s *Server) ServeHTTPS() {
64− addr := s.Opts.HTTPSAddress
65− config := &tls.Config{
66− MinVersion: tls.VersionTLS12,
67− MaxVersion: tls.VersionTLS13,
68− }
69− if config.NextProtos == nil {
70− config.NextProtos = []string{"http/1.1"}
71− }
72−
73− var err error
74− config.Certificates = make([]tls.Certificate, 1)
75− config.Certificates[0], err = tls.LoadX509KeyPair(s.Opts.TLSCertFile, s.Opts.TLSKeyFile)
76− if err != nil {
77− logger.Fatalf("FATAL: loading tls config (%s, %s) failed - %s", s.Opts.TLSCertFile, s.Opts.TLSKeyFile, err)
78− }
79−
80− ln, err := net.Listen("tcp", addr)
81− if err != nil {
82− logger.Fatalf("FATAL: listen (%s) failed - %s", addr, err)
83− }
84− logger.Printf("HTTPS: listening on %s", ln.Addr())
85−
86− tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config)
87− s.serve(tlsListener)
88− logger.Printf("HTTPS: closing %s", tlsListener.Addr())
89−}
90−
91−func (s *Server) serve(listener net.Listener) {
92− srv := &http.Server{Handler: s.Handler}
93−
94− // See https://golang.org/pkg/net/http/#Server.Shutdown
95− idleConnsClosed := make(chan struct{})
96− go func() {
97− <-s.stop // wait notification for stopping server
98−
99− // We received an interrupt signal, shut down.
100− if err := srv.Shutdown(context.Background()); err != nil {
101− // Error from closing listeners, or context timeout:
102− logger.Printf("HTTP server Shutdown: %v", err)
103− }
104− close(idleConnsClosed)
105− }()
106−
107− err := srv.Serve(listener)
108− if err != nil && !errors.Is(err, http.ErrServerClosed) {
109− logger.Errorf("ERROR: http.Serve() - %s", err)
110− }
111− <-idleConnsClosed
112−}
113−
114−// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
115−// connections. It's used by ListenAndServe and ListenAndServeTLS so
116−// dead TCP connections (e.g. closing laptop mid-download) eventually
117−// go away.
118−type tcpKeepAliveListener struct {
119− *net.TCPListener
120−}
121−
122−func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
123− tc, err := ln.AcceptTCP()
124− if err != nil {
125− return nil, err
126− }
127− err = tc.SetKeepAlive(true)
128− if err != nil {
129− logger.Printf("Error setting Keep-Alive: %v", err)
130− }
131− err = tc.SetKeepAlivePeriod(3 * time.Minute)
132− if err != nil {
133− logger.Printf("Error setting Keep-Alive period: %v", err)
134− }
135− return tc, nil
136−}
http_test.godeleted−39
@@ -1,39 +0,0 @@
1−package main
2−
3−import (
4− "net/http"
5− "sync"
6− "testing"
7− "time"
8−
9− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
10− "github.com/stretchr/testify/assert"
11−)
12−
13−func TestGracefulShutdown(t *testing.T) {
14− opts := options.NewOptions()
15− stop := make(chan struct{}, 1)
16− srv := Server{Handler: http.DefaultServeMux, Opts: opts, stop: stop}
17− var wg sync.WaitGroup
18− wg.Add(1)
19− go func() {
20− defer wg.Done()
21− srv.ServeHTTP()
22− }()
23−
24− stop <- struct{}{} // emulate catching signals
25−
26− // An idiomatic for sync.WaitGroup with timeout
27− c := make(chan struct{})
28− go func() {
29− defer close(c)
30− wg.Wait()
31− }()
32− select {
33− case <-c:
34− case <-time.After(1 * time.Second):
35− t.Fatal("Server should return gracefully but timeout has occurred")
36− }
37−
38− assert.Len(t, stop, 0) // check if stop chan is empty
39−}
main.go+3−53
@@ -1,20 +1,15 @@
11package main
22
33import (
4− "context"
54 "fmt"
65 "math/rand"
7− "net/http"
86 "os"
9− "os/signal"
107 "runtime"
11− "syscall"
128 "time"
139
1410 "github.com/ghodss/yaml"
1511 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
1612 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
17− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/middleware"
1813 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
1914 "github.com/spf13/pflag"
2015)
@@ -67,54 +62,9 @@ func main() {
6762
6863 rand.Seed(time.Now().UnixNano())
6964
70− oauthProxyStop := make(chan struct{}, 1)
71− metricsStop := startMetricsServer(opts.MetricsAddress, oauthProxyStop)
72−
73− s := &Server{
74− Handler: oauthproxy,
75− Opts: opts,
76− stop: oauthProxyStop,
77− }
78− // Observe signals in background goroutine.
79− go func() {
80− sigint := make(chan os.Signal, 1)
81− signal.Notify(sigint, os.Interrupt, syscall.SIGTERM)
82− <-sigint
83− s.stop <- struct{}{} // notify having caught signal stop oauthproxy
84− close(metricsStop) // and the metrics endpoint
85− }()
86− s.ListenAndServe()
87−}
88−
89−// startMetricsServer will start the metrics server on the specified address.
90−// It always return a channel to signal stop even when it does not run.
91−func startMetricsServer(address string, oauthProxyStop chan struct{}) chan struct{} {
92− stop := make(chan struct{}, 1)
93−
94− // Attempt to setup the metrics endpoint if we have an address
95− if address != "" {
96− s := &http.Server{Addr: address, Handler: middleware.DefaultMetricsHandler}
97− go func() {
98− // ListenAndServe always returns a non-nil error. After Shutdown or
99− // Close, the returned error is ErrServerClosed
100− if err := s.ListenAndServe(); err != http.ErrServerClosed {
101− logger.Println(err)
102− // Stop the metrics shutdown go routine
103− close(stop)
104− // Stop the oauthproxy server, we have encounter an unexpected error
105− close(oauthProxyStop)
106− }
107− }()
108−
109− go func() {
110− <-stop
111− if err := s.Shutdown(context.Background()); err != nil {
112− logger.Print(err)
113− }
114− }()
115− }
116−
117− return stop
65+ if err := oauthproxy.Start(); err != nil {
66+ logger.Fatalf("ERROR: Failed to start OAuth2 Proxy: %v", err)
67+ }
11868}
11969
12070// loadConfiguration will load in the user's configuration.
main_test.go+4−3
@@ -15,6 +15,7 @@ import (
1515
1616var _ = Describe("Configuration Loading Suite", func() {
1717 const testLegacyConfig = `
18+http_address="127.0.0.1:4180"
1819upstreams="http://httpbin"
1920set_basic_auth="true"
2021basic_auth_password="super-secret-password"
@@ -54,10 +55,11 @@ injectResponseHeaders:
5455 prefix: "Basic "
5556 basicAuthPassword:
5657 value: c3VwZXItc2VjcmV0LXBhc3N3b3Jk
58+server:
59+ bindAddress: "127.0.0.1:4180"
5760`
5861
5962 const testCoreConfig = `
60−http_address="0.0.0.0:4180"
6163cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
6264provider="oidc"
6365email_domains="example.com"
@@ -82,7 +84,6 @@ redirect_url="http://localhost:4180/oauth2/callback"
8284 opts, err := options.NewLegacyOptions().ToOptions()
8385 Expect(err).ToNot(HaveOccurred())
8486
85− opts.HTTPAddress = "0.0.0.0:4180"
8687 opts.Cookie.Secret = "OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w="
8788 opts.ProviderType = "oidc"
8889 opts.EmailDomains = []string{"example.com"}
@@ -203,7 +204,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
203204 configContent: testCoreConfig,
204205 alphaConfigContent: testAlphaConfig + ":",
205206 expectedOptions: func() *options.Options { return nil },
206− expectedErr: errors.New("failed to load alpha options: error unmarshalling config: error converting YAML to JSON: yaml: line 34: did not find expected key"),
207+ expectedErr: errors.New("failed to load alpha options: error unmarshalling config: error converting YAML to JSON: yaml: line 36: did not find expected key"),
207208 }),
208209 Entry("with alpha configuration and bad core configuration", loadConfigurationTableInput{
209210 configContent: testCoreConfig + "unknown_field=\"something\"",
oauthproxy.go+62−4
@@ -8,8 +8,11 @@ import (
88 "net"
99 "net/http"
1010 "net/url"
11+ "os"
12+ "os/signal"
1113 "regexp"
1214 "strings"
15+ "syscall"
1316 "time"
1417
1518 "github.com/justinas/alice"
@@ -21,6 +24,7 @@ import (
2124 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/basic"
2225 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
2326 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
27+ proxyhttp "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/http"
2428 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
2529 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
2630 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/middleware"
@@ -102,6 +106,7 @@ type OAuthProxy struct {
102106 headersChain alice.Chain
103107 preAuthChain alice.Chain
104108 pageWriter pagewriter.Writer
109+ server proxyhttp.Server
105110}
106111
107112// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
@@ -184,7 +189,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
184189 return nil, fmt.Errorf("could not build headers chain: %v", err)
185190 }
186191
187− return &OAuthProxy{
192+ p := &OAuthProxy{
188193 CookieName: opts.Cookie.Name,
189194 CSRFCookieName: fmt.Sprintf("%v_%v", opts.Cookie.Name, "csrf"),
190195 CookieSeed: opts.Cookie.Secret,
@@ -223,7 +228,60 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
223228 headersChain: headersChain,
224229 preAuthChain: preAuthChain,
225230 pageWriter: pageWriter,
226− }, nil
231+ }
232+
233+ if err := p.setupServer(opts); err != nil {
234+ return nil, fmt.Errorf("error setting up server: %v", err)
235+ }
236+
237+ return p, nil
238+}
239+
240+func (p *OAuthProxy) Start() error {
241+ if p.server == nil {
242+ // We have to call setupServer before Start is called.
243+ // If this doesn't happen it's a programming error.
244+ panic("server has not been initialised")
245+ }
246+
247+ ctx, cancel := context.WithCancel(context.Background())
248+
249+ // Observe signals in background goroutine.
250+ go func() {
251+ sigint := make(chan os.Signal, 1)
252+ signal.Notify(sigint, os.Interrupt, syscall.SIGTERM)
253+ <-sigint
254+ cancel() // cancel the context
255+ }()
256+
257+ return p.server.Start(ctx)
258+}
259+
260+func (p *OAuthProxy) setupServer(opts *options.Options) error {
261+ serverOpts := proxyhttp.Opts{
262+ Handler: p,
263+ BindAddress: opts.Server.BindAddress,
264+ SecureBindAddress: opts.Server.SecureBindAddress,
265+ TLS: opts.Server.TLS,
266+ }
267+
268+ appServer, err := proxyhttp.NewServer(serverOpts)
269+ if err != nil {
270+ return fmt.Errorf("could not build app server: %v", err)
271+ }
272+
273+ metricsServer, err := proxyhttp.NewServer(proxyhttp.Opts{
274+ Handler: middleware.DefaultMetricsHandler,
275+ BindAddress: opts.MetricsServer.BindAddress,
276+ SecureBindAddress: opts.MetricsServer.BindAddress,
277+ TLS: opts.MetricsServer.TLS,
278+ })
279+ if err != nil {
280+ return fmt.Errorf("could not build metrics server: %v", err)
281+ }
282+
283+ p.server = proxyhttp.NewServerGroup(appServer, metricsServer)
284+ return nil
227285}
228286
229287// buildPreAuthChain constructs a chain that should process every request before
@@ -233,9 +291,9 @@ func buildPreAuthChain(opts *options.Options) (alice.Chain, error) {
233291 chain := alice.New(middleware.NewScope(opts.ReverseProxy))
234292
235293 if opts.ForceHTTPS {
236− _, httpsPort, err := net.SplitHostPort(opts.HTTPSAddress)
294+ _, httpsPort, err := net.SplitHostPort(opts.Server.SecureBindAddress)
237295 if err != nil {
238− return alice.Chain{}, fmt.Errorf("invalid HTTPS address %q: %v", opts.HTTPAddress, err)
296+ return alice.Chain{}, fmt.Errorf("invalid HTTPS address %q: %v", opts.Server.SecureBindAddress, err)
239297 }
240298 chain = chain.Append(middleware.NewRedirectToHTTPS(httpsPort))
241299 }
oauthproxy_test.go+1
@@ -2341,6 +2341,7 @@ func baseTestOptions() *options.Options {
23412341 },
23422342 },
23432343 }
2344+
23442345 return opts
23452346}
23462347