mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-03-31 22:21:57 +02:00
Merge pull request #1559 from oauth2-proxy/refactor-oidc-verifier
Introduce ProviderVerifier to clean up OIDC discovery code
This commit is contained in:
commit
b547fe0b37
@ -8,6 +8,7 @@
|
||||
|
||||
## Changes since v7.2.1
|
||||
|
||||
- [#1559](https://github.com/oauth2-proxy/oauth2-proxy/pull/1559) Introduce ProviderVerifier to clean up OIDC discovery code (@JoelSpeed)
|
||||
- [#1561](https://github.com/oauth2-proxy/oauth2-proxy/pull/1561) Add ppc64le support (@mgiessing)
|
||||
- [#1563](https://github.com/oauth2-proxy/oauth2-proxy/pull/1563) Ensure claim extractor does not attempt profile call when URL is empty (@JoelSpeed)
|
||||
- [#1560](https://github.com/oauth2-proxy/oauth2-proxy/pull/1560) Fix provider data initialisation (@JoelSpeed)
|
||||
|
4
go.mod
4
go.mod
@ -9,6 +9,7 @@ require (
|
||||
github.com/bitly/go-simplejson v0.5.0
|
||||
github.com/bsm/redislock v0.7.0
|
||||
github.com/coreos/go-oidc/v3 v3.0.0
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
|
||||
github.com/go-redis/redis/v8 v8.2.3
|
||||
@ -18,6 +19,7 @@ require (
|
||||
github.com/justinas/alice v1.2.0
|
||||
github.com/mbland/hmacauth v0.0.0-20170912233209-44256dfd4bfa
|
||||
github.com/mitchellh/mapstructure v1.1.2
|
||||
github.com/oauth2-proxy/mockoidc v0.0.0-20210703044157-382d3faf2671
|
||||
github.com/oauth2-proxy/tools/reference-gen v0.0.0-20210118095127-56ffd7384404
|
||||
github.com/onsi/ginkgo v1.14.1
|
||||
github.com/onsi/gomega v1.10.2
|
||||
@ -26,7 +28,7 @@ require (
|
||||
github.com/spf13/cast v1.3.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.6.3
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/vmihailenco/msgpack/v4 v4.3.11
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
|
||||
|
12
go.sum
12
go.sum
@ -81,6 +81,7 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.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
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.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
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/oauth2-proxy/mockoidc v0.0.0-20210703044157-382d3faf2671 h1:rCw45DEDqAy46HsVu2WZQgEqKth79c8k6HZP2uPDTls=
|
||||
github.com/oauth2-proxy/mockoidc v0.0.0-20210703044157-382d3faf2671/go.mod h1:ejAP84CdQJiV7GcnIF0wNhbhcWsxew0qXV04CB+r+Mw=
|
||||
github.com/oauth2-proxy/tools/reference-gen v0.0.0-20210118095127-56ffd7384404 h1:ZpzR4Ou1nhldBG/vEzauoqyaUlofaUcLkv1C/gBK8ls=
|
||||
github.com/oauth2-proxy/tools/reference-gen v0.0.0-20210118095127-56ffd7384404/go.mod h1:YpORG8zs14vNlpXvuHYnnDvWazIRaDk02MaY8lafqdI=
|
||||
github.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
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.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
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.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
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.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
|
||||
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
|
||||
sessionscookie "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/cookie"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
|
||||
@ -1747,7 +1747,7 @@ func TestGetJwtSession(t *testing.T) {
|
||||
verifier := oidc.NewVerifier("https://issuer.example.com", keyset,
|
||||
&oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true,
|
||||
SkipClientIDCheck: true})
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
verificationOptions := internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: "https://test.myapp.com",
|
||||
ExtraAudiences: []string{},
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"net/url"
|
||||
|
||||
ipapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/ip"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
@ -68,26 +68,26 @@ type Options struct {
|
||||
// internal values that are set after config validation
|
||||
redirectURL *url.URL
|
||||
signatureData *SignatureData
|
||||
oidcVerifier *internaloidc.IDTokenVerifier
|
||||
jwtBearerVerifiers []*internaloidc.IDTokenVerifier
|
||||
oidcVerifier internaloidc.IDTokenVerifier
|
||||
jwtBearerVerifiers []internaloidc.IDTokenVerifier
|
||||
realClientIPParser ipapi.RealClientIPParser
|
||||
}
|
||||
|
||||
// Options for Getting internal values
|
||||
func (o *Options) GetRedirectURL() *url.URL { return o.redirectURL }
|
||||
func (o *Options) GetSignatureData() *SignatureData { return o.signatureData }
|
||||
func (o *Options) GetOIDCVerifier() *internaloidc.IDTokenVerifier { return o.oidcVerifier }
|
||||
func (o *Options) GetJWTBearerVerifiers() []*internaloidc.IDTokenVerifier {
|
||||
func (o *Options) GetRedirectURL() *url.URL { return o.redirectURL }
|
||||
func (o *Options) GetSignatureData() *SignatureData { return o.signatureData }
|
||||
func (o *Options) GetOIDCVerifier() internaloidc.IDTokenVerifier { return o.oidcVerifier }
|
||||
func (o *Options) GetJWTBearerVerifiers() []internaloidc.IDTokenVerifier {
|
||||
return o.jwtBearerVerifiers
|
||||
}
|
||||
func (o *Options) GetRealClientIPParser() ipapi.RealClientIPParser { return o.realClientIPParser }
|
||||
|
||||
// Options for Setting internal values
|
||||
func (o *Options) SetRedirectURL(s *url.URL) { o.redirectURL = s }
|
||||
func (o *Options) SetSignatureData(s *SignatureData) { o.signatureData = s }
|
||||
func (o *Options) SetOIDCVerifier(s *internaloidc.IDTokenVerifier) { o.oidcVerifier = s }
|
||||
func (o *Options) SetJWTBearerVerifiers(s []*internaloidc.IDTokenVerifier) { o.jwtBearerVerifiers = s }
|
||||
func (o *Options) SetRealClientIPParser(s ipapi.RealClientIPParser) { o.realClientIPParser = s }
|
||||
func (o *Options) SetRedirectURL(s *url.URL) { o.redirectURL = s }
|
||||
func (o *Options) SetSignatureData(s *SignatureData) { o.signatureData = s }
|
||||
func (o *Options) SetOIDCVerifier(s internaloidc.IDTokenVerifier) { o.oidcVerifier = s }
|
||||
func (o *Options) SetJWTBearerVerifiers(s []internaloidc.IDTokenVerifier) { o.jwtBearerVerifiers = s }
|
||||
func (o *Options) SetRealClientIPParser(s ipapi.RealClientIPParser) { o.realClientIPParser = s }
|
||||
|
||||
// NewOptions constructs a new Options with defaulted values
|
||||
func NewOptions() *Options {
|
||||
|
98
pkg/providers/oidc/provider.go
Normal file
98
pkg/providers/oidc/provider.go
Normal file
@ -0,0 +1,98 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
|
||||
)
|
||||
|
||||
// providerJSON resresents the information we need from an OIDC discovery
|
||||
type providerJSON struct {
|
||||
Issuer string `json:"issuer"`
|
||||
AuthURL string `json:"authorization_endpoint"`
|
||||
TokenURL string `json:"token_endpoint"`
|
||||
JWKsURL string `json:"jwks_uri"`
|
||||
UserInfoURL string `json:"userinfo_endpoint"`
|
||||
CodeChallengeAlgs []string `json:"code_challenge_methods_supported"`
|
||||
}
|
||||
|
||||
// Endpoints represents the endpoints discovered as part of the OIDC discovery process
|
||||
// that will be used by the authentication providers.
|
||||
type Endpoints struct {
|
||||
AuthURL string
|
||||
TokenURL string
|
||||
JWKsURL string
|
||||
UserInfoURL string
|
||||
}
|
||||
|
||||
// PKCE holds information relevant to the PKCE (code challenge) support of the
|
||||
// provider.
|
||||
type PKCE struct {
|
||||
CodeChallengeAlgs []string
|
||||
}
|
||||
|
||||
// DiscoveryProvider holds information about an identity provider having
|
||||
// used OIDC discovery to retrieve the information.
|
||||
type DiscoveryProvider interface {
|
||||
Endpoints() Endpoints
|
||||
PKCE() PKCE
|
||||
}
|
||||
|
||||
// NewProvider allows a user to perform an OIDC discovery and returns the DiscoveryProvider.
|
||||
// We implement this here as opposed to using oidc.Provider so that we can override the Issuer verification check.
|
||||
// As we have our own verifier and fetch the userinfo separately, the rest of the oidc.Provider implementation is not
|
||||
// useful to us.
|
||||
func NewProvider(ctx context.Context, issuerURL string, skipIssuerVerification bool) (DiscoveryProvider, error) {
|
||||
// go-oidc doesn't let us pass bypass the issuer check this in the oidc.NewProvider call
|
||||
// (which uses discovery to get the URLs), so we'll do a quick check ourselves and if
|
||||
// we get the URLs, we'll just use the non-discovery path.
|
||||
|
||||
logger.Printf("Performing OIDC Discovery...")
|
||||
|
||||
var p providerJSON
|
||||
requestURL := strings.TrimSuffix(issuerURL, "/") + "/.well-known/openid-configuration"
|
||||
if err := requests.New(requestURL).WithContext(ctx).Do().UnmarshalInto(&p); err != nil {
|
||||
return nil, fmt.Errorf("failed to discover OIDC configuration: %v", err)
|
||||
}
|
||||
|
||||
if !skipIssuerVerification && p.Issuer != issuerURL {
|
||||
return nil, fmt.Errorf("oidc: issuer did not match the issuer returned by provider, expected %q got %q", issuerURL, p.Issuer)
|
||||
}
|
||||
|
||||
return &discoveryProvider{
|
||||
authURL: p.AuthURL,
|
||||
tokenURL: p.TokenURL,
|
||||
jwksURL: p.JWKsURL,
|
||||
userInfoURL: p.UserInfoURL,
|
||||
codeChallengeAlgs: p.CodeChallengeAlgs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// discoveryProvider holds the discovered endpoints
|
||||
type discoveryProvider struct {
|
||||
authURL string
|
||||
tokenURL string
|
||||
jwksURL string
|
||||
userInfoURL string
|
||||
codeChallengeAlgs []string
|
||||
}
|
||||
|
||||
// Endpoints returns the discovered endpoints needed for an authentication provider.
|
||||
func (p *discoveryProvider) Endpoints() Endpoints {
|
||||
return Endpoints{
|
||||
AuthURL: p.authURL,
|
||||
TokenURL: p.tokenURL,
|
||||
JWKsURL: p.jwksURL,
|
||||
UserInfoURL: p.userInfoURL,
|
||||
}
|
||||
}
|
||||
|
||||
// PKCE returns information related to the PKCE (code challenge) support of the provider.
|
||||
func (p *discoveryProvider) PKCE() PKCE {
|
||||
return PKCE{
|
||||
CodeChallengeAlgs: p.codeChallengeAlgs,
|
||||
}
|
||||
}
|
153
pkg/providers/oidc/provider_test.go
Normal file
153
pkg/providers/oidc/provider_test.go
Normal file
@ -0,0 +1,153 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/oauth2-proxy/mockoidc"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Provider", func() {
|
||||
type newProviderTableInput struct {
|
||||
skipIssuerVerification bool
|
||||
expectedError string
|
||||
middlewares func(*mockoidc.MockOIDC) []func(http.Handler) http.Handler
|
||||
}
|
||||
|
||||
DescribeTable("NewProvider", func(in *newProviderTableInput) {
|
||||
m, err := mockoidc.NewServer(nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
if in.middlewares != nil {
|
||||
middlewares := in.middlewares(m)
|
||||
for _, middlware := range middlewares {
|
||||
m.AddMiddleware(middlware)
|
||||
}
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(m.Start(ln, nil)).To(Succeed())
|
||||
defer func() {
|
||||
Expect(m.Shutdown()).To(Succeed())
|
||||
}()
|
||||
|
||||
provider, err := NewProvider(context.Background(), m.Issuer(), in.skipIssuerVerification)
|
||||
if in.expectedError != "" {
|
||||
Expect(err).To(MatchError(HavePrefix(in.expectedError)))
|
||||
return
|
||||
}
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
endpoints := provider.Endpoints()
|
||||
Expect(endpoints.AuthURL).To(Equal(m.AuthorizationEndpoint()))
|
||||
Expect(endpoints.TokenURL).To(Equal(m.TokenEndpoint()))
|
||||
Expect(endpoints.JWKsURL).To(Equal(m.JWKSEndpoint()))
|
||||
Expect(endpoints.UserInfoURL).To(Equal(m.UserinfoEndpoint()))
|
||||
},
|
||||
Entry("with issuer verification and the issuer matches", &newProviderTableInput{
|
||||
skipIssuerVerification: false,
|
||||
}),
|
||||
Entry("with skip issuer verification and the issuer matches", &newProviderTableInput{
|
||||
skipIssuerVerification: true,
|
||||
}),
|
||||
Entry("with issuer verification and an invalid issuer", &newProviderTableInput{
|
||||
skipIssuerVerification: false,
|
||||
middlewares: func(m *mockoidc.MockOIDC) []func(http.Handler) http.Handler {
|
||||
return []func(http.Handler) http.Handler{
|
||||
newInvalidIssuerMiddleware(m),
|
||||
}
|
||||
},
|
||||
expectedError: "oidc: issuer did not match the issuer returned by provider",
|
||||
}),
|
||||
Entry("with skip issuer verification and an invalid issuer", &newProviderTableInput{
|
||||
skipIssuerVerification: true,
|
||||
middlewares: func(m *mockoidc.MockOIDC) []func(http.Handler) http.Handler {
|
||||
return []func(http.Handler) http.Handler{
|
||||
newInvalidIssuerMiddleware(m),
|
||||
}
|
||||
},
|
||||
}),
|
||||
Entry("when the issuer returns a bad response", &newProviderTableInput{
|
||||
skipIssuerVerification: false,
|
||||
middlewares: func(m *mockoidc.MockOIDC) []func(http.Handler) http.Handler {
|
||||
return []func(http.Handler) http.Handler{
|
||||
newBadRequestMiddleware(),
|
||||
}
|
||||
},
|
||||
expectedError: "failed to discover OIDC configuration: unexpected status \"400\"",
|
||||
}),
|
||||
)
|
||||
|
||||
It("with code challenges supported on the provider, shold populate PKCE information", func() {
|
||||
m, err := mockoidc.NewServer(nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
m.AddMiddleware(newCodeChallengeIssuerMiddleware(m))
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(m.Start(ln, nil)).To(Succeed())
|
||||
defer func() {
|
||||
Expect(m.Shutdown()).To(Succeed())
|
||||
}()
|
||||
|
||||
provider, err := NewProvider(context.Background(), m.Issuer(), false)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(provider.PKCE().CodeChallengeAlgs).To(ConsistOf("S256", "plain"))
|
||||
})
|
||||
})
|
||||
|
||||
func newInvalidIssuerMiddleware(m *mockoidc.MockOIDC) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
p := providerJSON{
|
||||
Issuer: "invalid",
|
||||
AuthURL: m.AuthorizationEndpoint(),
|
||||
TokenURL: m.TokenEndpoint(),
|
||||
JWKsURL: m.JWKSEndpoint(),
|
||||
UserInfoURL: m.UserinfoEndpoint(),
|
||||
}
|
||||
data, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
rw.WriteHeader(500)
|
||||
}
|
||||
rw.Write(data)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newCodeChallengeIssuerMiddleware(m *mockoidc.MockOIDC) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
p := providerJSON{
|
||||
Issuer: m.Issuer(),
|
||||
AuthURL: m.AuthorizationEndpoint(),
|
||||
TokenURL: m.TokenEndpoint(),
|
||||
JWKsURL: m.JWKSEndpoint(),
|
||||
UserInfoURL: m.UserinfoEndpoint(),
|
||||
CodeChallengeAlgs: []string{"S256", "plain"},
|
||||
}
|
||||
data, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
rw.WriteHeader(500)
|
||||
}
|
||||
rw.Write(data)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newBadRequestMiddleware() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(400)
|
||||
})
|
||||
}
|
||||
}
|
155
pkg/providers/oidc/provider_verifier.go
Normal file
155
pkg/providers/oidc/provider_verifier.go
Normal file
@ -0,0 +1,155 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
k8serrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
)
|
||||
|
||||
// ProviderVerifier represents the OIDC discovery and verification process
|
||||
type ProviderVerifier interface {
|
||||
DiscoveryEnabled() bool
|
||||
Provider() DiscoveryProvider
|
||||
Verifier() IDTokenVerifier
|
||||
}
|
||||
|
||||
// ProviderVerifierOptions allows you to configure a ProviderVerifier
|
||||
type ProviderVerifierOptions struct {
|
||||
// AudienceClaim allows to define any claim that is verified against the client id
|
||||
// By default `aud` claim is used for verification.
|
||||
AudienceClaims []string
|
||||
|
||||
// ClientID is the OAuth Client ID that is defined in the provider
|
||||
ClientID string
|
||||
|
||||
// ExtraAudiences is a list of additional audiences that are allowed
|
||||
// to pass verification in addition to the client id.
|
||||
ExtraAudiences []string
|
||||
|
||||
// IssuerURL is the OpenID Connect issuer URL
|
||||
// eg: https://accounts.google.com
|
||||
IssuerURL string
|
||||
|
||||
// JWKsURL is the OpenID Connect JWKS URL
|
||||
// eg: https://www.googleapis.com/oauth2/v3/certs
|
||||
JWKsURL string
|
||||
|
||||
// SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints
|
||||
SkipDiscovery bool
|
||||
|
||||
// SkipIssuerVerification skips verification of ID token issuers.
|
||||
// When false, ID Token Issuers must match the OIDC discovery URL.
|
||||
SkipIssuerVerification bool
|
||||
}
|
||||
|
||||
// validate checks that the required options are present before attempting to create
|
||||
// the ProviderVerifier.
|
||||
func (p ProviderVerifierOptions) validate() error {
|
||||
var errs []error
|
||||
|
||||
if p.IssuerURL == "" {
|
||||
errs = append(errs, errors.New("missing required setting: issuer-url"))
|
||||
}
|
||||
|
||||
if p.SkipDiscovery && p.JWKsURL == "" {
|
||||
errs = append(errs, errors.New("missing required setting: jwks-url"))
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return k8serrors.NewAggregate(errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// toVerificationOptions returns an IDTokenVerificationOptions based on the configured options.
|
||||
func (p ProviderVerifierOptions) toVerificationOptions() IDTokenVerificationOptions {
|
||||
return IDTokenVerificationOptions{
|
||||
AudienceClaims: p.AudienceClaims,
|
||||
ClientID: p.ClientID,
|
||||
ExtraAudiences: p.ExtraAudiences,
|
||||
}
|
||||
}
|
||||
|
||||
// toOIDCConfig returns an oidc.Config based on the configured options.
|
||||
func (p ProviderVerifierOptions) toOIDCConfig() *oidc.Config {
|
||||
return &oidc.Config{
|
||||
ClientID: p.ClientID,
|
||||
SkipIssuerCheck: p.SkipIssuerVerification,
|
||||
SkipClientIDCheck: true,
|
||||
}
|
||||
}
|
||||
|
||||
// NewProviderVerifier constructs a ProviderVerifier from the options given.
|
||||
func NewProviderVerifier(ctx context.Context, opts ProviderVerifierOptions) (ProviderVerifier, error) {
|
||||
if err := opts.validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid provider verifier options: %v", err)
|
||||
}
|
||||
|
||||
verifierBuilder, provider, err := getVerifierBuilder(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get verifier builder: %v", err)
|
||||
}
|
||||
verifier := NewVerifier(verifierBuilder(opts.toOIDCConfig()), opts.toVerificationOptions())
|
||||
|
||||
if provider == nil {
|
||||
// To avoid the possibility of nil pointers, always return an empty provider if discovery didn't occur.
|
||||
// Users are expected to check whether discovery was enabled before using the provider.
|
||||
provider = &discoveryProvider{}
|
||||
}
|
||||
|
||||
return &providerVerifier{
|
||||
discoveryEnabled: !opts.SkipDiscovery,
|
||||
provider: provider,
|
||||
verifier: verifier,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type verifierBuilder func(*oidc.Config) *oidc.IDTokenVerifier
|
||||
|
||||
func getVerifierBuilder(ctx context.Context, opts ProviderVerifierOptions) (verifierBuilder, DiscoveryProvider, error) {
|
||||
if opts.SkipDiscovery {
|
||||
// Instead of discovering the JWKs URK, it needs to be specified in the opts already
|
||||
return newVerifierBuilder(ctx, opts.IssuerURL, opts.JWKsURL), nil, nil
|
||||
}
|
||||
|
||||
provider, err := NewProvider(ctx, opts.IssuerURL, opts.SkipIssuerVerification)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error while discovery OIDC configuration: %v", err)
|
||||
}
|
||||
verifierBuilder := newVerifierBuilder(ctx, opts.IssuerURL, provider.Endpoints().JWKsURL)
|
||||
return verifierBuilder, provider, nil
|
||||
}
|
||||
|
||||
// newVerifierBuilder returns a function to create a IDToken verifier from an OIDC config.
|
||||
func newVerifierBuilder(ctx context.Context, issuerURL, jwksURL string) verifierBuilder {
|
||||
keySet := oidc.NewRemoteKeySet(ctx, jwksURL)
|
||||
return func(oidcConfig *oidc.Config) *oidc.IDTokenVerifier {
|
||||
return oidc.NewVerifier(issuerURL, keySet, oidcConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// providerVerifier is an implementation of the ProviderVerifier interface
|
||||
type providerVerifier struct {
|
||||
discoveryEnabled bool
|
||||
provider DiscoveryProvider
|
||||
verifier IDTokenVerifier
|
||||
}
|
||||
|
||||
// DiscoveryEnabled returns whether the provider verifier was constructed
|
||||
// using the OIDC discovery process or whether it was manually discovered.
|
||||
func (p *providerVerifier) DiscoveryEnabled() bool {
|
||||
return p.discoveryEnabled
|
||||
}
|
||||
|
||||
// Provider returns the OIDC discovery provider
|
||||
func (p *providerVerifier) Provider() DiscoveryProvider {
|
||||
return p.provider
|
||||
}
|
||||
|
||||
// Verifier returns the ID token verifier
|
||||
func (p *providerVerifier) Verifier() IDTokenVerifier {
|
||||
return p.verifier
|
||||
}
|
173
pkg/providers/oidc/provider_verifier_test.go
Normal file
173
pkg/providers/oidc/provider_verifier_test.go
Normal file
@ -0,0 +1,173 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/oauth2-proxy/mockoidc"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("ProviderVerifier", func() {
|
||||
var m *mockoidc.MockOIDC
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
m, err = mockoidc.Run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(m.Shutdown()).To(Succeed())
|
||||
})
|
||||
|
||||
type newProviderVerifierTableInput struct {
|
||||
modifyOpts func(*ProviderVerifierOptions)
|
||||
expectedError string
|
||||
}
|
||||
|
||||
DescribeTable("when constructing the provider verifier", func(in *newProviderVerifierTableInput) {
|
||||
opts := ProviderVerifierOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: m.Config().ClientID,
|
||||
ExtraAudiences: []string{},
|
||||
IssuerURL: m.Issuer(),
|
||||
}
|
||||
if in.modifyOpts != nil {
|
||||
in.modifyOpts(&opts)
|
||||
}
|
||||
|
||||
pv, err := NewProviderVerifier(context.Background(), opts)
|
||||
if in.expectedError != "" {
|
||||
Expect(err).To(MatchError(HavePrefix(in.expectedError)))
|
||||
return
|
||||
}
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(pv.DiscoveryEnabled()).ToNot(Equal(opts.SkipDiscovery), "DiscoveryEnabled should be the reverse of skip discovery")
|
||||
Expect(pv.Provider()).ToNot(BeNil())
|
||||
|
||||
if pv.DiscoveryEnabled() {
|
||||
endpoints := pv.Provider().Endpoints()
|
||||
Expect(endpoints.AuthURL).To(Equal(m.AuthorizationEndpoint()))
|
||||
Expect(endpoints.TokenURL).To(Equal(m.TokenEndpoint()))
|
||||
Expect(endpoints.JWKsURL).To(Equal(m.JWKSEndpoint()))
|
||||
Expect(endpoints.UserInfoURL).To(Equal(m.UserinfoEndpoint()))
|
||||
}
|
||||
},
|
||||
Entry("should be succesfful when discovering the OIDC provider", &newProviderVerifierTableInput{
|
||||
modifyOpts: func(_ *ProviderVerifierOptions) {},
|
||||
}),
|
||||
Entry("when the issuer URL is missing", &newProviderVerifierTableInput{
|
||||
modifyOpts: func(p *ProviderVerifierOptions) {
|
||||
p.IssuerURL = ""
|
||||
},
|
||||
expectedError: "invalid provider verifier options: missing required setting: issuer-url",
|
||||
}),
|
||||
Entry("when the issuer URL is invalid", &newProviderVerifierTableInput{
|
||||
modifyOpts: func(p *ProviderVerifierOptions) {
|
||||
p.IssuerURL = "invalid"
|
||||
},
|
||||
expectedError: "could not get verifier builder: error while discovery OIDC configuration: failed to discover OIDC configuration: error performing request: Get \"invalid/.well-known/openid-configuration\": unsupported protocol scheme \"\"",
|
||||
}),
|
||||
Entry("with skip discovery and the JWKs URL is missing", &newProviderVerifierTableInput{
|
||||
modifyOpts: func(p *ProviderVerifierOptions) {
|
||||
p.SkipDiscovery = true
|
||||
p.JWKsURL = ""
|
||||
},
|
||||
expectedError: "invalid provider verifier options: missing required setting: jwks-url",
|
||||
}),
|
||||
Entry("should be succesfful when skipping discovery with the JWKs URL specified", &newProviderVerifierTableInput{
|
||||
modifyOpts: func(p *ProviderVerifierOptions) {
|
||||
p.SkipDiscovery = true
|
||||
p.JWKsURL = m.JWKSEndpoint()
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
type verifierTableInput struct {
|
||||
modifyOpts func(*ProviderVerifierOptions)
|
||||
modifyClaims func(*jwt.StandardClaims)
|
||||
expectedError string
|
||||
}
|
||||
|
||||
DescribeTable("when constructing the provider verifier", func(in *verifierTableInput) {
|
||||
opts := ProviderVerifierOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: m.Config().ClientID,
|
||||
ExtraAudiences: []string{},
|
||||
IssuerURL: m.Issuer(),
|
||||
}
|
||||
if in.modifyOpts != nil {
|
||||
in.modifyOpts(&opts)
|
||||
}
|
||||
|
||||
pv, err := NewProviderVerifier(context.Background(), opts)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
now := time.Now()
|
||||
claims := jwt.StandardClaims{
|
||||
Audience: m.Config().ClientID,
|
||||
Issuer: m.Issuer(),
|
||||
ExpiresAt: now.Add(1 * time.Hour).Unix(),
|
||||
IssuedAt: now.Unix(),
|
||||
Subject: "user",
|
||||
}
|
||||
if in.modifyClaims != nil {
|
||||
in.modifyClaims(&claims)
|
||||
}
|
||||
|
||||
rawIDToken, err := m.Keypair.SignJWT(claims)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
idToken, err := pv.Verifier().Verify(context.Background(), rawIDToken)
|
||||
if in.expectedError != "" {
|
||||
Expect(err).To(MatchError(HavePrefix(in.expectedError)))
|
||||
return
|
||||
}
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(idToken.Issuer).To(Equal(claims.Issuer))
|
||||
Expect(idToken.Audience).To(ConsistOf(claims.Audience))
|
||||
Expect(idToken.Subject).To(Equal(claims.Subject))
|
||||
},
|
||||
Entry("with the default opts and claims", &verifierTableInput{}),
|
||||
Entry("when the audience is mismatched", &verifierTableInput{
|
||||
modifyClaims: func(j *jwt.StandardClaims) {
|
||||
j.Audience = "OtherClient"
|
||||
},
|
||||
expectedError: "audience from claim aud with value [OtherClient] does not match with any of allowed audiences",
|
||||
}),
|
||||
Entry("when the audience is an extra audience", &verifierTableInput{
|
||||
modifyOpts: func(p *ProviderVerifierOptions) {
|
||||
p.ExtraAudiences = []string{"ExtraIssuer"}
|
||||
},
|
||||
modifyClaims: func(j *jwt.StandardClaims) {
|
||||
j.Audience = "ExtraIssuer"
|
||||
},
|
||||
}),
|
||||
Entry("when the issuer is mismatched", &verifierTableInput{
|
||||
modifyClaims: func(j *jwt.StandardClaims) {
|
||||
j.Issuer = "OtherIssuer"
|
||||
},
|
||||
expectedError: "failed to verify token: oidc: id token issued by a different provider",
|
||||
}),
|
||||
Entry("when the issuer is mismatched with skip issuer verification", &verifierTableInput{
|
||||
modifyOpts: func(p *ProviderVerifierOptions) {
|
||||
p.SkipIssuerVerification = true
|
||||
},
|
||||
modifyClaims: func(j *jwt.StandardClaims) {
|
||||
j.Issuer = "OtherIssuer"
|
||||
},
|
||||
}),
|
||||
Entry("when the token has expired", &verifierTableInput{
|
||||
modifyClaims: func(j *jwt.StandardClaims) {
|
||||
j.ExpiresAt = time.Now().Add(-1 * time.Hour).Unix()
|
||||
},
|
||||
expectedError: "failed to verify token: oidc: token is expired",
|
||||
}),
|
||||
)
|
||||
})
|
@ -8,33 +8,42 @@ import (
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
)
|
||||
|
||||
// IDTokenVerifier Used to verify an ID Token and extends oidc.IDTokenVerifier from the underlying oidc library
|
||||
type IDTokenVerifier struct {
|
||||
*oidc.IDTokenVerifier
|
||||
*IDTokenVerificationOptions
|
||||
allowedAudiences map[string]struct{}
|
||||
// idTokenVerifier allows an ID Token to be verified against the issue and provided keys.
|
||||
type IDTokenVerifier interface {
|
||||
Verify(context.Context, string) (*oidc.IDToken, error)
|
||||
}
|
||||
|
||||
// IDTokenVerificationOptions options for the oidc.IDTokenVerifier that are required to verify an ID Token
|
||||
// idTokenVerifier Used to verify an ID Token and extends oidc.idTokenVerifier from the underlying oidc library
|
||||
type idTokenVerifier struct {
|
||||
verifier *oidc.IDTokenVerifier
|
||||
verificationOptions IDTokenVerificationOptions
|
||||
allowedAudiences map[string]struct{}
|
||||
}
|
||||
|
||||
// IDTokenVerificationOptions options for the oidc.idTokenVerifier that are required to verify an ID Token
|
||||
type IDTokenVerificationOptions struct {
|
||||
AudienceClaims []string
|
||||
ClientID string
|
||||
ExtraAudiences []string
|
||||
}
|
||||
|
||||
// NewVerifier constructs a new IDTokenVerifier
|
||||
func NewVerifier(iv *oidc.IDTokenVerifier, vo *IDTokenVerificationOptions) *IDTokenVerifier {
|
||||
// NewVerifier constructs a new idTokenVerifier
|
||||
func NewVerifier(iv *oidc.IDTokenVerifier, vo IDTokenVerificationOptions) IDTokenVerifier {
|
||||
allowedAudiences := make(map[string]struct{})
|
||||
allowedAudiences[vo.ClientID] = struct{}{}
|
||||
for _, extraAudience := range vo.ExtraAudiences {
|
||||
allowedAudiences[extraAudience] = struct{}{}
|
||||
}
|
||||
return &IDTokenVerifier{iv, vo, allowedAudiences}
|
||||
return &idTokenVerifier{
|
||||
verifier: iv,
|
||||
verificationOptions: vo,
|
||||
allowedAudiences: allowedAudiences,
|
||||
}
|
||||
}
|
||||
|
||||
// Verify verifies incoming ID Token
|
||||
func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*oidc.IDToken, error) {
|
||||
token, err := v.IDTokenVerifier.Verify(ctx, rawIDToken)
|
||||
func (v *idTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*oidc.IDToken, error) {
|
||||
token, err := v.verifier.Verify(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to verify token: %v", err)
|
||||
}
|
||||
@ -51,8 +60,8 @@ func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*oidc.
|
||||
return token, err
|
||||
}
|
||||
|
||||
func (v *IDTokenVerifier) verifyAudience(token *oidc.IDToken, claims map[string]interface{}) (bool, error) {
|
||||
for _, audienceClaim := range v.AudienceClaims {
|
||||
func (v *idTokenVerifier) verifyAudience(token *oidc.IDToken, claims map[string]interface{}) (bool, error) {
|
||||
for _, audienceClaim := range v.verificationOptions.AudienceClaims {
|
||||
if audienceClaimValue, audienceClaimExists := claims[audienceClaim]; audienceClaimExists {
|
||||
|
||||
// audience claim value can be either interface{} or []interface{},
|
||||
@ -72,10 +81,10 @@ func (v *IDTokenVerifier) verifyAudience(token *oidc.IDToken, claims map[string]
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("audience claims %v do not exist in claims: %v",
|
||||
v.AudienceClaims, claims)
|
||||
v.verificationOptions.AudienceClaims, claims)
|
||||
}
|
||||
|
||||
func (v *IDTokenVerifier) isValidAudience(claim string, audience []string, allowedAudiences map[string]struct{}) (bool, error) {
|
||||
func (v *idTokenVerifier) isValidAudience(claim string, audience []string, allowedAudiences map[string]struct{}) (bool, error) {
|
||||
for _, aud := range audience {
|
||||
if _, allowedAudienceExists := allowedAudiences[aud]; allowedAudienceExists {
|
||||
return true, nil
|
||||
@ -87,7 +96,7 @@ func (v *IDTokenVerifier) isValidAudience(claim string, audience []string, allow
|
||||
claim, audience, allowedAudiences)
|
||||
}
|
||||
|
||||
func (v *IDTokenVerifier) interfaceSliceToString(slice interface{}) []string {
|
||||
func (v *idTokenVerifier) interfaceSliceToString(slice interface{}) []string {
|
||||
s := reflect.ValueOf(slice)
|
||||
if s.Kind() != reflect.Slice {
|
||||
panic(fmt.Sprintf("given a non-slice type %s", s.Kind()))
|
@ -17,7 +17,7 @@ var _ = Describe("Verify", func() {
|
||||
ctx := context.Background()
|
||||
|
||||
It("Succeeds with default aud behavior", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
result, err := verify(ctx, IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: "1226737",
|
||||
ExtraAudiences: []string{},
|
||||
@ -32,7 +32,7 @@ var _ = Describe("Verify", func() {
|
||||
})
|
||||
|
||||
It("Fails with default aud behavior", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
result, err := verify(ctx, IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: "7817818",
|
||||
ExtraAudiences: []string{},
|
||||
@ -46,7 +46,7 @@ var _ = Describe("Verify", func() {
|
||||
})
|
||||
|
||||
It("Succeeds with extra audiences", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
result, err := verify(ctx, IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: "7817818",
|
||||
ExtraAudiences: []string{"xyz", "1226737"},
|
||||
@ -61,7 +61,7 @@ var _ = Describe("Verify", func() {
|
||||
})
|
||||
|
||||
It("Fails with extra audiences", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
result, err := verify(ctx, IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: "7817818",
|
||||
ExtraAudiences: []string{"xyz", "abc"},
|
||||
@ -76,7 +76,7 @@ var _ = Describe("Verify", func() {
|
||||
})
|
||||
|
||||
It("Succeeds with non default aud behavior", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
result, err := verify(ctx, IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"client_id"},
|
||||
ClientID: "1226737",
|
||||
ExtraAudiences: []string{},
|
||||
@ -91,7 +91,7 @@ var _ = Describe("Verify", func() {
|
||||
})
|
||||
|
||||
It("Fails with non default aud behavior", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
result, err := verify(ctx, IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"client_id"},
|
||||
ClientID: "7817818",
|
||||
ExtraAudiences: []string{},
|
||||
@ -105,7 +105,7 @@ var _ = Describe("Verify", func() {
|
||||
})
|
||||
|
||||
It("Succeeds with non default aud behavior and extra audiences", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
result, err := verify(ctx, IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"client_id"},
|
||||
ClientID: "7817818",
|
||||
ExtraAudiences: []string{"xyz", "1226737"},
|
||||
@ -120,7 +120,7 @@ var _ = Describe("Verify", func() {
|
||||
})
|
||||
|
||||
It("Fails with non default aud behavior and extra audiences", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
result, err := verify(ctx, IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"client_id"},
|
||||
ClientID: "7817818",
|
||||
ExtraAudiences: []string{"xyz", "abc"},
|
||||
@ -135,7 +135,7 @@ var _ = Describe("Verify", func() {
|
||||
})
|
||||
|
||||
It("Fails if audience claim does not exist", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
result, err := verify(ctx, IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"not_exists"},
|
||||
ClientID: "7817818",
|
||||
ExtraAudiences: []string{"xyz", "abc"},
|
||||
@ -151,7 +151,7 @@ var _ = Describe("Verify", func() {
|
||||
})
|
||||
|
||||
It("Succeeds with multiple audiences", func() {
|
||||
var result, err = verify(ctx, &IDTokenVerificationOptions{
|
||||
var result, err = verify(ctx, IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"client_id", "aud"},
|
||||
ClientID: "123456789",
|
||||
ExtraAudiences: []string{"1226737"},
|
||||
@ -167,7 +167,7 @@ var _ = Describe("Verify", func() {
|
||||
})
|
||||
|
||||
It("Succeeds if aud claim match", func() {
|
||||
result, err := verify(ctx, &IDTokenVerificationOptions{
|
||||
result, err := verify(ctx, IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"client_id", "aud"},
|
||||
ClientID: "1226737",
|
||||
ExtraAudiences: []string{"xyz", "abc"},
|
||||
@ -207,7 +207,7 @@ func (t *testVerifier) VerifySignature(ctx context.Context, jwt string) ([]byte,
|
||||
return jws.Verify(&t.jwk)
|
||||
}
|
||||
|
||||
func verify(ctx context.Context, verificationOptions *IDTokenVerificationOptions, payload payload) (*oidc.IDToken, error) {
|
||||
func verify(ctx context.Context, verificationOptions IDTokenVerificationOptions, payload payload) (*oidc.IDToken, error) {
|
||||
config := &oidc.Config{
|
||||
ClientID: "1226737",
|
||||
SkipClientIDCheck: true,
|
@ -8,13 +8,11 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/mbland/hmacauth"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
|
||||
)
|
||||
|
||||
@ -148,32 +146,27 @@ func parseJwtIssuers(issuers []string, msgs []string) ([]jwtIssuer, []string) {
|
||||
|
||||
// newVerifierFromJwtIssuer takes in issuer information in jwtIssuer info and returns
|
||||
// a verifier for that issuer.
|
||||
func newVerifierFromJwtIssuer(audienceClaims []string, extraAudiences []string, jwtIssuer jwtIssuer) (*internaloidc.IDTokenVerifier, error) {
|
||||
config := &oidc.Config{
|
||||
ClientID: jwtIssuer.audience,
|
||||
SkipClientIDCheck: true, // client id check is done within oauth2-proxy: IDTokenVerifier.Verify
|
||||
}
|
||||
// Try as an OpenID Connect Provider first
|
||||
var verifier *oidc.IDTokenVerifier
|
||||
provider, err := oidc.NewProvider(context.Background(), jwtIssuer.issuerURI)
|
||||
if err != nil {
|
||||
// Try as JWKS URI
|
||||
jwksURI := strings.TrimSuffix(jwtIssuer.issuerURI, "/") + "/.well-known/jwks.json"
|
||||
if err := requests.New(jwksURI).Do().Error(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
verifier = oidc.NewVerifier(jwtIssuer.issuerURI, oidc.NewRemoteKeySet(context.Background(), jwksURI), config)
|
||||
} else {
|
||||
verifier = provider.Verifier(config)
|
||||
}
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
func newVerifierFromJwtIssuer(audienceClaims []string, extraAudiences []string, jwtIssuer jwtIssuer) (internaloidc.IDTokenVerifier, error) {
|
||||
pvOpts := internaloidc.ProviderVerifierOptions{
|
||||
AudienceClaims: audienceClaims,
|
||||
ClientID: jwtIssuer.audience,
|
||||
ExtraAudiences: extraAudiences,
|
||||
// ExtraAudiences: o.Providers[0].OIDCConfig.ExtraAudiences,
|
||||
IssuerURL: jwtIssuer.issuerURI,
|
||||
}
|
||||
return internaloidc.NewVerifier(verifier, verificationOptions), nil
|
||||
|
||||
pv, err := internaloidc.NewProviderVerifier(context.TODO(), pvOpts)
|
||||
if err != nil {
|
||||
// If the discovery didn't work, try again without discovery
|
||||
pvOpts.JWKsURL = strings.TrimSuffix(jwtIssuer.issuerURI, "/") + "/.well-known/jwks.json"
|
||||
pvOpts.SkipDiscovery = true
|
||||
|
||||
pv, err = internaloidc.NewProviderVerifier(context.TODO(), pvOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not construct provider verifier for JWT Issuer: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return pv.Verifier(), nil
|
||||
}
|
||||
|
||||
// jwtIssuer hold parsed JWT issuer info that's used to construct a verifier.
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/ginkgo/extensions/table"
|
||||
. "github.com/onsi/gomega"
|
||||
@ -43,7 +43,7 @@ func newSignedTestADFSToken(tokenClaims adfsClaims) (string, error) {
|
||||
}
|
||||
|
||||
func testADFSProvider(hostname string) *ADFSProvider {
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
verificationOptions := internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: "https://test.myapp.com",
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -41,7 +41,7 @@ type azureOAuthPayload struct {
|
||||
}
|
||||
|
||||
func testAzureProvider(hostname string, opts options.AzureOptions) *AzureProvider {
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
verificationOptions := internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: "cd6d4fae-f6a6-4a34-8454-2c6b598e9532",
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -45,7 +45,7 @@ func newTestKeycloakOIDCSetup() (*httptest.Server, *KeycloakOIDCProvider) {
|
||||
}
|
||||
|
||||
func newKeycloakOIDCProvider(serverURL *url.URL, opts options.KeycloakOptions) *KeycloakOIDCProvider {
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
verificationOptions := internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{defaultAudienceClaim},
|
||||
ClientID: mockClientID,
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -27,7 +27,7 @@ type redeemTokenResponse struct {
|
||||
}
|
||||
|
||||
func newOIDCProvider(serverURL *url.URL, skipNonce bool) *OIDCProvider {
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
verificationOptions := internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: "https://test.myapp.com",
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/util"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
@ -47,7 +47,7 @@ type ProviderData struct {
|
||||
UserClaim string
|
||||
EmailClaim string
|
||||
GroupsClaim string
|
||||
Verifier *internaloidc.IDTokenVerifier
|
||||
Verifier internaloidc.IDTokenVerifier
|
||||
|
||||
// Universal Group authorization data structure
|
||||
// any provider can set to consume
|
||||
|
@ -16,7 +16,7 @@ import (
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
|
||||
. "github.com/onsi/gomega"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
@ -202,7 +202,7 @@ func TestProviderData_verifyIDToken(t *testing.T) {
|
||||
|
||||
provider := &ProviderData{}
|
||||
if tc.Verifier {
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
verificationOptions := internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: oidcClientID,
|
||||
}
|
||||
@ -409,7 +409,7 @@ func TestProviderData_buildSessionFromClaims(t *testing.T) {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
verificationOptions := internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: oidcClientID,
|
||||
}
|
||||
@ -478,7 +478,7 @@ func TestProviderData_checkNonce(t *testing.T) {
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
tc.Session.IDToken = rawIDToken
|
||||
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
verificationOptions := internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: oidcClientID,
|
||||
}
|
||||
|
@ -2,17 +2,12 @@ package providers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/oidc"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
|
||||
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
|
||||
k8serrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
)
|
||||
|
||||
@ -86,16 +81,27 @@ func newProviderDataFromConfig(providerConfig options.Provider) (*ProviderData,
|
||||
}
|
||||
|
||||
if needsVerifier {
|
||||
oidcProvider, verifier, err := newOIDCProviderVerifier(providerConfig)
|
||||
pv, err := internaloidc.NewProviderVerifier(context.TODO(), internaloidc.ProviderVerifierOptions{
|
||||
AudienceClaims: providerConfig.OIDCConfig.AudienceClaims,
|
||||
ClientID: providerConfig.ClientID,
|
||||
ExtraAudiences: providerConfig.OIDCConfig.ExtraAudiences,
|
||||
IssuerURL: providerConfig.OIDCConfig.IssuerURL,
|
||||
JWKsURL: providerConfig.OIDCConfig.JwksURL,
|
||||
SkipDiscovery: providerConfig.OIDCConfig.SkipDiscovery,
|
||||
SkipIssuerVerification: providerConfig.OIDCConfig.InsecureSkipIssuerVerification,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error setting OIDC configuration: %v", err)
|
||||
return nil, fmt.Errorf("error building OIDC ProviderVerifier: %v", err)
|
||||
}
|
||||
|
||||
p.Verifier = verifier
|
||||
if oidcProvider != nil {
|
||||
p.Verifier = pv.Verifier()
|
||||
if pv.DiscoveryEnabled() {
|
||||
// Use the discovered values rather than any specified values
|
||||
providerConfig.LoginURL = oidcProvider.Endpoint().AuthURL
|
||||
providerConfig.RedeemURL = oidcProvider.Endpoint().TokenURL
|
||||
endpoints := pv.Provider().Endpoints()
|
||||
providerConfig.LoginURL = endpoints.AuthURL
|
||||
providerConfig.RedeemURL = endpoints.TokenURL
|
||||
providerConfig.ProfileURL = endpoints.UserInfoURL
|
||||
providerConfig.OIDCConfig.JwksURL = endpoints.JWKsURL
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,118 +165,3 @@ func providerRequiresOIDCProviderVerifier(providerType options.ProviderType) (bo
|
||||
return false, fmt.Errorf("unknown provider type: %s", providerType)
|
||||
}
|
||||
}
|
||||
|
||||
func newOIDCProviderVerifier(providerConfig options.Provider) (*oidc.Provider, *internaloidc.IDTokenVerifier, error) {
|
||||
// If the issuer isn't set, default it for platforms where it makes sense
|
||||
if providerConfig.OIDCConfig.IssuerURL == "" {
|
||||
switch providerConfig.Type {
|
||||
case "gitlab":
|
||||
providerConfig.OIDCConfig.IssuerURL = "https://gitlab.com"
|
||||
case "oidc":
|
||||
return nil, nil, errors.New("missing required setting: OIDC Issuer URL cannot be empty")
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case providerConfig.OIDCConfig.InsecureSkipIssuerVerification && !providerConfig.OIDCConfig.SkipDiscovery:
|
||||
verifier, err := newInsecureSkipIssuerVerificationOIDCVerifier(providerConfig)
|
||||
return nil, verifier, err
|
||||
case providerConfig.OIDCConfig.SkipDiscovery:
|
||||
verifier, err := newSkipDiscoveryOIDCVerifier(providerConfig)
|
||||
return nil, verifier, err
|
||||
default:
|
||||
return newDiscoveryOIDCProviderVerifier(providerConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func newDiscoveryOIDCProviderVerifier(providerConfig options.Provider) (*oidc.Provider, *internaloidc.IDTokenVerifier, error) {
|
||||
// Configure discoverable provider data.
|
||||
provider, err := oidc.NewProvider(context.TODO(), providerConfig.OIDCConfig.IssuerURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: providerConfig.OIDCConfig.AudienceClaims,
|
||||
ClientID: providerConfig.ClientID,
|
||||
ExtraAudiences: providerConfig.OIDCConfig.ExtraAudiences,
|
||||
}
|
||||
verifier := internaloidc.NewVerifier(provider.Verifier(&oidc.Config{
|
||||
ClientID: providerConfig.ClientID,
|
||||
SkipIssuerCheck: providerConfig.OIDCConfig.InsecureSkipIssuerVerification,
|
||||
SkipClientIDCheck: true, // client id check is done within oauth2-proxy: IDTokenVerifier.Verify
|
||||
}), verificationOptions)
|
||||
|
||||
return provider, verifier, nil
|
||||
}
|
||||
|
||||
func newInsecureSkipIssuerVerificationOIDCVerifier(providerConfig options.Provider) (*internaloidc.IDTokenVerifier, error) {
|
||||
// go-oidc doesn't let us pass bypass the issuer check this in the oidc.NewProvider call
|
||||
// (which uses discovery to get the URLs), so we'll do a quick check ourselves and if
|
||||
// we get the URLs, we'll just use the non-discovery path.
|
||||
|
||||
logger.Printf("Performing OIDC Discovery...")
|
||||
|
||||
requestURL := strings.TrimSuffix(providerConfig.OIDCConfig.IssuerURL, "/") + "/.well-known/openid-configuration"
|
||||
body, err := requests.New(requestURL).
|
||||
Do().
|
||||
UnmarshalJSON()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to discover OIDC configuration: %v", err)
|
||||
}
|
||||
|
||||
// Prefer manually configured URLs. It's a bit unclear
|
||||
// why you'd be doing discovery and also providing the URLs
|
||||
// explicitly though...
|
||||
if providerConfig.LoginURL == "" {
|
||||
providerConfig.LoginURL = body.Get("authorization_endpoint").MustString()
|
||||
}
|
||||
|
||||
if providerConfig.RedeemURL == "" {
|
||||
providerConfig.RedeemURL = body.Get("token_endpoint").MustString()
|
||||
}
|
||||
|
||||
if providerConfig.OIDCConfig.JwksURL == "" {
|
||||
providerConfig.OIDCConfig.JwksURL = body.Get("jwks_uri").MustString()
|
||||
}
|
||||
|
||||
if providerConfig.ProfileURL == "" {
|
||||
providerConfig.ProfileURL = body.Get("userinfo_endpoint").MustString()
|
||||
}
|
||||
|
||||
// Now we have performed the discovery, construct the verifier manually
|
||||
return newSkipDiscoveryOIDCVerifier(providerConfig)
|
||||
}
|
||||
|
||||
func newSkipDiscoveryOIDCVerifier(providerConfig options.Provider) (*internaloidc.IDTokenVerifier, error) {
|
||||
var errs []error
|
||||
|
||||
// Construct a manual IDTokenVerifier from issuer URL & JWKS URI
|
||||
// instead of metadata discovery if we enable -skip-oidc-discovery.
|
||||
// In this case we need to make sure the required endpoints for
|
||||
// the provider are configured.
|
||||
if providerConfig.LoginURL == "" {
|
||||
errs = append(errs, errors.New("missing required setting: login-url"))
|
||||
}
|
||||
if providerConfig.RedeemURL == "" {
|
||||
errs = append(errs, errors.New("missing required setting: redeem-url"))
|
||||
}
|
||||
if providerConfig.OIDCConfig.JwksURL == "" {
|
||||
errs = append(errs, errors.New("missing required setting: oidc-jwks-url"))
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return nil, k8serrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
keySet := oidc.NewRemoteKeySet(context.TODO(), providerConfig.OIDCConfig.JwksURL)
|
||||
verificationOptions := &internaloidc.IDTokenVerificationOptions{
|
||||
AudienceClaims: providerConfig.OIDCConfig.AudienceClaims,
|
||||
ClientID: providerConfig.ClientID,
|
||||
ExtraAudiences: providerConfig.OIDCConfig.ExtraAudiences,
|
||||
}
|
||||
verifier := internaloidc.NewVerifier(oidc.NewVerifier(providerConfig.OIDCConfig.IssuerURL, keySet, &oidc.Config{
|
||||
ClientID: providerConfig.ClientID,
|
||||
SkipIssuerCheck: providerConfig.OIDCConfig.InsecureSkipIssuerVerification,
|
||||
SkipClientIDCheck: true, // client id check is done within oauth2-proxy: IDTokenVerifier.Verify
|
||||
}), verificationOptions)
|
||||
return verifier, nil
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ func TestSkipOIDCDiscovery(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err := newProviderDataFromConfig(providerConfig)
|
||||
g.Expect(err).To(MatchError("error setting OIDC configuration: [missing required setting: login-url, missing required setting: redeem-url, missing required setting: oidc-jwks-url]"))
|
||||
g.Expect(err).To(MatchError("error building OIDC ProviderVerifier: invalid provider verifier options: missing required setting: jwks-url"))
|
||||
|
||||
providerConfig.LoginURL = msAuthURL
|
||||
providerConfig.RedeemURL = msTokenURL
|
||||
|
Loading…
x
Reference in New Issue
Block a user