git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add new http server implementation
d8aca8aJoel Speed
@@ -30,6 +30,7 @@ require (
30 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
30 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
31 golang.org/x/net v0.0.0-20200707034311-ab3426394381
31 golang.org/x/net v0.0.0-20200707034311-ab3426394381
32 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
32 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
33+ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
33 google.golang.org/api v0.20.0
34 google.golang.org/api v0.20.0
34 gopkg.in/natefinch/lumberjack.v2 v2.0.0
35 gopkg.in/natefinch/lumberjack.v2 v2.0.0
35 gopkg.in/square/go-jose.v2 v2.4.1
36 gopkg.in/square/go-jose.v2 v2.4.1
go.sum+3
@@ -506,7 +506,10 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
506golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
506golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
507golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
507golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
508golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
508golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
509+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
509golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
510golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
511+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
512+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
510golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
513golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
511golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
514golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
512golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
515golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
pkg/http/http_suite_test.gonew file+88
@@ -0,0 +1,88 @@
1+package http
2+
3+import (
4+ "bytes"
5+ "crypto/rand"
6+ "crypto/rsa"
7+ "crypto/tls"
8+ "crypto/x509"
9+ "crypto/x509/pkix"
10+ "encoding/pem"
11+ "math/big"
12+ "net"
13+ "net/http"
14+ "testing"
15+ "time"
16+
17+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
18+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
19+ . "github.com/onsi/ginkgo"
20+ . "github.com/onsi/gomega"
21+)
22+
23+var certData []byte
24+var certDataSource, keyDataSource options.SecretSource
25+var client *http.Client
26+
27+func TestHTTPSuite(t *testing.T) {
28+ logger.SetOutput(GinkgoWriter)
29+ logger.SetErrOutput(GinkgoWriter)
30+
31+ RegisterFailHandler(Fail)
32+ RunSpecs(t, "HTTP")
33+}
34+
35+var _ = BeforeSuite(func() {
36+ By("Generating a self-signed cert for TLS tests", func() {
37+ priv, err := rsa.GenerateKey(rand.Reader, 2048)
38+ Expect(err).ToNot(HaveOccurred())
39+
40+ keyOut := bytes.NewBuffer(nil)
41+ privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
42+ Expect(err).ToNot(HaveOccurred())
43+ Expect(pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})).To(Succeed())
44+ keyDataSource.Value = keyOut.Bytes()
45+
46+ serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
47+ serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
48+ Expect(err).ToNot(HaveOccurred())
49+
50+ template := x509.Certificate{
51+ SerialNumber: serialNumber,
52+ Subject: pkix.Name{
53+ Organization: []string{"OAuth2 Proxy Test Suite"},
54+ },
55+ NotBefore: time.Now(),
56+ NotAfter: time.Now().Add(time.Hour),
57+ IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
58+ KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
59+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
60+ }
61+
62+ certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
63+ Expect(err).ToNot(HaveOccurred())
64+ certData = certBytes
65+
66+ certOut := bytes.NewBuffer(nil)
67+ Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed())
68+ certDataSource.Value = certOut.Bytes()
69+ })
70+
71+ By("Setting up a http client", func() {
72+ cert, err := tls.X509KeyPair(certDataSource.Value, keyDataSource.Value)
73+ Expect(err).ToNot(HaveOccurred())
74+
75+ certificate, err := x509.ParseCertificate(cert.Certificate[0])
76+ Expect(err).ToNot(HaveOccurred())
77+
78+ certpool := x509.NewCertPool()
79+ certpool.AddCert(certificate)
80+
81+ transport := http.DefaultTransport.(*http.Transport).Clone()
82+ transport.TLSClientConfig.RootCAs = certpool
83+
84+ client = &http.Client{
85+ Transport: transport,
86+ }
87+ })
88+})
pkg/http/server.gonew file+245
@@ -0,0 +1,245 @@
1+package http
2+
3+import (
4+ "context"
5+ "crypto/tls"
6+ "errors"
7+ "fmt"
8+ "net"
9+ "net/http"
10+ "strings"
11+ "time"
12+
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"
17+)
18+
19+// Server represents an HTTP or HTTPS server.
20+type Server interface {
21+ // Start blocks and runs the server.
22+ Start(ctx context.Context) error
23+}
24+
25+// Opts contains the information required to set up the server.
26+type Opts struct {
27+ // Handler is the http.Handler to be used to serve http pages by the server.
28+ Handler http.Handler
29+
30+ // BindAddress is the address the HTTP server should listen on.
31+ BindAddress string
32+
33+ // SecureBindAddress is the address the HTTPS server should listen on.
34+ SecureBindAddress string
35+
36+ // TLS is the TLS configuration for the server.
37+ TLS *options.TLS
38+}
39+
40+// NewServer creates a new Server from the options given.
41+func 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+ }
51+
52+ return s, nil
53+}
54+
55+// server is an implementation of the Server interface.
56+type server struct {
57+ handler http.Handler
58+
59+ listener net.Listener
60+ tlsListener net.Listener
61+}
62+
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.
66+func (s *server) setupListener(opts Opts) error {
67+ if opts.BindAddress == "" || opts.BindAddress == "-" {
68+ // No HTTP listener required
69+ return nil
70+ }
71+
72+ networkType := getNetworkScheme(opts.BindAddress)
73+ listenAddr := getListenAddress(opts.BindAddress)
74+
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
80+
81+ return nil
82+}
83+
84+// setupTLSListener sets the server TLS listener if the HTTPS server is enabled.
85+// The HTTPS server can be disabled by setting the SecureBindAddress to "-" or by
86+// leaving it empty.
87+func (s *server) setupTLSListener(opts Opts) error {
88+ if opts.SecureBindAddress == "" || opts.SecureBindAddress == "-" {
89+ // No HTTPS listener required
90+ return nil
91+ }
92+
93+ config := &tls.Config{
94+ MinVersion: tls.VersionTLS12,
95+ MaxVersion: tls.VersionTLS13,
96+ NextProtos: []string{"http/1.1"},
97+ }
98+ if opts.TLS == nil {
99+ return errors.New("no TLS config provided")
100+ }
101+ cert, err := getCertificate(opts.TLS)
102+ if err != nil {
103+ return fmt.Errorf("could not load certificate: %v", err)
104+ }
105+ config.Certificates = []tls.Certificate{cert}
106+
107+ listenAddr := getListenAddress(opts.SecureBindAddress)
108+
109+ listener, err := net.Listen("tcp", listenAddr)
110+ if err != nil {
111+ return fmt.Errorf("listen (%s) failed: %v", listenAddr, err)
112+ }
113+
114+ s.tlsListener = tls.NewListener(tcpKeepAliveListener{listener.(*net.TCPListener)}, config)
115+ return nil
116+}
117+
118+// Start starts the HTTP and HTTPS server if applicable.
119+// It will block until the context is cancelled.
120+// If any errors occur, only the first error will be returned.
121+func (s *server) Start(ctx context.Context) error {
122+ g, groupCtx := errgroup.WithContext(ctx)
123+
124+ if s.listener != nil {
125+ g.Go(func() error {
126+ if err := s.startServer(groupCtx, s.listener); err != nil {
127+ return fmt.Errorf("error starting insecure server: %v", err)
128+ }
129+ return nil
130+ })
131+ }
132+
133+ if s.tlsListener != nil {
134+ g.Go(func() error {
135+ if err := s.startServer(groupCtx, s.tlsListener); err != nil {
136+ return fmt.Errorf("error starting secure server: %v", err)
137+ }
138+ return nil
139+ })
140+ }
141+
142+ return g.Wait()
143+}
144+
145+// startServer creates and starts a new server with the given listener.
146+// When the given context is cancelled the server will be shutdown.
147+// If any errors occur, only the first error will be returned.
148+func (s *server) startServer(ctx context.Context, listener net.Listener) error {
149+ srv := &http.Server{Handler: s.handler}
150+ g, groupCtx := errgroup.WithContext(ctx)
151+
152+ g.Go(func() error {
153+ <-groupCtx.Done()
154+
155+ if err := srv.Shutdown(context.Background()); err != nil {
156+ return fmt.Errorf("error shutting down server: %v", err)
157+ }
158+ return nil
159+ })
160+
161+ g.Go(func() error {
162+ if err := srv.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
163+ return fmt.Errorf("could not start server: %v", err)
164+ }
165+ return nil
166+ })
167+
168+ return g.Wait()
169+}
170+
171+// getNetworkScheme gets the scheme for the HTTP server.
172+func getNetworkScheme(addr string) string {
173+ var scheme string
174+ i := strings.Index(addr, "://")
175+ if i > -1 {
176+ scheme = addr[0:i]
177+ }
178+
179+ switch scheme {
180+ case "", "http":
181+ return "tcp"
182+ default:
183+ return scheme
184+ }
185+}
186+
187+// getListenAddress gets the address for the HTTP server.
188+func getListenAddress(addr string) string {
189+ slice := strings.SplitN(addr, "//", 2)
190+ return slice[len(slice)-1]
191+}
192+
193+// getCertificate loads the certificate data from the TLS config.
194+func getCertificate(opts *options.TLS) (tls.Certificate, error) {
195+ keyData, err := getSecretValue(opts.Key)
196+ if err != nil {
197+ return tls.Certificate{}, fmt.Errorf("could not load key data: %v", err)
198+ }
199+
200+ certData, err := getSecretValue(opts.Cert)
201+ if err != nil {
202+ return tls.Certificate{}, fmt.Errorf("could not load cert data: %v", err)
203+ }
204+
205+ cert, err := tls.X509KeyPair(certData, keyData)
206+ if err != nil {
207+ return tls.Certificate{}, fmt.Errorf("could not parse certificate data: %v", err)
208+ }
209+
210+ return cert, nil
211+}
212+
213+// getSecretValue wraps util.GetSecretValue so that we can return an error if no
214+// source is provided.
215+func getSecretValue(src *options.SecretSource) ([]byte, error) {
216+ if src == nil {
217+ return nil, errors.New("no configuration provided")
218+ }
219+ return util.GetSecretValue(src)
220+}
221+
222+// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
223+// connections. It's used by so that dead TCP connections (e.g. closing laptop
224+// mid-download) eventually go away.
225+type tcpKeepAliveListener struct {
226+ *net.TCPListener
227+}
228+
229+// Accept implements the TCPListener interface.
230+// It sets the keep alive period to 3 minutes for each connection.
231+func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
232+ tc, err := ln.AcceptTCP()
233+ if err != nil {
234+ return nil, err
235+ }
236+ err = tc.SetKeepAlive(true)
237+ if err != nil {
238+ logger.Errorf("Error setting Keep-Alive: %v", err)
239+ }
240+ err = tc.SetKeepAlivePeriod(3 * time.Minute)
241+ if err != nil {
242+ logger.Printf("Error setting Keep-Alive period: %v", err)
243+ }
244+ return tc, nil
245+}
pkg/http/server_test.gonew file+472
@@ -0,0 +1,472 @@
1+package http
2+
3+import (
4+ "context"
5+ "errors"
6+ "fmt"
7+ "io/ioutil"
8+ "net/http"
9+
10+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
11+ . "github.com/onsi/ginkgo"
12+ . "github.com/onsi/ginkgo/extensions/table"
13+ . "github.com/onsi/gomega"
14+)
15+
16+const hello = "Hello World!"
17+
18+var _ = Describe("Server", func() {
19+ handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
20+ rw.Write([]byte(hello))
21+ })
22+
23+ Context("NewServer", func() {
24+ type newServerTableInput struct {
25+ opts Opts
26+ expectedErr error
27+ expectHTTPListener bool
28+ expectTLSListener bool
29+ }
30+
31+ DescribeTable("When creating the new server from the options", func(in *newServerTableInput) {
32+ srv, err := NewServer(in.opts)
33+ if in.expectedErr != nil {
34+ Expect(err).To(MatchError(ContainSubstring(in.expectedErr.Error())))
35+ Expect(srv).To(BeNil())
36+ return
37+ }
38+
39+ Expect(err).ToNot(HaveOccurred())
40+
41+ s, ok := srv.(*server)
42+ Expect(ok).To(BeTrue())
43+
44+ Expect(s.listener != nil).To(Equal(in.expectHTTPListener))
45+ if in.expectHTTPListener {
46+ Expect(s.listener.Close()).To(Succeed())
47+ }
48+ Expect(s.tlsListener != nil).To(Equal(in.expectTLSListener))
49+ if in.expectTLSListener {
50+ Expect(s.tlsListener.Close()).To(Succeed())
51+ }
52+ },
53+ Entry("with a valid http bind address", &newServerTableInput{
54+ opts: Opts{
55+ Handler: handler,
56+ BindAddress: "127.0.0.1:0",
57+ },
58+ expectedErr: nil,
59+ expectHTTPListener: true,
60+ expectTLSListener: false,
61+ }),
62+ Entry("with a valid https bind address, with no TLS config", &newServerTableInput{
63+ opts: Opts{
64+ Handler: handler,
65+ SecureBindAddress: "127.0.0.1:0",
66+ },
67+ expectedErr: errors.New("error setting up TLS listener: no TLS config provided"),
68+ expectHTTPListener: false,
69+ expectTLSListener: false,
70+ }),
71+ Entry("with a valid https bind address, and valid TLS config", &newServerTableInput{
72+ opts: Opts{
73+ Handler: handler,
74+ SecureBindAddress: "127.0.0.1:0",
75+ TLS: &options.TLS{
76+ Key: &keyDataSource,
77+ Cert: &certDataSource,
78+ },
79+ },
80+ expectedErr: nil,
81+ expectHTTPListener: false,
82+ expectTLSListener: true,
83+ }),
84+ Entry("with a both a valid http and valid https bind address, and valid TLS config", &newServerTableInput{
85+ opts: Opts{
86+ Handler: handler,
87+ BindAddress: "127.0.0.1:0",
88+ SecureBindAddress: "127.0.0.1:0",
89+ TLS: &options.TLS{
90+ Key: &keyDataSource,
91+ Cert: &certDataSource,
92+ },
93+ },
94+ expectedErr: nil,
95+ expectHTTPListener: true,
96+ expectTLSListener: true,
97+ }),
98+ Entry("with a \"-\" for the bind address", &newServerTableInput{
99+ opts: Opts{
100+ Handler: handler,
101+ BindAddress: "-",
102+ },
103+ expectedErr: nil,
104+ expectHTTPListener: false,
105+ expectTLSListener: false,
106+ }),
107+ Entry("with a \"-\" for the secure bind address", &newServerTableInput{
108+ opts: Opts{
109+ Handler: handler,
110+ SecureBindAddress: "-",
111+ },
112+ expectedErr: nil,
113+ expectHTTPListener: false,
114+ expectTLSListener: false,
115+ }),
116+ Entry("with an invalid bind address scheme", &newServerTableInput{
117+ opts: Opts{
118+ Handler: handler,
119+ BindAddress: "invalid://127.0.0.1:0",
120+ },
121+ expectedErr: errors.New("error setting up listener: listen (invalid, 127.0.0.1:0) failed: listen invalid: unknown network invalid"),
122+ expectHTTPListener: false,
123+ expectTLSListener: false,
124+ }),
125+ Entry("with an invalid secure bind address scheme", &newServerTableInput{
126+ opts: Opts{
127+ Handler: handler,
128+ SecureBindAddress: "invalid://127.0.0.1:0",
129+ TLS: &options.TLS{
130+ Key: &keyDataSource,
131+ Cert: &certDataSource,
132+ },
133+ },
134+ expectedErr: nil,
135+ expectHTTPListener: false,
136+ expectTLSListener: true,
137+ }),
138+ Entry("with an invalid bind address port", &newServerTableInput{
139+ opts: Opts{
140+ Handler: handler,
141+ BindAddress: "127.0.0.1:a",
142+ },
143+ expectedErr: errors.New("error setting up listener: listen (tcp, 127.0.0.1:a) failed: listen tcp: lookup tcp/a: "),
144+ expectHTTPListener: false,
145+ expectTLSListener: false,
146+ }),
147+ Entry("with an invalid secure bind address port", &newServerTableInput{
148+ opts: Opts{
149+ Handler: handler,
150+ SecureBindAddress: "127.0.0.1:a",
151+ TLS: &options.TLS{
152+ Key: &keyDataSource,
153+ Cert: &certDataSource,
154+ },
155+ },
156+ expectedErr: errors.New("error setting up TLS listener: listen (127.0.0.1:a) failed: listen tcp: lookup tcp/a: "),
157+ expectHTTPListener: false,
158+ expectTLSListener: false,
159+ }),
160+ Entry("with an invalid TLS key", &newServerTableInput{
161+ opts: Opts{
162+ Handler: handler,
163+ SecureBindAddress: "127.0.0.1:0",
164+ TLS: &options.TLS{
165+ Key: &options.SecretSource{
166+ Value: []byte("invalid"),
167+ },
168+ Cert: &certDataSource,
169+ },
170+ },
171+ expectedErr: errors.New("error setting up TLS listener: could not load certificate: could not parse certificate data: tls: failed to find any PEM data in key input"),
172+ expectHTTPListener: false,
173+ expectTLSListener: false,
174+ }),
175+ Entry("with an invalid TLS cert", &newServerTableInput{
176+ opts: Opts{
177+ Handler: handler,
178+ SecureBindAddress: "127.0.0.1:0",
179+ TLS: &options.TLS{
180+ Key: &keyDataSource,
181+ Cert: &options.SecretSource{
182+ Value: []byte("invalid"),
183+ },
184+ },
185+ },
186+ expectedErr: errors.New("error setting up TLS listener: could not load certificate: could not parse certificate data: tls: failed to find any PEM data in certificate input"),
187+ expectHTTPListener: false,
188+ expectTLSListener: false,
189+ }),
190+ Entry("with no TLS key", &newServerTableInput{
191+ opts: Opts{
192+ Handler: handler,
193+ SecureBindAddress: "127.0.0.1:0",
194+ TLS: &options.TLS{
195+ Cert: &certDataSource,
196+ },
197+ },
198+ expectedErr: errors.New("error setting up TLS listener: could not load certificate: could not load key data: no configuration provided"),
199+ expectHTTPListener: false,
200+ expectTLSListener: false,
201+ }),
202+ Entry("with no TLS cert", &newServerTableInput{
203+ opts: Opts{
204+ Handler: handler,
205+ SecureBindAddress: "127.0.0.1:0",
206+ TLS: &options.TLS{
207+ Key: &keyDataSource,
208+ },
209+ },
210+ expectedErr: errors.New("error setting up TLS listener: could not load certificate: could not load cert data: no configuration provided"),
211+ expectHTTPListener: false,
212+ expectTLSListener: false,
213+ }),
214+ Entry("when the bind address is prefixed with the http scheme", &newServerTableInput{
215+ opts: Opts{
216+ Handler: handler,
217+ BindAddress: "http://127.0.0.1:0",
218+ },
219+ expectedErr: nil,
220+ expectHTTPListener: true,
221+ expectTLSListener: false,
222+ }),
223+ Entry("when the secure bind address is prefixed with the https scheme", &newServerTableInput{
224+ opts: Opts{
225+ Handler: handler,
226+ SecureBindAddress: "https://127.0.0.1:0",
227+ TLS: &options.TLS{
228+ Key: &keyDataSource,
229+ Cert: &certDataSource,
230+ },
231+ },
232+ expectedErr: nil,
233+ expectHTTPListener: false,
234+ expectTLSListener: true,
235+ }),
236+ )
237+ })
238+
239+ Context("Start", func() {
240+ var srv Server
241+ var ctx context.Context
242+ var cancel context.CancelFunc
243+
244+ BeforeEach(func() {
245+ ctx, cancel = context.WithCancel(context.Background())
246+ })
247+
248+ AfterEach(func() {
249+ cancel()
250+ })
251+
252+ Context("with an http server", func() {
253+ var listenAddr string
254+
255+ BeforeEach(func() {
256+ var err error
257+ srv, err = NewServer(Opts{
258+ Handler: handler,
259+ BindAddress: "127.0.0.1:0",
260+ })
261+ Expect(err).ToNot(HaveOccurred())
262+
263+ s, ok := srv.(*server)
264+ Expect(ok).To(BeTrue())
265+
266+ listenAddr = fmt.Sprintf("http://%s/", s.listener.Addr().String())
267+ })
268+
269+ It("Starts the server and serves the handler", func() {
270+ go func() {
271+ defer GinkgoRecover()
272+ Expect(srv.Start(ctx)).To(Succeed())
273+ }()
274+
275+ resp, err := client.Get(listenAddr)
276+ Expect(err).ToNot(HaveOccurred())
277+ Expect(resp.StatusCode).To(Equal(http.StatusOK))
278+
279+ body, err := ioutil.ReadAll(resp.Body)
280+ Expect(err).ToNot(HaveOccurred())
281+ Expect(string(body)).To(Equal(hello))
282+ })
283+
284+ It("Stops the server when the context is cancelled", func() {
285+ go func() {
286+ defer GinkgoRecover()
287+ Expect(srv.Start(ctx)).To(Succeed())
288+ }()
289+
290+ _, err := client.Get(listenAddr)
291+ Expect(err).ToNot(HaveOccurred())
292+
293+ cancel()
294+
295+ Eventually(func() error {
296+ _, err := client.Get(listenAddr)
297+ return err
298+ }).Should(HaveOccurred())
299+ })
300+ })
301+
302+ Context("with an https server", func() {
303+ var secureListenAddr string
304+
305+ BeforeEach(func() {
306+ var err error
307+ srv, err = NewServer(Opts{
308+ Handler: handler,
309+ SecureBindAddress: "127.0.0.1:0",
310+ TLS: &options.TLS{
311+ Key: &keyDataSource,
312+ Cert: &certDataSource,
313+ },
314+ })
315+ Expect(err).ToNot(HaveOccurred())
316+
317+ s, ok := srv.(*server)
318+ Expect(ok).To(BeTrue())
319+
320+ secureListenAddr = fmt.Sprintf("https://%s/", s.tlsListener.Addr().String())
321+ })
322+
323+ It("Starts the server and serves the handler", func() {
324+ go func() {
325+ defer GinkgoRecover()
326+ Expect(srv.Start(ctx)).To(Succeed())
327+ }()
328+
329+ resp, err := client.Get(secureListenAddr)
330+ Expect(err).ToNot(HaveOccurred())
331+ Expect(resp.StatusCode).To(Equal(http.StatusOK))
332+
333+ body, err := ioutil.ReadAll(resp.Body)
334+ Expect(err).ToNot(HaveOccurred())
335+ Expect(string(body)).To(Equal(hello))
336+ })
337+
338+ It("Stops the server when the context is cancelled", func() {
339+ go func() {
340+ defer GinkgoRecover()
341+ Expect(srv.Start(ctx)).To(Succeed())
342+ }()
343+
344+ _, err := client.Get(secureListenAddr)
345+ Expect(err).ToNot(HaveOccurred())
346+
347+ cancel()
348+
349+ Eventually(func() error {
350+ _, err := client.Get(secureListenAddr)
351+ return err
352+ }).Should(HaveOccurred())
353+ })
354+
355+ It("Serves the certificate provided", func() {
356+ go func() {
357+ defer GinkgoRecover()
358+ Expect(srv.Start(ctx)).To(Succeed())
359+ }()
360+
361+ resp, err := client.Get(secureListenAddr)
362+ Expect(err).ToNot(HaveOccurred())
363+ Expect(resp.StatusCode).To(Equal(http.StatusOK))
364+
365+ Expect(resp.TLS.VerifiedChains).Should(HaveLen(1))
366+ Expect(resp.TLS.VerifiedChains[0]).Should(HaveLen(1))
367+ Expect(resp.TLS.VerifiedChains[0][0].Raw).Should(Equal(certData))
368+ })
369+ })
370+
371+ Context("with both an http and an https server", func() {
372+ var listenAddr, secureListenAddr string
373+
374+ BeforeEach(func() {
375+ var err error
376+ srv, err = NewServer(Opts{
377+ Handler: handler,
378+ BindAddress: "127.0.0.1:0",
379+ SecureBindAddress: "127.0.0.1:0",
380+ TLS: &options.TLS{
381+ Key: &keyDataSource,
382+ Cert: &certDataSource,
383+ },
384+ })
385+ Expect(err).ToNot(HaveOccurred())
386+
387+ s, ok := srv.(*server)
388+ Expect(ok).To(BeTrue())
389+
390+ listenAddr = fmt.Sprintf("http://%s/", s.listener.Addr().String())
391+ secureListenAddr = fmt.Sprintf("https://%s/", s.tlsListener.Addr().String())
392+ })
393+
394+ It("Starts the server and serves the handler on http", func() {
395+ go func() {
396+ defer GinkgoRecover()
397+ Expect(srv.Start(ctx)).To(Succeed())
398+ }()
399+
400+ resp, err := client.Get(listenAddr)
401+ Expect(err).ToNot(HaveOccurred())
402+ Expect(resp.StatusCode).To(Equal(http.StatusOK))
403+
404+ body, err := ioutil.ReadAll(resp.Body)
405+ Expect(err).ToNot(HaveOccurred())
406+ Expect(string(body)).To(Equal(hello))
407+ })
408+
409+ It("Starts the server and serves the handler on https", func() {
410+ go func() {
411+ defer GinkgoRecover()
412+ Expect(srv.Start(ctx)).To(Succeed())
413+ }()
414+
415+ resp, err := client.Get(secureListenAddr)
416+ Expect(err).ToNot(HaveOccurred())
417+ Expect(resp.StatusCode).To(Equal(http.StatusOK))
418+
419+ body, err := ioutil.ReadAll(resp.Body)
420+ Expect(err).ToNot(HaveOccurred())
421+ Expect(string(body)).To(Equal(hello))
422+ })
423+
424+ It("Stops both servers when the context is cancelled", func() {
425+ go func() {
426+ defer GinkgoRecover()
427+ Expect(srv.Start(ctx)).To(Succeed())
428+ }()
429+
430+ _, err := client.Get(listenAddr)
431+ Expect(err).ToNot(HaveOccurred())
432+ _, err = client.Get(secureListenAddr)
433+ Expect(err).ToNot(HaveOccurred())
434+
435+ cancel()
436+
437+ Eventually(func() error {
438+ _, err := client.Get(listenAddr)
439+ return err
440+ }).Should(HaveOccurred())
441+ Eventually(func() error {
442+ _, err := client.Get(secureListenAddr)
443+ return err
444+ }).Should(HaveOccurred())
445+ })
446+ })
447+ })
448+
449+ Context("getNetworkScheme", func() {
450+ DescribeTable("should return the scheme", func(in, expected string) {
451+ Expect(getNetworkScheme(in)).To(Equal(expected))
452+ },
453+ Entry("with no scheme", "127.0.0.1:0", "tcp"),
454+ Entry("with a tcp scheme", "tcp://127.0.0.1:0", "tcp"),
455+ Entry("with a http scheme", "http://192.168.0.1:1", "tcp"),
456+ Entry("with a unix scheme", "unix://172.168.16.2:2", "unix"),
457+ Entry("with a random scheme", "random://10.10.10.10:10", "random"),
458+ )
459+ })
460+
461+ Context("getListenAddress", func() {
462+ DescribeTable("should remove the scheme", func(in, expected string) {
463+ Expect(getListenAddress(in)).To(Equal(expected))
464+ },
465+ Entry("with no scheme", "127.0.0.1:0", "127.0.0.1:0"),
466+ Entry("with a tcp scheme", "tcp://127.0.0.1:0", "127.0.0.1:0"),
467+ Entry("with a http scheme", "http://192.168.0.1:1", "192.168.0.1:1"),
468+ Entry("with a unix scheme", "unix://172.168.16.2:2", "172.168.16.2:2"),
469+ Entry("with a random scheme", "random://10.10.10.10:10", "10.10.10.10:10"),
470+ )
471+ })
472+})