Repository: oauth2-proxy/oauth2-proxy
Pull request #1616 (2022-04-12 01:51:51)

Add redirect instructions for gitlab on sub-dir
<!--- Provide a general summary of your changes in the Title above -->

## Description

Addresses issue #1529

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

## How Has This Been Tested?

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [ ] My change requires a change to the documentation or CHANGELOG.
- [ ] I have updated the documentation/CHANGELOG accordingly.
- [ ] I have created a feature (non-master) branch for my PR.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 429b00ec..2e91dff1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,6 +41,8 @@ If you are using an architecture specific tag (ex: v7.2.1-arm64) you should move
 - [#1594](https://github.com/oauth2-proxy/oauth2-proxy/pull/1594) Release ARMv8 docker images (@braunsonm)
 - [#1649](https://github.com/oauth2-proxy/oauth2-proxy/pull/1649) Return a 400 instead of a 500 when a request contains an invalid redirect target (@niksko)
 - [#1638](https://github.com/oauth2-proxy/oauth2-proxy/pull/1638) Implement configurable upstream timeout (@jacksgt)
+- [#1650](https://github.com/oauth2-proxy/oauth2-proxy/pull/1650) Fixed 500 when checking if user has repo (@adamsong)
+- [#1635](https://github.com/oauth2-proxy/oauth2-proxy/pull/1635) Added description and unit tests for ipv6 address (@t-katsumura)
 
 # V7.2.1
 
diff --git a/docs/docs/configuration/auth.md b/docs/docs/configuration/auth.md
index dd43cea3..012bcfaf 100644
--- a/docs/docs/configuration/auth.md
+++ b/docs/docs/configuration/auth.md
@@ -223,6 +223,8 @@ If you are using self-hosted GitLab, make sure you set the following to the appr
 
     --oidc-issuer-url="<your gitlab url>"
 
+If your self-hosted GitLab is on a sub-directory (e.g. domain.tld/gitlab), as opposed to its own sub-domain (e.g. gitlab.domain.tld), you may need to add a redirect from domain.tld/oauth pointing at e.g. domain.tld/gitlab/oauth.
+
 ### LinkedIn Auth Provider
 
 For LinkedIn, the registration steps are:
diff --git a/docs/docs/configuration/overview.md b/docs/docs/configuration/overview.md
index 85d3cad5..00c59ca5 100644
--- a/docs/docs/configuration/overview.md
+++ b/docs/docs/configuration/overview.md
@@ -119,8 +119,8 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
 | `--google-service-account-json` | string | the path to the service account json credentials | |
 | `--htpasswd-file` | string | additionally authenticate against a htpasswd file. Entries must be created with `htpasswd -B` for bcrypt encryption | |
 | `--htpasswd-user-group` | string \| list | the groups to be set on sessions for htpasswd users | |
-| `--http-address` | string | `[http://]<addr>:<port>` or `unix://<path>` to listen on for HTTP clients | `"127.0.0.1:4180"` |
-| `--https-address` | string | `<addr>:<port>` to listen on for HTTPS clients | `":443"` |
+| `--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"` |
+| `--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"` |
 | `--logging-compress` | bool | Should rotated log files be compressed using gzip | false |
 | `--logging-filename` | string | File to log requests to, empty for `stdout` | `""` (stdout) |
 | `--logging-local-time` | bool | Use local time in log files and backup filenames instead of UTC | true (local time) |
diff --git a/docs/versioned_docs/version-7.2.x/configuration/auth.md b/docs/versioned_docs/version-7.2.x/configuration/auth.md
index c9529979..c7c7184b 100644
--- a/docs/versioned_docs/version-7.2.x/configuration/auth.md
+++ b/docs/versioned_docs/version-7.2.x/configuration/auth.md
@@ -222,6 +222,8 @@ If you are using self-hosted GitLab, make sure you set the following to the appr
 
     --oidc-issuer-url="<your gitlab url>"
 
+If your self-hosted GitLab is on a sub-directory (e.g. domain.tld/gitlab), as opposed to its own sub-domain (e.g. gitlab.domain.tld), you may need to add a redirect from domain.tld/oauth pointing at e.g. domain.tld/gitlab/oauth.
+
 ### LinkedIn Auth Provider
 
 For LinkedIn, the registration steps are:
diff --git a/pkg/util/util.go b/pkg/util/util.go
index 41c9a09f..d8c1fc06 100644
--- a/pkg/util/util.go
+++ b/pkg/util/util.go
@@ -33,7 +33,7 @@ func GetCertPool(paths []string) (*x509.CertPool, error) {
 }
 
 // https://golang.org/src/crypto/tls/generate_cert.go as a function
-func GenerateCert() ([]byte, []byte, error) {
+func GenerateCert(ipaddr string) ([]byte, []byte, error) {
 	var err error
 
 	priv, err := rsa.GenerateKey(rand.Reader, 2048)
@@ -63,7 +63,7 @@ func GenerateCert() ([]byte, []byte, error) {
 
 		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
 
-		IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
+		IPAddresses: []net.IP{net.ParseIP(ipaddr)},
 	}
 	certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
 	return certBytes, keyBytes, err
diff --git a/providers/github.go b/providers/github.go
index a3572854..ae4b2e83 100644
--- a/providers/github.go
+++ b/providers/github.go
@@ -312,7 +312,7 @@ func (p *GitHubProvider) hasRepo(ctx context.Context, accessToken string) (bool,
 	endpoint := &url.URL{
 		Scheme: p.ValidateURL.Scheme,
 		Host:   p.ValidateURL.Host,
-		Path:   path.Join(p.ValidateURL.Path, "/repo/", p.Repo),
+		Path:   path.Join(p.ValidateURL.Path, "/repos/", p.Repo),
 	}
 
 	var repo repository


Test patch:
diff --git a/pkg/http/http_suite_test.go b/pkg/http/http_suite_test.go
index 62d5a3e6..79aa19a8 100644
--- a/pkg/http/http_suite_test.go
+++ b/pkg/http/http_suite_test.go
@@ -15,8 +15,9 @@ import (
 	. "github.com/onsi/gomega"
 )
 
-var certData []byte
-var certDataSource, keyDataSource options.SecretSource
+var ipv4CertData, ipv6CertData []byte
+var ipv4CertDataSource, ipv4KeyDataSource options.SecretSource
+var ipv6CertDataSource, ipv6KeyDataSource options.SecretSource
 var client *http.Client
 
 func TestHTTPSuite(t *testing.T) {
@@ -28,28 +29,46 @@ func TestHTTPSuite(t *testing.T) {
 }
 
 var _ = BeforeSuite(func() {
-	By("Generating a self-signed cert for TLS tests", func() {
-		certBytes, keyBytes, err := util.GenerateCert()
+	By("Generating a ipv4 self-signed cert for TLS tests", func() {
+		certBytes, keyBytes, err := util.GenerateCert("127.0.0.1")
 		Expect(err).ToNot(HaveOccurred())
-		certData = certBytes
+		ipv4CertData = certBytes
 
 		certOut := new(bytes.Buffer)
 		Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed())
-		certDataSource.Value = certOut.Bytes()
+		ipv4CertDataSource.Value = certOut.Bytes()
 		keyOut := new(bytes.Buffer)
 		Expect(pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})).To(Succeed())
-		keyDataSource.Value = keyOut.Bytes()
+		ipv4KeyDataSource.Value = keyOut.Bytes()
+	})
+
+	By("Generating a ipv6 self-signed cert for TLS tests", func() {
+		certBytes, keyBytes, err := util.GenerateCert("::1")
+		Expect(err).ToNot(HaveOccurred())
+		ipv6CertData = certBytes
+
+		certOut := new(bytes.Buffer)
+		Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed())
+		ipv6CertDataSource.Value = certOut.Bytes()
+		keyOut := new(bytes.Buffer)
+		Expect(pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})).To(Succeed())
+		ipv6KeyDataSource.Value = keyOut.Bytes()
 	})
 
 	By("Setting up a http client", func() {
-		cert, err := tls.X509KeyPair(certDataSource.Value, keyDataSource.Value)
+		ipv4cert, err := tls.X509KeyPair(ipv4CertDataSource.Value, ipv4KeyDataSource.Value)
+		Expect(err).ToNot(HaveOccurred())
+		ipv6cert, err := tls.X509KeyPair(ipv6CertDataSource.Value, ipv6KeyDataSource.Value)
 		Expect(err).ToNot(HaveOccurred())
 
-		certificate, err := x509.ParseCertificate(cert.Certificate[0])
+		ipv4certificate, err := x509.ParseCertificate(ipv4cert.Certificate[0])
+		Expect(err).ToNot(HaveOccurred())
+		ipv6certificate, err := x509.ParseCertificate(ipv6cert.Certificate[0])
 		Expect(err).ToNot(HaveOccurred())
 
 		certpool := x509.NewCertPool()
-		certpool.AddCert(certificate)
+		certpool.AddCert(ipv4certificate)
+		certpool.AddCert(ipv6certificate)
 
 		transport := http.DefaultTransport.(*http.Transport).Clone()
 		transport.TLSClientConfig.RootCAs = certpool
diff --git a/pkg/http/server_test.go b/pkg/http/server_test.go
index 0451fa71..3c48cabe 100644
--- a/pkg/http/server_test.go
+++ b/pkg/http/server_test.go
@@ -50,7 +50,7 @@ var _ = Describe("Server", func() {
 				Expect(s.tlsListener.Close()).To(Succeed())
 			}
 		},
-			Entry("with a valid http bind address", &newServerTableInput{
+			Entry("with an ipv4 valid http bind address", &newServerTableInput{
 				opts: Opts{
 					Handler:     handler,
 					BindAddress: "127.0.0.1:0",
@@ -59,7 +59,7 @@ var _ = Describe("Server", func() {
 				expectHTTPListener: true,
 				expectTLSListener:  false,
 			}),
-			Entry("with a valid https bind address, with no TLS config", &newServerTableInput{
+			Entry("with an ipv4 valid https bind address, with no TLS config", &newServerTableInput{
 				opts: Opts{
 					Handler:           handler,
 					SecureBindAddress: "127.0.0.1:0",
@@ -68,27 +68,27 @@ var _ = Describe("Server", func() {
 				expectHTTPListener: false,
 				expectTLSListener:  false,
 			}),
-			Entry("with a valid https bind address, and valid TLS config", &newServerTableInput{
+			Entry("with an ipv4 valid https bind address, and valid TLS config", &newServerTableInput{
 				opts: Opts{
 					Handler:           handler,
 					SecureBindAddress: "127.0.0.1:0",
 					TLS: &options.TLS{
-						Key:  &keyDataSource,
-						Cert: &certDataSource,
+						Key:  &ipv4KeyDataSource,
+						Cert: &ipv4CertDataSource,
 					},
 				},
 				expectedErr:        nil,
 				expectHTTPListener: false,
 				expectTLSListener:  true,
 			}),
-			Entry("with a both a valid http and valid https bind address, and valid TLS config", &newServerTableInput{
+			Entry("with a both a ipv4 valid http and ipv4 valid https bind address, and valid TLS config", &newServerTableInput{
 				opts: Opts{
 					Handler:           handler,
 					BindAddress:       "127.0.0.1:0",
 					SecureBindAddress: "127.0.0.1:0",
 					TLS: &options.TLS{
-						Key:  &keyDataSource,
-						Cert: &certDataSource,
+						Key:  &ipv4KeyDataSource,
+						Cert: &ipv4CertDataSource,
 					},
 				},
 				expectedErr:        nil,
@@ -113,7 +113,7 @@ var _ = Describe("Server", func() {
 				expectHTTPListener: false,
 				expectTLSListener:  false,
 			}),
-			Entry("with an invalid bind address scheme", &newServerTableInput{
+			Entry("with an ipv4 invalid bind address scheme", &newServerTableInput{
 				opts: Opts{
 					Handler:     handler,
 					BindAddress: "invalid://127.0.0.1:0",
@@ -122,20 +122,20 @@ var _ = Describe("Server", func() {
 				expectHTTPListener: false,
 				expectTLSListener:  false,
 			}),
-			Entry("with an invalid secure bind address scheme", &newServerTableInput{
+			Entry("with an ipv4 invalid secure bind address scheme", &newServerTableInput{
 				opts: Opts{
 					Handler:           handler,
 					SecureBindAddress: "invalid://127.0.0.1:0",
 					TLS: &options.TLS{
-						Key:  &keyDataSource,
-						Cert: &certDataSource,
+						Key:  &ipv4KeyDataSource,
+						Cert: &ipv4CertDataSource,
 					},
 				},
 				expectedErr:        nil,
 				expectHTTPListener: false,
 				expectTLSListener:  true,
 			}),
-			Entry("with an invalid bind address port", &newServerTableInput{
+			Entry("with an ipv4 invalid bind address port", &newServerTableInput{
 				opts: Opts{
 					Handler:     handler,
 					BindAddress: "127.0.0.1:a",
@@ -144,20 +144,20 @@ var _ = Describe("Server", func() {
 				expectHTTPListener: false,
 				expectTLSListener:  false,
 			}),
-			Entry("with an invalid secure bind address port", &newServerTableInput{
+			Entry("with an ipv4 invalid secure bind address port", &newServerTableInput{
 				opts: Opts{
 					Handler:           handler,
 					SecureBindAddress: "127.0.0.1:a",
 					TLS: &options.TLS{
-						Key:  &keyDataSource,
-						Cert: &certDataSource,
+						Key:  &ipv4KeyDataSource,
+						Cert: &ipv4CertDataSource,
 					},
 				},
 				expectedErr:        errors.New("error setting up TLS listener: listen (127.0.0.1:a) failed: listen tcp: "),
 				expectHTTPListener: false,
 				expectTLSListener:  false,
 			}),
-			Entry("with an invalid TLS key", &newServerTableInput{
+			Entry("with an ipv4 invalid TLS key", &newServerTableInput{
 				opts: Opts{
 					Handler:           handler,
 					SecureBindAddress: "127.0.0.1:0",
@@ -165,19 +165,19 @@ var _ = Describe("Server", func() {
 						Key: &options.SecretSource{
 							Value: []byte("invalid"),
 						},
-						Cert: &certDataSource,
+						Cert: &ipv4CertDataSource,
 					},
 				},
 				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"),
 				expectHTTPListener: false,
 				expectTLSListener:  false,
 			}),
-			Entry("with an invalid TLS cert", &newServerTableInput{
+			Entry("with an ipv4 invalid TLS cert", &newServerTableInput{
 				opts: Opts{
 					Handler:           handler,
 					SecureBindAddress: "127.0.0.1:0",
 					TLS: &options.TLS{
-						Key: &keyDataSource,
+						Key: &ipv4KeyDataSource,
 						Cert: &options.SecretSource{
 							Value: []byte("invalid"),
 						},
@@ -187,31 +187,31 @@ var _ = Describe("Server", func() {
 				expectHTTPListener: false,
 				expectTLSListener:  false,
 			}),
-			Entry("with no TLS key", &newServerTableInput{
+			Entry("with an ipv4 address, with no TLS key", &newServerTableInput{
 				opts: Opts{
 					Handler:           handler,
 					SecureBindAddress: "127.0.0.1:0",
 					TLS: &options.TLS{
-						Cert: &certDataSource,
+						Cert: &ipv4CertDataSource,
 					},
 				},
 				expectedErr:        errors.New("error setting up TLS listener: could not load certificate: could not load key data: no configuration provided"),
 				expectHTTPListener: false,
 				expectTLSListener:  false,
 			}),
-			Entry("with no TLS cert", &newServerTableInput{
+			Entry("with an ipv4 address, with no TLS cert", &newServerTableInput{
 				opts: Opts{
 					Handler:           handler,
 					SecureBindAddress: "127.0.0.1:0",
 					TLS: &options.TLS{
-						Key: &keyDataSource,
+						Key: &ipv4KeyDataSource,
 					},
 				},
 				expectedErr:        errors.New("error setting up TLS listener: could not load certificate: could not load cert data: no configuration provided"),
 				expectHTTPListener: false,
 				expectTLSListener:  false,
 			}),
-			Entry("when the bind address is prefixed with the http scheme", &newServerTableInput{
+			Entry("when the ipv4 bind address is prefixed with the http scheme", &newServerTableInput{
 				opts: Opts{
 					Handler:     handler,
 					BindAddress: "http://127.0.0.1:0",
@@ -220,26 +220,26 @@ var _ = Describe("Server", func() {
 				expectHTTPListener: true,
 				expectTLSListener:  false,
 			}),
-			Entry("when the secure bind address is prefixed with the https scheme", &newServerTableInput{
+			Entry("when the ipv4 secure bind address is prefixed with the https scheme", &newServerTableInput{
 				opts: Opts{
 					Handler:           handler,
 					SecureBindAddress: "https://127.0.0.1:0",
 					TLS: &options.TLS{
-						Key:  &keyDataSource,
-						Cert: &certDataSource,
+						Key:  &ipv4KeyDataSource,
+						Cert: &ipv4CertDataSource,
 					},
 				},
 				expectedErr:        nil,
 				expectHTTPListener: false,
 				expectTLSListener:  true,
 			}),
-			Entry("with a valid https bind address, and valid TLS config with MinVersion", &newServerTableInput{
+			Entry("with an ipv4 valid https bind address, and valid TLS config with MinVersion", &newServerTableInput{
 				opts: Opts{
 					Handler:           handler,
 					SecureBindAddress: "127.0.0.1:0",
 					TLS: &options.TLS{
-						Key:        &keyDataSource,
-						Cert:       &certDataSource,
+						Key:        &ipv4KeyDataSource,
+						Cert:       &ipv4CertDataSource,
 						MinVersion: "TLS1.3",
 					},
 				},
@@ -247,13 +247,206 @@ var _ = Describe("Server", func() {
 				expectHTTPListener: false,
 				expectTLSListener:  true,
 			}),
-			Entry("with a valid https bind address, and invalid TLS config with unknown MinVersion", &newServerTableInput{
+			Entry("with an ipv4 valid https bind address, and invalid TLS config with unknown MinVersion", &newServerTableInput{
 				opts: Opts{
 					Handler:           handler,
 					SecureBindAddress: "127.0.0.1:0",
 					TLS: &options.TLS{
-						Key:        &keyDataSource,
-						Cert:       &certDataSource,
+						Key:        &ipv4KeyDataSource,
+						Cert:       &ipv4CertDataSource,
+						MinVersion: "TLS1.42",
+					},
+				},
+				expectedErr:        errors.New("error setting up TLS listener: unknown TLS MinVersion config provided"),
+				expectHTTPListener: false,
+				expectTLSListener:  true,
+			}),
+			Entry("with an ipv6 valid http bind address", &newServerTableInput{
+				opts: Opts{
+					Handler:     handler,
+					BindAddress: "[::1]:0",
+				},
+				expectedErr:        nil,
+				expectHTTPListener: true,
+				expectTLSListener:  false,
+			}),
+			Entry("with an ipv6 valid https bind address, with no TLS config", &newServerTableInput{
+				opts: Opts{
+					Handler:           handler,
+					SecureBindAddress: "[::1]:0",
+				},
+				expectedErr:        errors.New("error setting up TLS listener: no TLS config provided"),
+				expectHTTPListener: false,
+				expectTLSListener:  false,
+			}),
+			Entry("with an ipv6 valid https bind address, and valid TLS config", &newServerTableInput{
+				opts: Opts{
+					Handler:           handler,
+					SecureBindAddress: "[::1]:0",
+					TLS: &options.TLS{
+						Key:  &ipv6KeyDataSource,
+						Cert: &ipv6CertDataSource,
+					},
+				},
+				expectedErr:        nil,
+				expectHTTPListener: false,
+				expectTLSListener:  true,
+			}),
+			Entry("with a both a ipv6 valid http and ipv6 valid https bind address, and valid TLS config", &newServerTableInput{
+				opts: Opts{
+					Handler:           handler,
+					BindAddress:       "[::1]:0",
+					SecureBindAddress: "[::1]:0",
+					TLS: &options.TLS{
+						Key:  &ipv6KeyDataSource,
+						Cert: &ipv6CertDataSource,
+					},
+				},
+				expectedErr:        nil,
+				expectHTTPListener: true,
+				expectTLSListener:  true,
+			}),
+			Entry("with an ipv6 invalid bind address scheme", &newServerTableInput{
+				opts: Opts{
+					Handler:     handler,
+					BindAddress: "invalid://[::1]:0",
+				},
+				expectedErr:        errors.New("error setting up listener: listen (invalid, [::1]:0) failed: listen invalid: unknown network invalid"),
+				expectHTTPListener: false,
+				expectTLSListener:  false,
+			}),
+			Entry("with an ipv6 invalid secure bind address scheme", &newServerTableInput{
+				opts: Opts{
+					Handler:           handler,
+					SecureBindAddress: "invalid://[::1]:0",
+					TLS: &options.TLS{
+						Key:  &ipv6KeyDataSource,
+						Cert: &ipv6CertDataSource,
+					},
+				},
+				expectedErr:        nil,
+				expectHTTPListener: false,
+				expectTLSListener:  true,
+			}),
+			Entry("with an ipv6 invalid bind address port", &newServerTableInput{
+				opts: Opts{
+					Handler:     handler,
+					BindAddress: "[::1]:a",
+				},
+				expectedErr:        errors.New("error setting up listener: listen (tcp, [::1]:a) failed: listen tcp: "),
+				expectHTTPListener: false,
+				expectTLSListener:  false,
+			}),
+			Entry("with an ipv6 invalid secure bind address port", &newServerTableInput{
+				opts: Opts{
+					Handler:           handler,
+					SecureBindAddress: "[::1]:a",
+					TLS: &options.TLS{
+						Key:  &ipv6KeyDataSource,
+						Cert: &ipv6CertDataSource,
+					},
+				},
+				expectedErr:        errors.New("error setting up TLS listener: listen ([::1]:a) failed: listen tcp: "),
+				expectHTTPListener: false,
+				expectTLSListener:  false,
+			}),
+			Entry("with an ipv6 invalid TLS key", &newServerTableInput{
+				opts: Opts{
+					Handler:           handler,
+					SecureBindAddress: "[::1]:0",
+					TLS: &options.TLS{
+						Key: &options.SecretSource{
+							Value: []byte("invalid"),
+						},
+						Cert: &ipv6CertDataSource,
+					},
+				},
+				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"),
+				expectHTTPListener: false,
+				expectTLSListener:  false,
+			}),
+			Entry("with an ipv6 invalid TLS cert", &newServerTableInput{
+				opts: Opts{
+					Handler:           handler,
+					SecureBindAddress: "[::1]:0",
+					TLS: &options.TLS{
+						Key: &ipv6KeyDataSource,
+						Cert: &options.SecretSource{
+							Value: []byte("invalid"),
+						},
+					},
+				},
+				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"),
+				expectHTTPListener: false,
+				expectTLSListener:  false,
+			}),
+			Entry("with an ipv6 address, with no TLS key", &newServerTableInput{
+				opts: Opts{
+					Handler:           handler,
+					SecureBindAddress: "[::1]:0",
+					TLS: &options.TLS{
+						Cert: &ipv6CertDataSource,
+					},
+				},
+				expectedErr:        errors.New("error setting up TLS listener: could not load certificate: could not load key data: no configuration provided"),
+				expectHTTPListener: false,
+				expectTLSListener:  false,
+			}),
+			Entry("with an ipv6 address, with no TLS cert", &newServerTableInput{
+				opts: Opts{
+					Handler:           handler,
+					SecureBindAddress: "[::1]:0",
+					TLS: &options.TLS{
+						Key: &ipv6KeyDataSource,
+					},
+				},
+				expectedErr:        errors.New("error setting up TLS listener: could not load certificate: could not load cert data: no configuration provided"),
+				expectHTTPListener: false,
+				expectTLSListener:  false,
+			}),
+			Entry("when the ipv6 bind address is prefixed with the http scheme", &newServerTableInput{
+				opts: Opts{
+					Handler:     handler,
+					BindAddress: "http://[::1]:0",
+				},
+				expectedErr:        nil,
+				expectHTTPListener: true,
+				expectTLSListener:  false,
+			}),
+			Entry("when the ipv6 secure bind address is prefixed with the https scheme", &newServerTableInput{
+				opts: Opts{
+					Handler:           handler,
+					SecureBindAddress: "https://[::1]:0",
+					TLS: &options.TLS{
+						Key:  &ipv6KeyDataSource,
+						Cert: &ipv6CertDataSource,
+					},
+				},
+				expectedErr:        nil,
+				expectHTTPListener: false,
+				expectTLSListener:  true,
+			}),
+			Entry("with an ipv6 valid https bind address, and valid TLS config with MinVersion", &newServerTableInput{
+				opts: Opts{
+					Handler:           handler,
+					SecureBindAddress: "[::1]:0",
+					TLS: &options.TLS{
+						Key:        &ipv6KeyDataSource,
+						Cert:       &ipv6CertDataSource,
+						MinVersion: "TLS1.3",
+					},
+				},
+				expectedErr:        nil,
+				expectHTTPListener: false,
+				expectTLSListener:  true,
+			}),
+			Entry("with an ipv6 valid https bind address, and invalid TLS config with unknown MinVersion", &newServerTableInput{
+				opts: Opts{
+					Handler:           handler,
+					SecureBindAddress: "[::1]:0",
+					TLS: &options.TLS{
+						Key:        &ipv6KeyDataSource,
+						Cert:       &ipv6CertDataSource,
 						MinVersion: "TLS1.42",
 					},
 				},
@@ -277,7 +470,7 @@ var _ = Describe("Server", func() {
 			cancel()
 		})
 
-		Context("with an http server", func() {
+		Context("with an ipv4 http server", func() {
 			var listenAddr string
 
 			BeforeEach(func() {
@@ -327,7 +520,7 @@ var _ = Describe("Server", func() {
 			})
 		})
 
-		Context("with an https server", func() {
+		Context("with an ipv4 https server", func() {
 			var secureListenAddr string
 
 			BeforeEach(func() {
@@ -336,8 +529,8 @@ var _ = Describe("Server", func() {
 					Handler:           handler,
 					SecureBindAddress: "127.0.0.1:0",
 					TLS: &options.TLS{
-						Key:  &keyDataSource,
-						Cert: &certDataSource,
+						Key:  &ipv4KeyDataSource,
+						Cert: &ipv4CertDataSource,
 					},
 				})
 				Expect(err).ToNot(HaveOccurred())
@@ -392,11 +585,11 @@ var _ = Describe("Server", func() {
 
 				Expect(resp.TLS.VerifiedChains).Should(HaveLen(1))
 				Expect(resp.TLS.VerifiedChains[0]).Should(HaveLen(1))
-				Expect(resp.TLS.VerifiedChains[0][0].Raw).Should(Equal(certData))
+				Expect(resp.TLS.VerifiedChains[0][0].Raw).Should(Equal(ipv4CertData))
 			})
 		})
 
-		Context("with both an http and an https server", func() {
+		Context("with both an ipv4 http and an ipv4 https server", func() {
 			var listenAddr, secureListenAddr string
 
 			BeforeEach(func() {
@@ -406,8 +599,204 @@ var _ = Describe("Server", func() {
 					BindAddress:       "127.0.0.1:0",
 					SecureBindAddress: "127.0.0.1:0",
 					TLS: &options.TLS{
-						Key:  &keyDataSource,
-						Cert: &certDataSource,
+						Key:  &ipv4KeyDataSource,
+						Cert: &ipv4CertDataSource,
+					},
+				})
+				Expect(err).ToNot(HaveOccurred())
+
+				s, ok := srv.(*server)
+				Expect(ok).To(BeTrue())
+
+				listenAddr = fmt.Sprintf("http://%s/", s.listener.Addr().String())
+				secureListenAddr = fmt.Sprintf("https://%s/", s.tlsListener.Addr().String())
+			})
+
+			It("Starts the server and serves the handler on http", func() {
+				go func() {
+					defer GinkgoRecover()
+					Expect(srv.Start(ctx)).To(Succeed())
+				}()
+
+				resp, err := client.Get(listenAddr)
+				Expect(err).ToNot(HaveOccurred())
+				Expect(resp.StatusCode).To(Equal(http.StatusOK))
+
+				body, err := ioutil.ReadAll(resp.Body)
+				Expect(err).ToNot(HaveOccurred())
+				Expect(string(body)).To(Equal(hello))
+			})
+
+			It("Starts the server and serves the handler on https", func() {
+				go func() {
+					defer GinkgoRecover()
+					Expect(srv.Start(ctx)).To(Succeed())
+				}()
+
+				resp, err := client.Get(secureListenAddr)
+				Expect(err).ToNot(HaveOccurred())
+				Expect(resp.StatusCode).To(Equal(http.StatusOK))
+
+				body, err := ioutil.ReadAll(resp.Body)
+				Expect(err).ToNot(HaveOccurred())
+				Expect(string(body)).To(Equal(hello))
+			})
+
+			It("Stops both servers when the context is cancelled", func() {
+				go func() {
+					defer GinkgoRecover()
+					Expect(srv.Start(ctx)).To(Succeed())
+				}()
+
+				_, err := client.Get(listenAddr)
+				Expect(err).ToNot(HaveOccurred())
+				_, err = client.Get(secureListenAddr)
+				Expect(err).ToNot(HaveOccurred())
+
+				cancel()
+
+				Eventually(func() error {
+					_, err := client.Get(listenAddr)
+					return err
+				}).Should(HaveOccurred())
+				Eventually(func() error {
+					_, err := client.Get(secureListenAddr)
+					return err
+				}).Should(HaveOccurred())
+			})
+		})
+
+		Context("with an ipv6 http server", func() {
+			var listenAddr string
+
+			BeforeEach(func() {
+				var err error
+				srv, err = NewServer(Opts{
+					Handler:     handler,
+					BindAddress: "[::1]:0",
+				})
+				Expect(err).ToNot(HaveOccurred())
+
+				s, ok := srv.(*server)
+				Expect(ok).To(BeTrue())
+
+				listenAddr = fmt.Sprintf("http://%s/", s.listener.Addr().String())
+			})
+
+			It("Starts the server and serves the handler", func() {
+				go func() {
+					defer GinkgoRecover()
+					Expect(srv.Start(ctx)).To(Succeed())
+				}()
+
+				resp, err := client.Get(listenAddr)
+				Expect(err).ToNot(HaveOccurred())
+				Expect(resp.StatusCode).To(Equal(http.StatusOK))
+
+				body, err := ioutil.ReadAll(resp.Body)
+				Expect(err).ToNot(HaveOccurred())
+				Expect(string(body)).To(Equal(hello))
+			})
+
+			It("Stops the server when the context is cancelled", func() {
+				go func() {
+					defer GinkgoRecover()
+					Expect(srv.Start(ctx)).To(Succeed())
+				}()
+
+				_, err := client.Get(listenAddr)
+				Expect(err).ToNot(HaveOccurred())
+
+				cancel()
+
+				Eventually(func() error {
+					_, err := client.Get(listenAddr)
+					return err
+				}).Should(HaveOccurred())
+			})
+		})
+
+		Context("with an ipv6 https server", func() {
+			var secureListenAddr string
+
+			BeforeEach(func() {
+				var err error
+				srv, err = NewServer(Opts{
+					Handler:           handler,
+					SecureBindAddress: "[::1]:0",
+					TLS: &options.TLS{
+						Key:  &ipv6KeyDataSource,
+						Cert: &ipv6CertDataSource,
+					},
+				})
+				Expect(err).ToNot(HaveOccurred())
+
+				s, ok := srv.(*server)
+				Expect(ok).To(BeTrue())
+
+				secureListenAddr = fmt.Sprintf("https://%s/", s.tlsListener.Addr().String())
+			})
+
+			It("Starts the server and serves the handler", func() {
+				go func() {
+					defer GinkgoRecover()
+					Expect(srv.Start(ctx)).To(Succeed())
+				}()
+
+				resp, err := client.Get(secureListenAddr)
+				Expect(err).ToNot(HaveOccurred())
+				Expect(resp.StatusCode).To(Equal(http.StatusOK))
+
+				body, err := ioutil.ReadAll(resp.Body)
+				Expect(err).ToNot(HaveOccurred())
+				Expect(string(body)).To(Equal(hello))
+			})
+
+			It("Stops the server when the context is cancelled", func() {
+				go func() {
+					defer GinkgoRecover()
+					Expect(srv.Start(ctx)).To(Succeed())
+				}()
+
+				_, err := client.Get(secureListenAddr)
+				Expect(err).ToNot(HaveOccurred())
+
+				cancel()
+
+				Eventually(func() error {
+					_, err := client.Get(secureListenAddr)
+					return err
+				}).Should(HaveOccurred())
+			})
+
+			It("Serves the certificate provided", func() {
+				go func() {
+					defer GinkgoRecover()
+					Expect(srv.Start(ctx)).To(Succeed())
+				}()
+
+				resp, err := client.Get(secureListenAddr)
+				Expect(err).ToNot(HaveOccurred())
+				Expect(resp.StatusCode).To(Equal(http.StatusOK))
+
+				Expect(resp.TLS.VerifiedChains).Should(HaveLen(1))
+				Expect(resp.TLS.VerifiedChains[0]).Should(HaveLen(1))
+				Expect(resp.TLS.VerifiedChains[0][0].Raw).Should(Equal(ipv6CertData))
+			})
+		})
+
+		Context("with both an ipv6 http and an ipv6 https server", func() {
+			var listenAddr, secureListenAddr string
+
+			BeforeEach(func() {
+				var err error
+				srv, err = NewServer(Opts{
+					Handler:           handler,
+					BindAddress:       "[::1]:0",
+					SecureBindAddress: "[::1]:0",
+					TLS: &options.TLS{
+						Key:  &ipv6KeyDataSource,
+						Cert: &ipv6CertDataSource,
 					},
 				})
 				Expect(err).ToNot(HaveOccurred())
@@ -478,11 +867,16 @@ var _ = Describe("Server", func() {
 		DescribeTable("should return the scheme", func(in, expected string) {
 			Expect(getNetworkScheme(in)).To(Equal(expected))
 		},
-			Entry("with no scheme", "127.0.0.1:0", "tcp"),
-			Entry("with a tcp scheme", "tcp://127.0.0.1:0", "tcp"),
-			Entry("with a http scheme", "http://192.168.0.1:1", "tcp"),
-			Entry("with a unix scheme", "unix://172.168.16.2:2", "unix"),
-			Entry("with a random scheme", "random://10.10.10.10:10", "random"),
+			Entry("ipv4 address with no scheme", "127.0.0.1:0", "tcp"),
+			Entry("ipv4 address with a tcp scheme", "tcp://127.0.0.1:0", "tcp"),
+			Entry("ipv4 address with a http scheme", "http://192.168.0.1:1", "tcp"),
+			Entry("ipv4 address with a unix scheme", "unix://172.168.16.2:2", "unix"),
+			Entry("ipv4 address with a random scheme", "random://10.10.10.10:10", "random"),
+			Entry("ipv6 address with no scheme", "[::1]:0", "tcp"),
+			Entry("ipv6 address with a tcp scheme", "tcp://[::1]:0", "tcp"),
+			Entry("ipv6 address with a http scheme", "http://[::ffff:c0a8:1]:1", "tcp"),
+			Entry("ipv6 address with a unix scheme", "unix://[::ffff:aca8:1002]:2", "unix"),
+			Entry("ipv6 address with a random scheme", "random://[::ffff:a0a:a0a]:10", "random"),
 		)
 	})
 
@@ -490,11 +884,16 @@ var _ = Describe("Server", func() {
 		DescribeTable("should remove the scheme", func(in, expected string) {
 			Expect(getListenAddress(in)).To(Equal(expected))
 		},
-			Entry("with no scheme", "127.0.0.1:0", "127.0.0.1:0"),
-			Entry("with a tcp scheme", "tcp://127.0.0.1:0", "127.0.0.1:0"),
-			Entry("with a http scheme", "http://192.168.0.1:1", "192.168.0.1:1"),
-			Entry("with a unix scheme", "unix://172.168.16.2:2", "172.168.16.2:2"),
-			Entry("with a random scheme", "random://10.10.10.10:10", "10.10.10.10:10"),
+			Entry("ipv4 address with no scheme", "127.0.0.1:0", "127.0.0.1:0"),
+			Entry("ipv4 address with a tcp scheme", "tcp://127.0.0.1:0", "127.0.0.1:0"),
+			Entry("ipv4 address with a http scheme", "http://192.168.0.1:1", "192.168.0.1:1"),
+			Entry("ipv4 address with a unix scheme", "unix://172.168.16.2:2", "172.168.16.2:2"),
+			Entry("ipv4 address with a random scheme", "random://10.10.10.10:10", "10.10.10.10:10"),
+			Entry("ipv6 address with no scheme", "[::1]:0", "[::1]:0"),
+			Entry("ipv6 address with a tcp scheme", "tcp://[::1]:0", "[::1]:0"),
+			Entry("ipv6 address with a http scheme", "http://[::ffff:c0a8:1]:1", "[::ffff:c0a8:1]:1"),
+			Entry("ipv6 address with a unix scheme", "unix://[::ffff:aca8:1002]:2", "[::ffff:aca8:1002]:2"),
+			Entry("ipv6 address with a random scheme", "random://[::ffff:a0a:a0a]:10", "[::ffff:a0a:a0a]:10"),
 		)
 	})
 })
diff --git a/pkg/sessions/redis/redis_store_test.go b/pkg/sessions/redis/redis_store_test.go
index f5ced7e2..9f0bd0a9 100644
--- a/pkg/sessions/redis/redis_store_test.go
+++ b/pkg/sessions/redis/redis_store_test.go
@@ -54,7 +54,7 @@ var (
 
 var _ = BeforeSuite(func() {
 	var err error
-	certBytes, keyBytes, err := util.GenerateCert()
+	certBytes, keyBytes, err := util.GenerateCert("127.0.0.1")
 	Expect(err).ToNot(HaveOccurred())
 	certOut := new(bytes.Buffer)
 	Expect(pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})).To(Succeed())
diff --git a/providers/github_test.go b/providers/github_test.go
index 6ef31979..28ab2baf 100644
--- a/providers/github_test.go
+++ b/providers/github_test.go
@@ -34,7 +34,7 @@ func testGitHubProvider(hostname string, opts options.GitHubOptions) *GitHubProv
 
 func testGitHubBackend(payloads map[string][]string) *httptest.Server {
 	pathToQueryMap := map[string][]string{
-		"/repo/oauth2-proxy/oauth2-proxy":                       {""},
+		"/repos/oauth2-proxy/oauth2-proxy":                      {""},
 		"/repos/oauth2-proxy/oauth2-proxy/collaborators/mbland": {""},
 		"/user":        {""},
 		"/user/emails": {""},
@@ -229,7 +229,7 @@ func TestGitHubProvider_getEmailWithWriteAccessToPrivateRepo(t *testing.T) {
 
 func TestGitHubProvider_getEmailWithNoAccessToPrivateRepo(t *testing.T) {
 	b := testGitHubBackend(map[string][]string{
-		"/repo/oauth2-proxy/oauth2-proxy": {`{}`},
+		"/repos/oauth2-proxy/oauth2-proxy": {`{}`},
 	})
 	defer b.Close()