git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add DiscoveryProvider to perform OIDC discovery
commit3bb9621f5de4d32e95877ed0337b1c1947aa99c5 authorJoel Speed <joel.speed@hotmail.co.uk> dateWed 16 Feb 2022, 13:25 (5 years ago) parented38922
4 files changed+208−3
go.mod+3−1 go.sum+10−2 pkg/providers/oidc/provider.go+81 pkg/providers/oidc/provider_test.go+114
go.mod+3−1
@@ -9,6 +9,7 @@ require (
99 github.com/bitly/go-simplejson v0.5.0
1010 github.com/bsm/redislock v0.7.0
1111 github.com/coreos/go-oidc/v3 v3.0.0
12+ github.com/dgrijalva/jwt-go v3.2.0+incompatible
1213 github.com/fsnotify/fsnotify v1.4.9
1314 github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
1415 github.com/go-redis/redis/v8 v8.2.3
@@ -18,6 +19,7 @@ require (
1819 github.com/justinas/alice v1.2.0
1920 github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
2021 github.com/mitchellh/mapstructure v1.1.2
22+ github.com/oauth2-proxy/mockoidc v0.0.0-20210703044157-382d3faf2671
2123 github.com/oauth2-proxy/tools/reference-gen v0.0.0-20210118095127-56ffd7384404
2224 github.com/onsi/ginkgo v1.14.1
2325 github.com/onsi/gomega v1.10.2
@@ -26,7 +28,7 @@ require (
2628 github.com/spf13/cast v1.3.0
2729 github.com/spf13/pflag v1.0.5
2830 github.com/spf13/viper v1.6.3
29− github.com/stretchr/testify v1.6.1
31+ github.com/stretchr/testify v1.7.0
3032 github.com/vmihailenco/msgpack/v4 v4.3.11
3133 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
3234 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
go.sum+10−2
@@ -81,6 +81,7 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
8181github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8282github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
8383github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
84+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
8485github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
8586github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
8687github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
@@ -169,8 +170,9 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
169170github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
170171github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
171172github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
172−github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
173173github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
174+github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
175+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
174176github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
175177github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
176178github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@@ -293,6 +295,8 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
293295github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
294296github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
295297github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
298+github.com/oauth2-proxy/mockoidc v0.0.0-20210703044157-382d3faf2671 h1:rCw45DEDqAy46HsVu2WZQgEqKth79c8k6HZP2uPDTls=
299+github.com/oauth2-proxy/mockoidc v0.0.0-20210703044157-382d3faf2671/go.mod h1:ejAP84CdQJiV7GcnIF0wNhbhcWsxew0qXV04CB+r+Mw=
296300github.com/oauth2-proxy/tools/reference-gen v0.0.0-20210118095127-56ffd7384404 h1:ZpzR4Ou1nhldBG/vEzauoqyaUlofaUcLkv1C/gBK8ls=
297301github.com/oauth2-proxy/tools/reference-gen v0.0.0-20210118095127-56ffd7384404/go.mod h1:YpORG8zs14vNlpXvuHYnnDvWazIRaDk02MaY8lafqdI=
298302github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
@@ -412,8 +416,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
412416github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
413417github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
414418github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
415−github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
416419github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
420+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
421+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
417422github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
418423github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
419424github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@@ -456,6 +461,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
456461golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
457462golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
458463golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
464+golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
459465golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
460466golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
461467golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -535,6 +541,7 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w
535541golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
536542golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
537543golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
544+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
538545golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
539546golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
540547golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -547,6 +554,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
547554golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
548555golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
549556golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
557+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
550558golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
551559golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
552560golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
pkg/providers/oidc/provider.gonew file+81
@@ -0,0 +1,81 @@
1+package oidc
2+
3+import (
4+ "context"
5+ "fmt"
6+ "strings"
7+
8+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
9+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
10+)
11+
12+// providerJSON resresents the information we need from an OIDC discovery
13+type providerJSON struct {
14+ Issuer string `json:"issuer"`
15+ AuthURL string `json:"authorization_endpoint"`
16+ TokenURL string `json:"token_endpoint"`
17+ JWKsURL string `json:"jwks_uri"`
18+ UserInfoURL string `json:"userinfo_endpoint"`
19+}
20+
21+// Endpoints represents the endpoints discovered as part of the OIDC discovery process
22+// that will be used by the authentication providers.
23+type Endpoints struct {
24+ AuthURL string
25+ TokenURL string
26+ JWKsURL string
27+ UserInfoURL string
28+}
29+
30+// DiscoveryProvider holds information about an identity provider having
31+// used OIDC discovery to retrieve the information.
32+type DiscoveryProvider interface {
33+ Endpoints() Endpoints
34+}
35+
36+// NewProvider allows a user to perform an OIDC discovery and returns the DiscoveryProvider.
37+// We implement this here as opposed to using oidc.Provider so that we can override the Issuer verification check.
38+// As we have our own verifier and fetch the userinfo separately, the rest of the oidc.Provider implementation is not
39+// useful to us.
40+func NewProvider(ctx context.Context, issuerURL string, skipIssuerVerification bool) (DiscoveryProvider, error) {
41+ // go-oidc doesn't let us pass bypass the issuer check this in the oidc.NewProvider call
42+ // (which uses discovery to get the URLs), so we'll do a quick check ourselves and if
43+ // we get the URLs, we'll just use the non-discovery path.
44+
45+ logger.Printf("Performing OIDC Discovery...")
46+
47+ var p providerJSON
48+ requestURL := strings.TrimSuffix(issuerURL, "/") + "/.well-known/openid-configuration"
49+ if err := requests.New(requestURL).WithContext(ctx).Do().UnmarshalInto(&p); err != nil {
50+ return nil, fmt.Errorf("failed to discover OIDC configuration: %v", err)
51+ }
52+
53+ if !skipIssuerVerification && p.Issuer != issuerURL {
54+ return nil, fmt.Errorf("oidc: issuer did not match the issuer returned by provider, expected %q got %q", issuerURL, p.Issuer)
55+ }
56+
57+ return &discoveryProvider{
58+ authURL: p.AuthURL,
59+ tokenURL: p.TokenURL,
60+ jwksURL: p.JWKsURL,
61+ userInfoURL: p.UserInfoURL,
62+ }, nil
63+}
64+
65+// discoveryProvider holds the discovered endpoints
66+type discoveryProvider struct {
67+ authURL string
68+ tokenURL string
69+ jwksURL string
70+ userInfoURL string
71+}
72+
73+// Endpoints returns the discovered endpoints needed for an authentication provider.
74+func (p *discoveryProvider) Endpoints() Endpoints {
75+ return Endpoints{
76+ AuthURL: p.authURL,
77+ TokenURL: p.tokenURL,
78+ JWKsURL: p.jwksURL,
79+ UserInfoURL: p.userInfoURL,
80+ }
81+}
pkg/providers/oidc/provider_test.gonew file+114
@@ -0,0 +1,114 @@
1+package oidc
2+
3+import (
4+ "context"
5+ "encoding/json"
6+ "net"
7+ "net/http"
8+
9+ "github.com/oauth2-proxy/mockoidc"
10+ . "github.com/onsi/ginkgo"
11+ . "github.com/onsi/ginkgo/extensions/table"
12+ . "github.com/onsi/gomega"
13+)
14+
15+var _ = Describe("Provider", func() {
16+ type newProviderTableInput struct {
17+ skipIssuerVerification bool
18+ expectedError string
19+ middlewares func(*mockoidc.MockOIDC) []func(http.Handler) http.Handler
20+ }
21+
22+ DescribeTable("NewProvider", func(in *newProviderTableInput) {
23+ m, err := mockoidc.NewServer(nil)
24+ Expect(err).ToNot(HaveOccurred())
25+
26+ if in.middlewares != nil {
27+ middlewares := in.middlewares(m)
28+ for _, middlware := range middlewares {
29+ m.AddMiddleware(middlware)
30+ }
31+ }
32+
33+ ln, err := net.Listen("tcp", "127.0.0.1:0")
34+ Expect(err).ToNot(HaveOccurred())
35+
36+ Expect(m.Start(ln, nil)).To(Succeed())
37+ defer func() {
38+ Expect(m.Shutdown()).To(Succeed())
39+ }()
40+
41+ provider, err := NewProvider(context.Background(), m.Issuer(), in.skipIssuerVerification)
42+ if in.expectedError != "" {
43+ Expect(err).To(MatchError(HavePrefix(in.expectedError)))
44+ return
45+ }
46+ Expect(err).ToNot(HaveOccurred())
47+
48+ endpoints := provider.Endpoints()
49+ Expect(endpoints.AuthURL).To(Equal(m.AuthorizationEndpoint()))
50+ Expect(endpoints.TokenURL).To(Equal(m.TokenEndpoint()))
51+ Expect(endpoints.JWKsURL).To(Equal(m.JWKSEndpoint()))
52+ Expect(endpoints.UserInfoURL).To(Equal(m.UserinfoEndpoint()))
53+ },
54+ Entry("with issuer verification and the issuer matches", &newProviderTableInput{
55+ skipIssuerVerification: false,
56+ }),
57+ Entry("with skip issuer verification and the issuer matches", &newProviderTableInput{
58+ skipIssuerVerification: true,
59+ }),
60+ Entry("with issuer verification and an invalid issuer", &newProviderTableInput{
61+ skipIssuerVerification: false,
62+ middlewares: func(m *mockoidc.MockOIDC) []func(http.Handler) http.Handler {
63+ return []func(http.Handler) http.Handler{
64+ newInvalidIssuerMiddleware(m),
65+ }
66+ },
67+ expectedError: "oidc: issuer did not match the issuer returned by provider",
68+ }),
69+ Entry("with skip issuer verification and an invalid issuer", &newProviderTableInput{
70+ skipIssuerVerification: true,
71+ middlewares: func(m *mockoidc.MockOIDC) []func(http.Handler) http.Handler {
72+ return []func(http.Handler) http.Handler{
73+ newInvalidIssuerMiddleware(m),
74+ }
75+ },
76+ }),
77+ Entry("when the issuer returns a bad response", &newProviderTableInput{
78+ skipIssuerVerification: false,
79+ middlewares: func(m *mockoidc.MockOIDC) []func(http.Handler) http.Handler {
80+ return []func(http.Handler) http.Handler{
81+ newBadRequestMiddleware(),
82+ }
83+ },
84+ expectedError: "failed to discover OIDC configuration: unexpected status \"400\"",
85+ }),
86+ )
87+})
88+
89+func newInvalidIssuerMiddleware(m *mockoidc.MockOIDC) func(http.Handler) http.Handler {
90+ return func(next http.Handler) http.Handler {
91+ return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
92+ p := providerJSON{
93+ Issuer: "invalid",
94+ AuthURL: m.AuthorizationEndpoint(),
95+ TokenURL: m.TokenEndpoint(),
96+ JWKsURL: m.JWKSEndpoint(),
97+ UserInfoURL: m.UserinfoEndpoint(),
98+ }
99+ data, err := json.Marshal(p)
100+ if err != nil {
101+ rw.WriteHeader(500)
102+ }
103+ rw.Write(data)
104+ })
105+ }
106+}
107+
108+func newBadRequestMiddleware() func(http.Handler) http.Handler {
109+ return func(next http.Handler) http.Handler {
110+ return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
111+ rw.WriteHeader(400)
112+ })
113+ }
114+}