* feat: add support for specifying allowed OIDC JWT signing algorithms (#2753) TODO: - [X] update docs - [X] add support in yaml (modern) config - [X] add more test(s)? Add (legacy for now) configuration flag "oidc-enabled-signing-alg" (cfg: oidc_enabled_signing_algs) that allows setting what signing algorithms are specified by provider in JWT header ("alg" header claim). In particular useful when skip_oidc_discovery = true, as verifier defaults to only accept "RS256" in alg field in such circumstances. Signed-off-by: Jan Larwig <jan@larwig.com> * doc: update changelog and alpha config Signed-off-by: Jan Larwig <jan@larwig.com> * feat: add signing algorithm intersection handling with oidc discovery and additional tests Signed-off-by: Jan Larwig <jan@larwig.com> --------- Signed-off-by: Jan Larwig <jan@larwig.com> Co-authored-by: Jan Larwig <jan@larwig.com>
This commit is contained in:
parent
30853098c7
commit
7c96234233
|
|
@ -12,6 +12,7 @@
|
|||
- [#3332](https://github.com/oauth2-proxy/oauth2-proxy/pull/3332) ci: distribute windows binary with .exe extension (@igitur)
|
||||
- [#2685](https://github.com/oauth2-proxy/oauth2-proxy/pull/2685) feat: allow arbitrary claims from the IDToken and IdentityProvider UserInfo endpoint to be added to the session state (@vegetablest)
|
||||
- [#3278](https://github.com/oauth2-proxy/oauth2-proxy/pull/3278) feat: possibility to inject id_token in redirect url during sign out (@albanf)
|
||||
- [#2851](https://github.com/oauth2-proxy/oauth2-proxy/pull/2851) feat: add support for specifying allowed OIDC JWT signing algorithms (#2753) (@andoks / @tuunit)
|
||||
|
||||
# V7.14.3
|
||||
|
||||
|
|
|
|||
|
|
@ -488,6 +488,7 @@ character.
|
|||
| `userIDClaim` | _string_ | UserIDClaim indicates which claim contains the user ID<br/>default set to 'email' |
|
||||
| `audienceClaims` | _[]string_ | AudienceClaim allows to define any claim that is verified against the client id<br/>By default `aud` claim is used for verification. |
|
||||
| `extraAudiences` | _[]string_ | ExtraAudiences is a list of additional audiences that are allowed<br/>to pass verification in addition to the client id. |
|
||||
| `enabledSigningAlgs` | _[]string_ | EnabledSigningAlgs is a list of allowed JWT signing algorithms.<br/>When discovery is enabled, the effective set is the intersection<br/>between this list and the provider's discovered supported algorithms.<br/>By default `RS256` is used if nothing has been discovered or specified. |
|
||||
|
||||
### Provider
|
||||
|
||||
|
|
|
|||
|
|
@ -99,7 +99,8 @@ Provider specific options can be found on their respective subpages.
|
|||
| flag: `--oidc-groups-claim`<br/>toml: `oidc_groups_claim` | string | which OIDC claim contains the user groups | `"groups"` |
|
||||
| flag: `--oidc-issuer-url`<br/>toml: `oidc_issuer_url` | string | the OpenID Connect issuer URL, e.g. `"https://accounts.google.com"` | |
|
||||
| flag: `--oidc-jwks-url`<br/>toml: `oidc_jwks_url` | string | OIDC JWKS URI for token verification; required if OIDC discovery is disabled and public key files are not provided | |
|
||||
| flag: `--oidc-public-key-file`<br/>toml: `oidc_public_key_files` | string | Path to public key file in PEM format to use for verifying JWT tokens (may be given multiple times). Required if OIDC discovery is disabled na JWKS URL isn't provided | string \| list |
|
||||
| flag: `--oidc-public-key-file`<br/>toml: `oidc_public_key_files` | string | Path to public key file in PEM format to use for verifying JWT tokens (may be given multiple times). Required if OIDC discovery is disabled na JWKS URL isn't provided | |
|
||||
| flag: `--oidc-enabled-signing-alg`<br/>toml: `oidc_enabled_signing_algs` | string \| list | List of allowed JWT signing algorithms. When oidc discovery is enabled, the effective set is the intersection between this list and the provider's discovered supported algorithms. | |
|
||||
| flag: `--profile-url`<br/>toml: `profile_url` | string | Profile access endpoint | |
|
||||
| flag: `--prompt`<br/>toml: `prompt` | string | [OIDC prompt](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest); if present, `approval-prompt` is ignored | `""` |
|
||||
| flag: `--provider-ca-file`<br/>toml: `provider_ca_files` | string \| list | Paths to CA certificates that should be used when connecting to the provider. If not specified, the default Go trust sources are used instead. |
|
||||
|
|
|
|||
|
|
@ -186,6 +186,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
|
|||
InsecureAllowUnverifiedEmail: ptr.To(false),
|
||||
InsecureSkipIssuerVerification: ptr.To(false),
|
||||
SkipDiscovery: ptr.To(false),
|
||||
EnabledSigningAlgs: []string{},
|
||||
},
|
||||
MicrosoftEntraIDConfig: options.MicrosoftEntraIDOptions{
|
||||
FederatedTokenAuth: ptr.To(false),
|
||||
|
|
|
|||
|
|
@ -51,15 +51,16 @@ func NewLegacyOptions() *LegacyOptions {
|
|||
},
|
||||
|
||||
LegacyProvider: LegacyProvider{
|
||||
ProviderType: "google",
|
||||
AzureTenant: "common",
|
||||
ApprovalPrompt: "force",
|
||||
UserIDClaim: "email",
|
||||
OIDCEmailClaim: "email",
|
||||
OIDCGroupsClaim: "groups",
|
||||
OIDCAudienceClaims: []string{"aud"},
|
||||
OIDCExtraAudiences: []string{},
|
||||
InsecureOIDCSkipNonce: true,
|
||||
ProviderType: "google",
|
||||
AzureTenant: "common",
|
||||
ApprovalPrompt: "force",
|
||||
UserIDClaim: "email",
|
||||
OIDCEmailClaim: "email",
|
||||
OIDCGroupsClaim: "groups",
|
||||
OIDCAudienceClaims: []string{"aud"},
|
||||
OIDCExtraAudiences: []string{},
|
||||
OIDCEnabledSigningAlgs: []string{},
|
||||
InsecureOIDCSkipNonce: true,
|
||||
},
|
||||
|
||||
Options: *NewOptions(),
|
||||
|
|
@ -545,6 +546,7 @@ type LegacyProvider struct {
|
|||
OIDCAudienceClaims []string `flag:"oidc-audience-claim" cfg:"oidc_audience_claims"`
|
||||
OIDCExtraAudiences []string `flag:"oidc-extra-audience" cfg:"oidc_extra_audiences"`
|
||||
OIDCPublicKeyFiles []string `flag:"oidc-public-key-file" cfg:"oidc_public_key_files"`
|
||||
OIDCEnabledSigningAlgs []string `flag:"oidc-enabled-signing-alg" cfg:"oidc_enabled_signing_algs"`
|
||||
LoginURL string `flag:"login-url" cfg:"login_url"`
|
||||
AuthRequestResponseMode string `flag:"auth-request-response-mode" cfg:"auth_request_response_mode"`
|
||||
RedeemURL string `flag:"redeem-url" cfg:"redeem_url"`
|
||||
|
|
@ -606,6 +608,7 @@ func legacyProviderFlagSet() *pflag.FlagSet {
|
|||
flagSet.StringSlice("oidc-audience-claim", OIDCAudienceClaims, "which OIDC claims are used as audience to verify against client id")
|
||||
flagSet.StringSlice("oidc-extra-audience", []string{}, "additional audiences allowed to pass audience verification")
|
||||
flagSet.StringSlice("oidc-public-key-file", []string{}, "path to public key file in PEM format to use for verifying JWT tokens (may be given multiple times)")
|
||||
flagSet.StringSlice("oidc-enabled-signing-alg", []string{}, "accepted signing algorithms for provider to use")
|
||||
flagSet.String("login-url", "", "Authentication endpoint")
|
||||
flagSet.String("redeem-url", "", "Token redemption endpoint")
|
||||
flagSet.String("profile-url", "", "Profile access endpoint")
|
||||
|
|
@ -727,6 +730,7 @@ func (l *LegacyProvider) convert() (Providers, error) {
|
|||
AudienceClaims: l.OIDCAudienceClaims,
|
||||
ExtraAudiences: l.OIDCExtraAudiences,
|
||||
PublicKeyFiles: l.OIDCPublicKeyFiles,
|
||||
EnabledSigningAlgs: l.OIDCEnabledSigningAlgs,
|
||||
}
|
||||
|
||||
// Support for legacy configuration option
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ var _ = Describe("Legacy Options", func() {
|
|||
legacyOpts.LegacyUpstreams.Upstreams = []string{"http://foo.bar/baz", "file:///var/lib/website#/bar", "static://204"}
|
||||
legacyOpts.LegacyProvider.ClientID = "oauth-proxy"
|
||||
legacyOpts.LegacyUpstreams.DisableKeepAlives = false
|
||||
legacyOpts.LegacyProvider.OIDCEnabledSigningAlgs = []string{"RS256", "EdDSA"}
|
||||
|
||||
staticCode := 204
|
||||
opts.UpstreamServers = UpstreamConfig{
|
||||
|
|
@ -128,6 +129,7 @@ var _ = Describe("Legacy Options", func() {
|
|||
opts.Providers[0].OIDCConfig.ExtraAudiences = []string{}
|
||||
opts.Providers[0].OIDCConfig.InsecureSkipNonce = ptr.To(true)
|
||||
opts.Providers[0].OIDCConfig.InsecureSkipIssuerVerification = ptr.To(false)
|
||||
opts.Providers[0].OIDCConfig.EnabledSigningAlgs = []string{"RS256", "EdDSA"}
|
||||
opts.Providers[0].LoginURLParameters = []LoginURLParameter{
|
||||
{Name: "approval_prompt", Default: []string{"force"}},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -321,6 +321,11 @@ type OIDCOptions struct {
|
|||
// ExtraAudiences is a list of additional audiences that are allowed
|
||||
// to pass verification in addition to the client id.
|
||||
ExtraAudiences []string `yaml:"extraAudiences,omitempty"`
|
||||
// EnabledSigningAlgs is a list of allowed JWT signing algorithms.
|
||||
// When discovery is enabled, the effective set is the intersection
|
||||
// between this list and the provider's discovered supported algorithms.
|
||||
// By default `RS256` is used if nothing has been discovered or specified.
|
||||
EnabledSigningAlgs []string `yaml:"enabledSigningAlgs,omitempty"`
|
||||
}
|
||||
|
||||
type LoginGovOptions struct {
|
||||
|
|
|
|||
|
|
@ -155,13 +155,48 @@ func getVerifierBuilder(ctx context.Context, opts ProviderVerifierOptions) (veri
|
|||
return nil, nil, fmt.Errorf("error while discovery OIDC configuration: %w", err)
|
||||
}
|
||||
|
||||
supportedSigningAlgs, err := intersectSigningAlgs(provider.SupportedSigningAlgs(), opts.SupportedSigningAlgs)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error while determining supported signing algorithms: %w", err)
|
||||
}
|
||||
|
||||
return newVerifierBuilder(
|
||||
opts.IssuerURL,
|
||||
oidc.NewRemoteKeySet(ctx, provider.Endpoints().JWKsURL),
|
||||
provider.SupportedSigningAlgs(),
|
||||
supportedSigningAlgs,
|
||||
), provider, nil
|
||||
}
|
||||
|
||||
// intersectSigningAlgs returns the intersecting list of signing algorithms from the oidc discovery
|
||||
// and the signing algorithms provided through the options.
|
||||
func intersectSigningAlgs(discoveredSigningAlgs, configuredSigningAlgs []string) ([]string, error) {
|
||||
if len(configuredSigningAlgs) == 0 {
|
||||
return discoveredSigningAlgs, nil
|
||||
}
|
||||
|
||||
if len(discoveredSigningAlgs) == 0 {
|
||||
return configuredSigningAlgs, nil
|
||||
}
|
||||
|
||||
discovered := make(map[string]struct{}, len(discoveredSigningAlgs))
|
||||
for _, signingAlg := range discoveredSigningAlgs {
|
||||
discovered[signingAlg] = struct{}{}
|
||||
}
|
||||
|
||||
intersection := make([]string, 0, len(configuredSigningAlgs))
|
||||
for _, signingAlg := range configuredSigningAlgs {
|
||||
if _, ok := discovered[signingAlg]; ok {
|
||||
intersection = append(intersection, signingAlg)
|
||||
}
|
||||
}
|
||||
|
||||
if len(intersection) == 0 {
|
||||
return nil, fmt.Errorf("no supported signing algorithms in common between provider and configuration: discovered=%v, configured=%v", discoveredSigningAlgs, configuredSigningAlgs)
|
||||
}
|
||||
|
||||
return intersection, nil
|
||||
}
|
||||
|
||||
// GetPublicKeyFromBytes parses a PEM-encoded public key from a byte array
|
||||
// and returns a crypto.PublicKey object.
|
||||
func getPublicKeyFromBytes(bytes []byte) (crypto.PublicKey, error) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ package oidc
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
|
@ -195,6 +198,21 @@ var _ = Describe("ProviderVerifier", func() {
|
|||
Expect(idToken.Subject).To(Equal(claims.Subject))
|
||||
},
|
||||
Entry("with the default opts and claims", &verifierTableInput{}),
|
||||
Entry("with skip discovery and an allowed signing algorithm", &verifierTableInput{
|
||||
modifyOpts: func(p *ProviderVerifierOptions) {
|
||||
p.SkipDiscovery = true
|
||||
p.JWKsURL = m.JWKSEndpoint()
|
||||
p.SupportedSigningAlgs = []string{"RS256"}
|
||||
},
|
||||
}),
|
||||
Entry("with skip discovery and a disallowed signing algorithm", &verifierTableInput{
|
||||
modifyOpts: func(p *ProviderVerifierOptions) {
|
||||
p.SkipDiscovery = true
|
||||
p.JWKsURL = m.JWKSEndpoint()
|
||||
p.SupportedSigningAlgs = []string{"HS256"}
|
||||
},
|
||||
expectedError: "failed to verify token: oidc: malformed jwt: unexpected signature algorithm \"RS256\"; expected [\"HS256\"]",
|
||||
}),
|
||||
Entry("when the audience is mismatched", &verifierTableInput{
|
||||
modifyClaims: func(j *jwt.RegisteredClaims) {
|
||||
j.Audience = jwt.ClaimStrings{"OtherClient"}
|
||||
|
|
@ -230,4 +248,78 @@ var _ = Describe("ProviderVerifier", func() {
|
|||
expectedError: "failed to verify token: oidc: token is expired",
|
||||
}),
|
||||
)
|
||||
|
||||
Describe("intersectSigningAlgs", func() {
|
||||
DescribeTable("when determining allowed signing algorithms", func(discoveredSigningAlgs, configuredSigningAlgs, expected []string, expectedError string) {
|
||||
actual, err := intersectSigningAlgs(discoveredSigningAlgs, configuredSigningAlgs)
|
||||
Expect(actual).To(Equal(expected))
|
||||
if len(expectedError) > 0 {
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(Equal(expectedError))
|
||||
}
|
||||
},
|
||||
Entry("returns discovered values when no configured values are provided", []string{"RS256", "HS256"}, []string(nil), []string{"RS256", "HS256"}, ""),
|
||||
Entry("returns configured values when no discovered values are provided", []string(nil), []string{"RS256"}, []string{"RS256"}, ""),
|
||||
Entry("returns the configured order of the intersection", []string{"RS256", "HS256", "EdDSA"}, []string{"EdDSA", "RS256"}, []string{"EdDSA", "RS256"}, ""),
|
||||
Entry("returns an error when there is no intersection", []string{"RS256", "HS256"}, []string{"EdDSA"}, nil, "no supported signing algorithms in common between provider and configuration: discovered=[RS256 HS256], configured=[EdDSA]"),
|
||||
)
|
||||
})
|
||||
|
||||
It("uses the intersection between discovered and configured signing algorithms", func() {
|
||||
customServer, err := mockoidc.NewServer(nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
customServer.AddMiddleware(newConfiguredSigningAlgsIssuerMiddleware(customServer, []string{"RS256", "HS256"}))
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(customServer.Start(listener, nil)).To(Succeed())
|
||||
defer func() {
|
||||
Expect(customServer.Shutdown()).To(Succeed())
|
||||
}()
|
||||
|
||||
pv, err := NewProviderVerifier(context.Background(), ProviderVerifierOptions{
|
||||
AudienceClaims: []string{"aud"},
|
||||
ClientID: customServer.Config().ClientID,
|
||||
ExtraAudiences: []string{},
|
||||
IssuerURL: customServer.Issuer(),
|
||||
SupportedSigningAlgs: []string{"HS256"},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
rawIDToken, err := customServer.Keypair.SignJWT(jwt.RegisteredClaims{
|
||||
Audience: jwt.ClaimStrings{customServer.Config().ClientID},
|
||||
Issuer: customServer.Issuer(),
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
Subject: "user",
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
_, err = pv.Verifier().Verify(context.Background(), rawIDToken)
|
||||
Expect(err).To(MatchError(HavePrefix("failed to verify token: oidc: malformed jwt: unexpected signature algorithm \"RS256\"; expected [\"HS256\"]")))
|
||||
})
|
||||
})
|
||||
|
||||
func newConfiguredSigningAlgsIssuerMiddleware(m *mockoidc.MockOIDC, supportedSigningAlgs []string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
provider := providerJSON{
|
||||
Issuer: m.Issuer(),
|
||||
AuthURL: m.AuthorizationEndpoint(),
|
||||
TokenURL: m.TokenEndpoint(),
|
||||
JWKsURL: m.JWKSEndpoint(),
|
||||
UserInfoURL: m.UserinfoEndpoint(),
|
||||
SupportedSigningAlgs: supportedSigningAlgs,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(provider)
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = rw.Write(data)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,27 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
jose "github.com/go-jose/go-jose/v4"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
|
||||
)
|
||||
|
||||
var supportedOIDCSigningAlgorithms = map[jose.SignatureAlgorithm]struct{}{
|
||||
jose.EdDSA: {},
|
||||
jose.HS256: {},
|
||||
jose.HS384: {},
|
||||
jose.HS512: {},
|
||||
jose.RS256: {},
|
||||
jose.RS384: {},
|
||||
jose.RS512: {},
|
||||
jose.ES256: {},
|
||||
jose.ES384: {},
|
||||
jose.ES512: {},
|
||||
jose.PS256: {},
|
||||
jose.PS384: {},
|
||||
jose.PS512: {},
|
||||
}
|
||||
|
||||
// validateProviders is the initial validation migration for multiple providrers
|
||||
// It currently includes only logic that can verify the providers one by one and does not break the valdation pipe
|
||||
func validateProviders(o *options.Options) []string {
|
||||
|
|
@ -59,6 +76,22 @@ func validateProvider(provider options.Provider, providerIDs map[string]struct{}
|
|||
msgs = append(msgs, validateEntraConfig(provider)...)
|
||||
}
|
||||
|
||||
msgs = append(msgs, validateOIDCSigningAlgorithms(provider)...)
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
func validateOIDCSigningAlgorithms(provider options.Provider) []string {
|
||||
msgs := []string{}
|
||||
|
||||
for _, algorithm := range provider.OIDCConfig.EnabledSigningAlgs {
|
||||
if _, ok := supportedOIDCSigningAlgorithms[jose.SignatureAlgorithm(algorithm)]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
msgs = append(msgs, fmt.Sprintf("provider %s has invalid EnabledSigningAlgs entry %q", provider.ID, algorithm))
|
||||
}
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,33 @@ var _ = Describe("Providers", func() {
|
|||
ClientSecret: "ClientSecret",
|
||||
}
|
||||
|
||||
validOIDCSigningAlgorithmsProvider := options.Provider{
|
||||
ID: "ProviderIDOIDCSigningAlgorithms",
|
||||
ClientID: "ClientID",
|
||||
ClientSecret: "ClientSecret",
|
||||
OIDCConfig: options.OIDCOptions{
|
||||
EnabledSigningAlgs: []string{"RS256", "EdDSA"},
|
||||
},
|
||||
}
|
||||
|
||||
invalidOIDCSigningAlgorithmsProvider := options.Provider{
|
||||
ID: "ProviderIDInvalidOIDCSigningAlgorithms",
|
||||
ClientID: "ClientID",
|
||||
ClientSecret: "ClientSecret",
|
||||
OIDCConfig: options.OIDCOptions{
|
||||
EnabledSigningAlgs: []string{"RS256", "invalid"},
|
||||
},
|
||||
}
|
||||
|
||||
invalidOIDCSigningAlgorithmCaseProvider := options.Provider{
|
||||
ID: "ProviderIDInvalidOIDCSigningAlgorithmCase",
|
||||
ClientID: "ClientID",
|
||||
ClientSecret: "ClientSecret",
|
||||
OIDCConfig: options.OIDCOptions{
|
||||
EnabledSigningAlgs: []string{"rs256"},
|
||||
},
|
||||
}
|
||||
|
||||
validLoginGovProvider := options.Provider{
|
||||
Type: "login.gov",
|
||||
ID: "ProviderIDLoginGov",
|
||||
|
|
@ -34,6 +61,8 @@ var _ = Describe("Providers", func() {
|
|||
emptyIDMsg := "provider has empty id: ids are required for all providers"
|
||||
duplicateProviderIDMsg := "multiple providers found with id ProviderID: provider ids must be unique"
|
||||
skipButtonAndMultipleProvidersMsg := "SkipProviderButton and multiple providers are mutually exclusive"
|
||||
invalidOIDCSigningAlgorithmMsg := "provider ProviderIDInvalidOIDCSigningAlgorithms has invalid EnabledSigningAlgs entry \"invalid\""
|
||||
invalidOIDCSigningAlgorithmCaseMsg := "provider ProviderIDInvalidOIDCSigningAlgorithmCase has invalid EnabledSigningAlgs entry \"rs256\""
|
||||
|
||||
DescribeTable("validateProviders",
|
||||
func(o *validateProvidersTableInput) {
|
||||
|
|
@ -79,5 +108,29 @@ var _ = Describe("Providers", func() {
|
|||
},
|
||||
errStrings: []string{skipButtonAndMultipleProvidersMsg},
|
||||
}),
|
||||
Entry("with valid OIDC signing algorithms", &validateProvidersTableInput{
|
||||
options: &options.Options{
|
||||
Providers: options.Providers{
|
||||
validOIDCSigningAlgorithmsProvider,
|
||||
},
|
||||
},
|
||||
errStrings: []string{},
|
||||
}),
|
||||
Entry("with an invalid OIDC signing algorithm", &validateProvidersTableInput{
|
||||
options: &options.Options{
|
||||
Providers: options.Providers{
|
||||
invalidOIDCSigningAlgorithmsProvider,
|
||||
},
|
||||
},
|
||||
errStrings: []string{invalidOIDCSigningAlgorithmMsg},
|
||||
}),
|
||||
Entry("with an OIDC signing algorithm using invalid casing", &validateProvidersTableInput{
|
||||
options: &options.Options{
|
||||
Providers: options.Providers{
|
||||
invalidOIDCSigningAlgorithmCaseProvider,
|
||||
},
|
||||
},
|
||||
errStrings: []string{invalidOIDCSigningAlgorithmCaseMsg},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ func newProviderDataFromConfig(providerConfig options.Provider) (*ProviderData,
|
|||
IssuerURL: providerConfig.OIDCConfig.IssuerURL,
|
||||
JWKsURL: providerConfig.OIDCConfig.JwksURL,
|
||||
PublicKeyFiles: providerConfig.OIDCConfig.PublicKeyFiles,
|
||||
SupportedSigningAlgs: providerConfig.OIDCConfig.EnabledSigningAlgs,
|
||||
SkipDiscovery: ptr.Deref(providerConfig.OIDCConfig.SkipDiscovery, options.DefaultSkipDiscovery),
|
||||
SkipIssuerVerification: ptr.Deref(providerConfig.OIDCConfig.InsecureSkipIssuerVerification, options.DefaultInsecureSkipIssuerVerification),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
package providers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/oauth2-proxy/mockoidc"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
|
||||
. "github.com/onsi/gomega"
|
||||
|
|
@ -121,6 +126,91 @@ func TestURLsCorrectlyParsed(t *testing.T) {
|
|||
g.Expect(pd.RedeemURL.String()).To(Equal(msTokenURL))
|
||||
}
|
||||
|
||||
func TestEnabledSigningAlgsAreAppliedToProviderVerifier(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
m, err := mockoidc.NewServer(nil)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
m.AddMiddleware(newSigningAlgsIssuerMiddleware(m, []string{"RS256", "HS256"}))
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(m.Start(listener, nil)).To(Succeed())
|
||||
defer func() {
|
||||
g.Expect(m.Shutdown()).To(Succeed())
|
||||
}()
|
||||
|
||||
providerConfig := options.Provider{
|
||||
ID: providerID,
|
||||
Type: "oidc",
|
||||
ClientID: m.Config().ClientID,
|
||||
ClientSecretFile: clientSecret,
|
||||
OIDCConfig: options.OIDCOptions{
|
||||
IssuerURL: m.Issuer(),
|
||||
AudienceClaims: []string{"aud"},
|
||||
EnabledSigningAlgs: []string{"HS256"},
|
||||
},
|
||||
}
|
||||
|
||||
pd, err := newProviderDataFromConfig(providerConfig)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
rawIDToken, err := m.Keypair.SignJWT(jwt.RegisteredClaims{
|
||||
Audience: jwt.ClaimStrings{m.Config().ClientID},
|
||||
Issuer: m.Issuer(),
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
Subject: "user",
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
_, err = pd.Verifier.Verify(context.Background(), rawIDToken)
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring("unexpected signature algorithm"))
|
||||
}
|
||||
|
||||
func TestEnabledSigningAlgsRejectUnsupportedTokens(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
m, err := mockoidc.Run()
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
defer func() {
|
||||
g.Expect(m.Shutdown()).To(Succeed())
|
||||
}()
|
||||
|
||||
providerConfig := options.Provider{
|
||||
ID: providerID,
|
||||
Type: "oidc",
|
||||
ClientID: m.Config().ClientID,
|
||||
ClientSecretFile: clientSecret,
|
||||
LoginURL: m.AuthorizationEndpoint(),
|
||||
RedeemURL: m.TokenEndpoint(),
|
||||
OIDCConfig: options.OIDCOptions{
|
||||
IssuerURL: m.Issuer(),
|
||||
SkipDiscovery: ptr.To(true),
|
||||
JwksURL: m.JWKSEndpoint(),
|
||||
AudienceClaims: []string{"aud"},
|
||||
EnabledSigningAlgs: []string{"HS256"},
|
||||
},
|
||||
}
|
||||
|
||||
pd, err := newProviderDataFromConfig(providerConfig)
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
rawIDToken, err := m.Keypair.SignJWT(jwt.RegisteredClaims{
|
||||
Audience: jwt.ClaimStrings{m.Config().ClientID},
|
||||
Issuer: m.Issuer(),
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
Subject: "user",
|
||||
})
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
_, err = pd.Verifier.Verify(context.Background(), rawIDToken)
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring("unexpected signature algorithm"))
|
||||
}
|
||||
|
||||
func TestScope(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
package providers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/mockoidc"
|
||||
. "github.com/onsi/gomega"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
|
@ -111,3 +114,33 @@ func Test_formatGroup(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newSigningAlgsIssuerMiddleware(m *mockoidc.MockOIDC, supportedSigningAlgs []string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
provider := 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"`
|
||||
SupportedSigningAlgs []string `json:"id_token_signing_alg_values_supported"`
|
||||
}{
|
||||
Issuer: m.Issuer(),
|
||||
AuthURL: m.AuthorizationEndpoint(),
|
||||
TokenURL: m.TokenEndpoint(),
|
||||
JWKsURL: m.JWKSEndpoint(),
|
||||
UserInfoURL: m.UserinfoEndpoint(),
|
||||
SupportedSigningAlgs: supportedSigningAlgs,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(provider)
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = rw.Write(data)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue