git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge pull request #1985 from isodude/systemd-socket
4d2b5c3Joel Speed
CHANGELOG.md+2
@@ -10,6 +10,7 @@
10
10
11- [#2800](https://github.com/oauth2-proxy/oauth2-proxy/pull/2800) Add some opencontainer labels to docker image (@halkeye)
11- [#2800](https://github.com/oauth2-proxy/oauth2-proxy/pull/2800) Add some opencontainer labels to docker image (@halkeye)
12- [#2755](https://github.com/oauth2-proxy/oauth2-proxy/pull/2755) feat: add X-Envoy-External-Address as supported header (@bjencks)
12- [#2755](https://github.com/oauth2-proxy/oauth2-proxy/pull/2755) feat: add X-Envoy-External-Address as supported header (@bjencks)
13+- [#1985](https://github.com/oauth2-proxy/oauth2-proxy/pull/1985) Add support for systemd socket (@isodude)
13
14
14# V7.7.1
15# V7.7.1
15
16
@@ -58,6 +59,7 @@
58- [#2790](https://github.com/oauth2-proxy/oauth2-proxy/pull/2790) chore(deps): update all golang dependencies (@tuunit)
59- [#2790](https://github.com/oauth2-proxy/oauth2-proxy/pull/2790) chore(deps): update all golang dependencies (@tuunit)
59- [#2607](https://github.com/oauth2-proxy/oauth2-proxy/pull/2607) fix(csrf): fix possible infinite loop (@Primexz)
60- [#2607](https://github.com/oauth2-proxy/oauth2-proxy/pull/2607) fix(csrf): fix possible infinite loop (@Primexz)
60
61
62+
61# V7.6.0
63# V7.6.0
62
64
63## Release Highlights
65## Release Highlights
docs/docs/configuration/overview.md+1−1
@@ -219,7 +219,7 @@ Provider specific options can be found on their respective subpages.
219
219
220| Flag / Config Field | Type | Description | Default |
220| Flag / Config Field | Type | Description | Default |
221| ------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
221| ------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
222−| flag: `--http-address`<br/>toml: `http_address` | string | `[http://]<addr>:<port>` or `unix://<path>` to listen on for HTTP clients. Square brackets are required for ipv6 address, e.g. `http://[::1]:4180` | `"127.0.0.1:4180"` |
222+| flag: `--http-address`<br/>toml: `http_address` | string | `[http://]<addr>:<port>` or `unix://<path>` or `fd:<int>` (case insensitive) to listen on for HTTP clients. Square brackets are required for ipv6 address, e.g. `http://[::1]:4180` | `"127.0.0.1:4180"` |
223| flag: `--https-address`<br/>toml: `https_address` | string | `[https://]<addr>:<port>` to listen on for HTTPS clients. Square brackets are required for ipv6 address, e.g. `https://[::1]:443` | `":443"` |
223| flag: `--https-address`<br/>toml: `https_address` | string | `[https://]<addr>:<port>` to listen on for HTTPS clients. Square brackets are required for ipv6 address, e.g. `https://[::1]:443` | `":443"` |
224| flag: `--metrics-address`<br/>toml: `metrics_address` | string | the address prometheus metrics will be scraped from | `""` |
224| flag: `--metrics-address`<br/>toml: `metrics_address` | string | the address prometheus metrics will be scraped from | `""` |
225| flag: `--metrics-secure-address`<br/>toml: `metrics_secure_address` | string | the address prometheus metrics will be scraped from if using HTTPS | `""` |
225| flag: `--metrics-secure-address`<br/>toml: `metrics_secure_address` | string | the address prometheus metrics will be scraped from if using HTTPS | `""` |
docs/docs/configuration/systemd_socket.mdnew file+43
@@ -0,0 +1,43 @@
1+---
2+id: systemd_socket
3+title: Systemd Socket Activation
4+---
5+
6+Pass an existing listener created by systemd.socket to oauth2-proxy.
7+
8+To do this create a socket:
9+
10+oauth2-proxy.socket
11+```
12+[Socket]
13+ListenStream=%t/oauth2.sock
14+SocketGroup=www-data
15+SocketMode=0660
16+```
17+
18+Now it's possible to call this socket from e.g. nginx:
19+```
20+server {
21+ location /oauth2/ {
22+ proxy_pass http://unix:/run/oauth2-proxy/oauth2.sock;
23+}
24+```
25+
26+The oauth2-proxy should have `--http-address=fd:3` as a parameter.
27+Here fd is case insensitive and means file descriptor. The number 3 refers to the first non-stdin/stdout/stderr file descriptor,
28+systemd-socket-activate (which is what systemd.socket uses), listens to what it is told and passes
29+the listener it created onto the process, starting with file descriptor 3.
30+
31+```
32+./oauth2-proxy \
33+ --http-address="fd:3" \
34+ --email-domain="yourcompany.com" \
35+ --upstream=http://127.0.0.1:8080/ \
36+ --cookie-secret=... \
37+ --cookie-secure=true \
38+ --provider=... \
39+ --client-id=... \
40+ --client-secret=...
41+```
42+
43+Currently TLS is not supported (but it's doable).
docs/docs/installation.md+1
@@ -29,3 +29,4 @@ title: Installation
292. [Select a Provider and Register an OAuth Application with a Provider](configuration/providers/index.md)
292. [Select a Provider and Register an OAuth Application with a Provider](configuration/providers/index.md)
303. [Configure OAuth2 Proxy using config file, command line options, or environment variables](configuration/overview.md)
303. [Configure OAuth2 Proxy using config file, command line options, or environment variables](configuration/overview.md)
314. [Configure SSL or Deploy behind an SSL endpoint](configuration/tls.md) (example provided for Nginx)
314. [Configure SSL or Deploy behind an SSL endpoint](configuration/tls.md) (example provided for Nginx)
32+5. [Configure OAuth2 Proxy using systemd.socket](configuration/systemd_socket.md) (example provided for Nginx/Systemd)
go.mod+1
@@ -11,6 +11,7 @@ require (
11 github.com/bitly/go-simplejson v0.5.1
11 github.com/bitly/go-simplejson v0.5.1
12 github.com/bsm/redislock v0.9.4
12 github.com/bsm/redislock v0.9.4
13 github.com/coreos/go-oidc/v3 v3.11.0
13 github.com/coreos/go-oidc/v3 v3.11.0
14+ github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
14 github.com/fsnotify/fsnotify v1.7.0
15 github.com/fsnotify/fsnotify v1.7.0
15 github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
16 github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
16 github.com/go-jose/go-jose/v3 v3.0.1
17 github.com/go-jose/go-jose/v3 v3.0.1
go.sum+2
@@ -42,6 +42,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
42github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
42github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
43github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
43github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
44github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
44github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
45+github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
46+github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
45github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
47github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
46github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
48github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
47github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
49github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
oauthproxy.go+3−1
@@ -606,7 +606,9 @@ func (p *OAuthProxy) isAPIPath(req *http.Request) bool {
606
606
607// isTrustedIP is used to check if a request comes from a trusted client IP address.
607// isTrustedIP is used to check if a request comes from a trusted client IP address.
608func (p *OAuthProxy) isTrustedIP(req *http.Request) bool {
608func (p *OAuthProxy) isTrustedIP(req *http.Request) bool {
609+ // RemoteAddr @ means unix socket
610+ // https://github.com/golang/go/blob/0fa53e41f122b1661d0678a6d36d71b7b5ad031d/src/syscall/syscall_linux.go#L506-L511
609− if p.trustedIPs == nil {
611+ if p.trustedIPs == nil && req.RemoteAddr != "@" {
610 return false
612 return false
611 }
613 }
612
614
pkg/apis/options/legacy_options.go+1−1
@@ -470,7 +470,7 @@ func legacyServerFlagset() *pflag.FlagSet {
470 flagSet.String("metrics-secure-address", "", "the address /metrics will be served on for HTTPS clients (e.g. \":9100\")")
470 flagSet.String("metrics-secure-address", "", "the address /metrics will be served on for HTTPS clients (e.g. \":9100\")")
471 flagSet.String("metrics-tls-cert-file", "", "path to certificate file for secure metrics server")
471 flagSet.String("metrics-tls-cert-file", "", "path to certificate file for secure metrics server")
472 flagSet.String("metrics-tls-key-file", "", "path to private key file for secure metrics server")
472 flagSet.String("metrics-tls-key-file", "", "path to private key file for secure metrics server")
473− flagSet.String("http-address", "127.0.0.1:4180", "[http://]<addr>:<port> or unix://<path> to listen on for HTTP clients")
473+ flagSet.String("http-address", "127.0.0.1:4180", "[http://]<addr>:<port> or unix://<path> or fd:<int> (case insensitive) to listen on for HTTP clients")
474 flagSet.String("https-address", ":443", "<addr>:<port> to listen on for HTTPS clients")
474 flagSet.String("https-address", ":443", "<addr>:<port> to listen on for HTTPS clients")
475 flagSet.String("tls-cert-file", "", "path to certificate file")
475 flagSet.String("tls-cert-file", "", "path to certificate file")
476 flagSet.String("tls-key-file", "", "path to private key file")
476 flagSet.String("tls-key-file", "", "path to private key file")
pkg/http/server.go+60
@@ -7,15 +7,27 @@ import (
7 "fmt"
7 "fmt"
8 "net"
8 "net"
9 "net/http"
9 "net/http"
10+ "os"
11+ "strconv"
10 "strings"
12 "strings"
11 "time"
13 "time"
12
14
15+ "github.com/coreos/go-systemd/activation"
13 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
16 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
14 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options/util"
17 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options/util"
15 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
18 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
16 "golang.org/x/sync/errgroup"
19 "golang.org/x/sync/errgroup"
17)
20)
18
21
22+// listenFdsStart corresponds to `SD_LISTEN_FDS_START`.
23+// Since the 3 first file descriptors in every linux process is
24+// stdin, stdout and stderr. The first usable file descriptor is 3.
25+// systemd-socket-activate will always assume that the first socket will be
26+// 3 and the rest follow.
27+const (
28+ listenFdsStart = 3
29+)
30+
19// Server represents an HTTP or HTTPS server.
31// Server represents an HTTP or HTTPS server.
20type Server interface {
32type Server interface {
21 // Start blocks and runs the server.
33 // Start blocks and runs the server.
@@ -35,6 +47,9 @@ type Opts struct {
35
47
36 // TLS is the TLS configuration for the server.
48 // TLS is the TLS configuration for the server.
37 TLS *options.TLS
49 TLS *options.TLS
50+
51+ // Let testing infrastructure circumvent parsing file descriptors
52+ fdFiles []*os.File
38}
53}
39
54
40// NewServer creates a new Server from the options given.
55// NewServer creates a new Server from the options given.
@@ -42,6 +57,11 @@ func NewServer(opts Opts) (Server, error) {
42 s := &server{
57 s := &server{
43 handler: opts.Handler,
58 handler: opts.Handler,
44 }
59 }
60+
61+ if len(opts.fdFiles) > 0 {
62+ s.fdFiles = opts.fdFiles
63+ }
64+
45 if err := s.setupListener(opts); err != nil {
65 if err := s.setupListener(opts); err != nil {
46 return nil, fmt.Errorf("error setting up listener: %v", err)
66 return nil, fmt.Errorf("error setting up listener: %v", err)
47 }
67 }
@@ -58,6 +78,30 @@ type server struct {
58
78
59 listener net.Listener
79 listener net.Listener
60 tlsListener net.Listener
80 tlsListener net.Listener
81+
82+ // ensure activation.Files are called once
83+ fdFiles []*os.File
84+}
85+
86+// convert a string filedescriptor to an actual listener
87+func (s *server) fdToListener(bindAddress string) (net.Listener, error) {
88+ fd, err := strconv.Atoi(bindAddress)
89+ if err != nil {
90+ return nil, fmt.Errorf("listen failed: fd with name is not implemented yet")
91+ }
92+ fdIndex := fd - listenFdsStart
93+
94+ if len(s.fdFiles) == 0 {
95+ s.fdFiles = activation.Files(true)
96+ }
97+
98+ l := len(s.fdFiles)
99+
100+ if fdIndex < 0 || fdIndex >= l || l == 0 {
101+ return nil, fmt.Errorf("listen failed: fd outside of range of available file descriptors")
102+ }
103+
104+ return net.FileListener(s.fdFiles[fdIndex])
61}
105}
62
106
63// setupListener sets the server listener if the HTTP server is enabled.
107// setupListener sets the server listener if the HTTP server is enabled.
@@ -69,6 +113,22 @@ func (s *server) setupListener(opts Opts) error {
69 return nil
113 return nil
70 }
114 }
71
115
116+ // Use fd: as a prefix for systemd socket activation, it's generic
117+ // enough and short.
118+ // The most common usage would be --http-address fd:3.
119+ // This causes oauth2-proxy to just assume that the third fd passed
120+ // to the program is indeed a net.Listener and starts using it
121+ // without setting up a new listener.
122+ if strings.HasPrefix(strings.ToLower(opts.BindAddress), "fd:") {
123+ listenAddr := opts.BindAddress[3:]
124+ listener, err := s.fdToListener(listenAddr)
125+ if err != nil {
126+ err = fmt.Errorf("listen (%s, %s) failed: %v", "file", listenAddr, err)
127+ }
128+ s.listener = listener
129+ return err
130+ }
131+
72 networkType := getNetworkScheme(opts.BindAddress)
132 networkType := getNetworkScheme(opts.BindAddress)
73 listenAddr := getListenAddress(opts.BindAddress)
133 listenAddr := getListenAddress(opts.BindAddress)
74
134
pkg/http/server_test.go+321−1
@@ -5,6 +5,7 @@ import (
5 "errors"
5 "errors"
6 "fmt"
6 "fmt"
7 "io"
7 "io"
8+ "net"
8 "net/http"
9 "net/http"
9 "os"
10 "os"
10
11
@@ -17,16 +18,17 @@ import (
17const hello = "Hello World!"
18const hello = "Hello World!"
18
19
19var _ = Describe("Server", func() {
20var _ = Describe("Server", func() {
21+
20 handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
22 handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
21 rw.Write([]byte(hello))
23 rw.Write([]byte(hello))
22 })
24 })
23−
24 Context("NewServer", func() {
25 Context("NewServer", func() {
25 type newServerTableInput struct {
26 type newServerTableInput struct {
26 opts Opts
27 opts Opts
27 expectedErr error
28 expectedErr error
28 expectHTTPListener bool
29 expectHTTPListener bool
29 expectTLSListener bool
30 expectTLSListener bool
31+ fdAddr string
30 ipv6 bool
32 ipv6 bool
31 }
33 }
32
34
@@ -34,6 +36,15 @@ var _ = Describe("Server", func() {
34 if in.ipv6 {
36 if in.ipv6 {
35 skipDevContainer()
37 skipDevContainer()
36 }
38 }
39+
40+ if in.fdAddr != "" {
41+ l, err := net.Listen("tcp", in.fdAddr)
42+ Expect(err).ToNot(HaveOccurred())
43+ f, err := l.(*net.TCPListener).File()
44+ Expect(err).ToNot(HaveOccurred())
45+ in.opts.fdFiles = []*os.File{f}
46+ }
47+
37 srv, err := NewServer(in.opts)
48 srv, err := NewServer(in.opts)
38 if in.expectedErr != nil {
49 if in.expectedErr != nil {
39 Expect(err).To(MatchError(ContainSubstring(in.expectedErr.Error())))
50 Expect(err).To(MatchError(ContainSubstring(in.expectedErr.Error())))
@@ -55,6 +66,46 @@ var _ = Describe("Server", func() {
55 Expect(s.tlsListener.Close()).To(Succeed())
66 Expect(s.tlsListener.Close()).To(Succeed())
56 }
67 }
57 },
68 },
69+ Entry("with a valid non-lowercase fd IPv4 bind address", &newServerTableInput{
70+ opts: Opts{
71+ Handler: handler,
72+ BindAddress: "Fd:3",
73+ },
74+ expectedErr: nil,
75+ expectHTTPListener: true,
76+ expectTLSListener: false,
77+ fdAddr: "127.0.0.1:0",
78+ }),
79+ Entry("with a valid fd IPv4 bind address", &newServerTableInput{
80+ opts: Opts{
81+ Handler: handler,
82+ BindAddress: "fd:3",
83+ },
84+ expectedErr: nil,
85+ expectHTTPListener: true,
86+ expectTLSListener: false,
87+ fdAddr: "127.0.0.1:0",
88+ }),
89+ Entry("with a invalid fd named bind address", &newServerTableInput{
90+ opts: Opts{
91+ Handler: handler,
92+ BindAddress: "fd:hello",
93+ },
94+ expectedErr: fmt.Errorf("error setting up listener: listen (file, %s) failed: listen failed: fd with name is not implemented yet", "hello"),
95+ expectHTTPListener: true,
96+ expectTLSListener: false,
97+ fdAddr: "127.0.0.1:0",
98+ }),
99+ Entry("with a invalid fd IPv4 bind address", &newServerTableInput{
100+ opts: Opts{
101+ Handler: handler,
102+ BindAddress: "fd:4",
103+ },
104+ expectedErr: fmt.Errorf("error setting up listener: listen (file, %d) failed: listen failed: fd outside of range of available file descriptors", 4),
105+ expectHTTPListener: true,
106+ expectTLSListener: false,
107+ fdAddr: "127.0.0.1:0",
108+ }),
58 Entry("with an ipv4 valid http bind address", &newServerTableInput{
109 Entry("with an ipv4 valid http bind address", &newServerTableInput{
59 opts: Opts{
110 opts: Opts{
60 Handler: handler,
111 Handler: handler,
@@ -86,6 +137,21 @@ var _ = Describe("Server", func() {
86 expectHTTPListener: false,
137 expectHTTPListener: false,
87 expectTLSListener: true,
138 expectTLSListener: true,
88 }),
139 }),
140+ Entry("with a both a fd valid http and ipv4 valid https bind address, and valid TLS config", &newServerTableInput{
141+ opts: Opts{
142+ Handler: handler,
143+ BindAddress: "fd:3",
144+ SecureBindAddress: "127.0.0.1:0",
145+ TLS: &options.TLS{
146+ Key: &ipv4KeyDataSource,
147+ Cert: &ipv4CertDataSource,
148+ },
149+ },
150+ expectedErr: nil,
151+ expectHTTPListener: true,
152+ expectTLSListener: true,
153+ fdAddr: "127.0.0.1:0",
154+ }),
89 Entry("with a both a ipv4 valid http and ipv4 valid https bind address, and valid TLS config", &newServerTableInput{
155 Entry("with a both a ipv4 valid http and ipv4 valid https bind address, and valid TLS config", &newServerTableInput{
90 opts: Opts{
156 opts: Opts{
91 Handler: handler,
157 Handler: handler,
@@ -300,6 +366,27 @@ var _ = Describe("Server", func() {
300 expectHTTPListener: false,
366 expectHTTPListener: false,
301 expectTLSListener: true,
367 expectTLSListener: true,
302 }),
368 }),
369+ Entry("with valid fd IPv6 bind address", &newServerTableInput{
370+ opts: Opts{
371+ Handler: handler,
372+ BindAddress: "fd:3",
373+ },
374+ expectedErr: nil,
375+ expectHTTPListener: true,
376+ expectTLSListener: false,
377+ fdAddr: "[::1]:0",
378+ ipv6: true,
379+ }),
380+ Entry("with a invalid fd IPv6 bind address", &newServerTableInput{
381+ opts: Opts{
382+ Handler: handler,
383+ BindAddress: "fd:4",
384+ },
385+ expectedErr: fmt.Errorf("error setting up listener: listen (file, %d) failed: listen failed: fd outside of range of available file descriptors", 4),
386+ expectHTTPListener: true,
387+ expectTLSListener: false,
388+ fdAddr: "[::1]:0",
389+ }),
303 Entry("with an ipv6 valid http bind address", &newServerTableInput{
390 Entry("with an ipv6 valid http bind address", &newServerTableInput{
304 opts: Opts{
391 opts: Opts{
305 Handler: handler,
392 Handler: handler,
@@ -334,6 +421,22 @@ var _ = Describe("Server", func() {
334 expectTLSListener: true,
421 expectTLSListener: true,
335 ipv6: true,
422 ipv6: true,
336 }),
423 }),
424+ Entry("with a both a fd valid http and ipv6 valid https bind address, and valid TLS config", &newServerTableInput{
425+ opts: Opts{
426+ Handler: handler,
427+ BindAddress: "fd:3",
428+ SecureBindAddress: "[::1]:0",
429+ TLS: &options.TLS{
430+ Key: &ipv6KeyDataSource,
431+ Cert: &ipv6CertDataSource,
432+ },
433+ },
434+ expectedErr: nil,
435+ expectHTTPListener: true,
436+ expectTLSListener: true,
437+ fdAddr: "[::1]:0",
438+ ipv6: true,
439+ }),
337 Entry("with a both a ipv6 valid http and ipv6 valid https bind address, and valid TLS config", &newServerTableInput{
440 Entry("with a both a ipv6 valid http and ipv6 valid https bind address, and valid TLS config", &newServerTableInput{
338 opts: Opts{
441 opts: Opts{
339 Handler: handler,
442 Handler: handler,
@@ -563,6 +666,58 @@ var _ = Describe("Server", func() {
563
666
564 })
667 })
565
668
669+ Context("with an fd ipv4 http server", func() {
670+ var listenAddr string
671+
672+ BeforeEach(func() {
673+ l, err := net.Listen("tcp", "127.0.0.1:0")
674+ Expect(err).ToNot(HaveOccurred())
675+ f, err := l.(*net.TCPListener).File()
676+ Expect(err).ToNot(HaveOccurred())
677+
678+ srv, err = NewServer(Opts{
679+ Handler: handler,
680+ BindAddress: "fd:3",
681+ fdFiles: []*os.File{f},
682+ })
683+ Expect(err).ToNot(HaveOccurred())
684+
685+ listenAddr = fmt.Sprintf("http://%s/", l.Addr().String())
686+ })
687+
688+ It("Starts the server and serves the handler", func() {
689+ go func() {
690+ defer GinkgoRecover()
691+ Expect(srv.Start(ctx)).To(Succeed())
692+ }()
693+
694+ resp, err := httpGet(ctx, listenAddr)
695+ Expect(err).ToNot(HaveOccurred())
696+ Expect(resp.StatusCode).To(Equal(http.StatusOK))
697+
698+ body, err := io.ReadAll(resp.Body)
699+ Expect(err).ToNot(HaveOccurred())
700+ Expect(string(body)).To(Equal(hello))
701+ })
702+
703+ It("Stops the server when the context is cancelled", func() {
704+ go func() {
705+ defer GinkgoRecover()
706+ Expect(srv.Start(ctx)).To(Succeed())
707+ }()
708+
709+ _, err := httpGet(ctx, listenAddr)
710+ Expect(err).ToNot(HaveOccurred())
711+
712+ cancel()
713+
714+ Eventually(func() error {
715+ _, err := httpGet(ctx, listenAddr)
716+ return err
717+ }).Should(HaveOccurred())
718+ })
719+ })
720+
566 Context("with an ipv4 http server", func() {
721 Context("with an ipv4 http server", func() {
567 var listenAddr string
722 var listenAddr string
568
723
@@ -682,6 +837,88 @@ var _ = Describe("Server", func() {
682 })
837 })
683 })
838 })
684
839
840+ Context("with a fd ipv4 http and an ipv4 https server", func() {
841+ var listenAddr, secureListenAddr string
842+
843+ BeforeEach(func() {
844+ l, err := net.Listen("tcp", "127.0.0.1:0")
845+ Expect(err).ToNot(HaveOccurred())
846+ f, err := l.(*net.TCPListener).File()
847+ Expect(err).ToNot(HaveOccurred())
848+
849+ srv, err = NewServer(Opts{
850+ Handler: handler,
851+ BindAddress: "fd:3",
852+ fdFiles: []*os.File{f},
853+ SecureBindAddress: "127.0.0.1:0",
854+ TLS: &options.TLS{
855+ Key: &ipv4KeyDataSource,
856+ Cert: &ipv4CertDataSource,
857+ },
858+ })
859+ Expect(err).ToNot(HaveOccurred())
860+
861+ s, ok := srv.(*server)
862+ Expect(ok).To(BeTrue())
863+
864+ listenAddr = fmt.Sprintf("http://%s/", l.Addr().String())
865+ secureListenAddr = fmt.Sprintf("https://%s/", s.tlsListener.Addr().String())
866+ })
867+
868+ It("Starts the server and serves the handler on http", func() {
869+ go func() {
870+ defer GinkgoRecover()
871+ Expect(srv.Start(ctx)).To(Succeed())
872+ }()
873+
874+ resp, err := httpGet(ctx, listenAddr)
875+ Expect(err).ToNot(HaveOccurred())
876+ Expect(resp.StatusCode).To(Equal(http.StatusOK))
877+
878+ body, err := io.ReadAll(resp.Body)
879+ Expect(err).ToNot(HaveOccurred())
880+ Expect(string(body)).To(Equal(hello))
881+ })
882+
883+ It("Starts the server and serves the handler on https", func() {
884+ go func() {
885+ defer GinkgoRecover()
886+ Expect(srv.Start(ctx)).To(Succeed())
887+ }()
888+
889+ resp, err := httpGet(ctx, secureListenAddr)
890+ Expect(err).ToNot(HaveOccurred())
891+ Expect(resp.StatusCode).To(Equal(http.StatusOK))
892+
893+ body, err := io.ReadAll(resp.Body)
894+ Expect(err).ToNot(HaveOccurred())
895+ Expect(string(body)).To(Equal(hello))
896+ })
897+
898+ It("Stops both servers when the context is cancelled", func() {
899+ go func() {
900+ defer GinkgoRecover()
901+ Expect(srv.Start(ctx)).To(Succeed())
902+ }()
903+
904+ _, err := httpGet(ctx, listenAddr)
905+ Expect(err).ToNot(HaveOccurred())
906+ _, err = httpGet(ctx, secureListenAddr)
907+ Expect(err).ToNot(HaveOccurred())
908+
909+ cancel()
910+
911+ Eventually(func() error {
912+ _, err := httpGet(ctx, listenAddr)
913+ return err
914+ }).Should(HaveOccurred())
915+ Eventually(func() error {
916+ _, err := httpGet(ctx, secureListenAddr)
917+ return err
918+ }).Should(HaveOccurred())
919+ })
920+ })
921+
685 Context("with both an ipv4 http and an ipv4 https server", func() {
922 Context("with both an ipv4 http and an ipv4 https server", func() {
686 var listenAddr, secureListenAddr string
923 var listenAddr, secureListenAddr string
687
924
@@ -880,6 +1117,89 @@ var _ = Describe("Server", func() {
880 })
1117 })
881 })
1118 })
882
1119
1120+ Context("with an fd ipv6 http and an ipv6 https server", func() {
1121+ var listenAddr, secureListenAddr string
1122+
1123+ BeforeEach(func() {
1124+ skipDevContainer()
1125+ l, err := net.Listen("tcp", "[::1]:0")
1126+ Expect(err).ToNot(HaveOccurred())
1127+ f, err := l.(*net.TCPListener).File()
1128+ Expect(err).ToNot(HaveOccurred())
1129+
1130+ srv, err = NewServer(Opts{
1131+ Handler: handler,
1132+ BindAddress: "fd:3",
1133+ fdFiles: []*os.File{f},
1134+ SecureBindAddress: "[::1]:0",
1135+ TLS: &options.TLS{
1136+ Key: &ipv6KeyDataSource,
1137+ Cert: &ipv6CertDataSource,
1138+ },
1139+ })
1140+ Expect(err).ToNot(HaveOccurred())
1141+
1142+ s, ok := srv.(*server)
1143+ Expect(ok).To(BeTrue())
1144+
1145+ listenAddr = fmt.Sprintf("http://%s/", l.Addr().String())
1146+ secureListenAddr = fmt.Sprintf("https://%s/", s.tlsListener.Addr().String())
1147+ })
1148+
1149+ It("Starts the server and serves the handler on http", func() {
1150+ go func() {
1151+ defer GinkgoRecover()
1152+ Expect(srv.Start(ctx)).To(Succeed())
1153+ }()
1154+
1155+ resp, err := httpGet(ctx, listenAddr)
1156+ Expect(err).ToNot(HaveOccurred())
1157+ Expect(resp.StatusCode).To(Equal(http.StatusOK))
1158+
1159+ body, err := io.ReadAll(resp.Body)
1160+ Expect(err).ToNot(HaveOccurred())
1161+ Expect(string(body)).To(Equal(hello))
1162+ })
1163+
1164+ It("Starts the server and serves the handler on https", func() {
1165+ go func() {
1166+ defer GinkgoRecover()
1167+ Expect(srv.Start(ctx)).To(Succeed())
1168+ }()
1169+
1170+ resp, err := httpGet(ctx, secureListenAddr)
1171+ Expect(err).ToNot(HaveOccurred())
1172+ Expect(resp.StatusCode).To(Equal(http.StatusOK))
1173+
1174+ body, err := io.ReadAll(resp.Body)
1175+ Expect(err).ToNot(HaveOccurred())
1176+ Expect(string(body)).To(Equal(hello))
1177+ })
1178+
1179+ It("Stops both servers when the context is cancelled", func() {
1180+ go func() {
1181+ defer GinkgoRecover()
1182+ Expect(srv.Start(ctx)).To(Succeed())
1183+ }()
1184+
1185+ _, err := httpGet(ctx, listenAddr)
1186+ Expect(err).ToNot(HaveOccurred())
1187+ _, err = httpGet(ctx, secureListenAddr)
1188+ Expect(err).ToNot(HaveOccurred())
1189+
1190+ cancel()
1191+
1192+ Eventually(func() error {
1193+ _, err := httpGet(ctx, listenAddr)
1194+ return err
1195+ }).Should(HaveOccurred())
1196+ Eventually(func() error {
1197+ _, err := httpGet(ctx, secureListenAddr)
1198+ return err
1199+ }).Should(HaveOccurred())
1200+ })
1201+ })
1202+
883 Context("with both an ipv6 http and an ipv6 https server", func() {
1203 Context("with both an ipv6 http and an ipv6 https server", func() {
884 var listenAddr, secureListenAddr string
1204 var listenAddr, secureListenAddr string
885
1205