@@ -2,11 +2,14 @@ package oidc
8 "github.com/coreos/go-oidc/v3/oidc"
12 "github.com/coreos/go-oidc/v3/oidc"
9− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
10 k8serrors "k8s.io/apimachinery/pkg/util/errors"
13 k8serrors "k8s.io/apimachinery/pkg/util/errors"
@@ -38,6 +41,10 @@ type ProviderVerifierOptions struct {
38 // eg: https://www.googleapis.com/oauth2/v3/certs
41 // eg: https://www.googleapis.com/oauth2/v3/certs
39 JWKsURL string
42 JWKsURL string
44+ // PublicKeyFiles is a list of paths pointing to public key files in PEM format to use
45+ // for verifying JWT tokens
46+ PublicKeyFiles []string
41 // SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints
48 // SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints
42 SkipDiscovery bool
49 SkipDiscovery bool
@@ -59,8 +66,12 @@ func (p ProviderVerifierOptions) validate() error {
59 errs = append(errs, errors.New("missing required setting: issuer-url"))
66 errs = append(errs, errors.New("missing required setting: issuer-url"))
62− if p.SkipDiscovery && p.JWKsURL == "" {
69+ if p.SkipDiscovery && p.JWKsURL == "" && len(p.PublicKeyFiles) == 0 {
63− errs = append(errs, errors.New("missing required setting: jwks-url"))
70+ errs = append(errs, errors.New("missing required setting: jwks-url or public-key-files"))
73+ if p.JWKsURL != "" && len(p.PublicKeyFiles) > 0 {
74+ errs = append(errs, errors.New("mutually exclusive settings: jwks-url and public-key-files"))
66 if len(errs) > 0 {
77 if len(errs) > 0 {
@@ -91,12 +102,12 @@ func (p ProviderVerifierOptions) toOIDCConfig() *oidc.Config {
91// NewProviderVerifier constructs a ProviderVerifier from the options given.
102// NewProviderVerifier constructs a ProviderVerifier from the options given.
92func NewProviderVerifier(ctx context.Context, opts ProviderVerifierOptions) (ProviderVerifier, error) {
103func NewProviderVerifier(ctx context.Context, opts ProviderVerifierOptions) (ProviderVerifier, error) {
93 if err := opts.validate(); err != nil {
104 if err := opts.validate(); err != nil {
94− return nil, fmt.Errorf("invalid provider verifier options: %v", err)
105+ return nil, fmt.Errorf("invalid provider verifier options: %w", err)
97 verifierBuilder, provider, err := getVerifierBuilder(ctx, opts)
108 verifierBuilder, provider, err := getVerifierBuilder(ctx, opts)
98 if err != nil {
109 if err != nil {
99− return nil, fmt.Errorf("could not get verifier builder: %v", err)
110+ return nil, fmt.Errorf("could not get verifier builder: %w", err)
101 verifier := NewVerifier(verifierBuilder(opts.toOIDCConfig()), opts.toVerificationOptions())
112 verifier := NewVerifier(verifierBuilder(opts.toOIDCConfig()), opts.toVerificationOptions())
@@ -117,22 +128,78 @@ type verifierBuilder func(*oidc.Config) *oidc.IDTokenVerifier
118func getVerifierBuilder(ctx context.Context, opts ProviderVerifierOptions) (verifierBuilder, DiscoveryProvider, error) {
129func getVerifierBuilder(ctx context.Context, opts ProviderVerifierOptions) (verifierBuilder, DiscoveryProvider, error) {
119 if opts.SkipDiscovery {
130 if opts.SkipDiscovery {
131+ var keySet oidc.KeySet
134+ if opts.JWKsURL != "" {
135+ keySet = oidc.NewRemoteKeySet(ctx, opts.JWKsURL)
137+ keySet, err = newKeySetFromStatic(opts.PublicKeyFiles)
139+ return nil, nil, fmt.Errorf("error while parsing public keys: %w", err)
120 // Instead of discovering the JWKs URL, it needs to be specified in the opts already
142 // Instead of discovering the JWKs URL, it needs to be specified in the opts already
121− return newVerifierBuilder(ctx, opts.IssuerURL, opts.JWKsURL, opts.SupportedSigningAlgs), nil, nil
143+ return newVerifierBuilder(
146+ opts.SupportedSigningAlgs,
124 provider, err := NewProvider(ctx, opts.IssuerURL, opts.SkipIssuerVerification)
150 provider, err := NewProvider(ctx, opts.IssuerURL, opts.SkipIssuerVerification)
125 if err != nil {
151 if err != nil {
126− return nil, nil, fmt.Errorf("error while discovery OIDC configuration: %v", err)
152+ return nil, nil, fmt.Errorf("error while discovery OIDC configuration: %w", err)
155+ return newVerifierBuilder(
157+ oidc.NewRemoteKeySet(ctx, provider.Endpoints().JWKsURL),
158+ provider.SupportedSigningAlgs(),
162+// GetPublicKeyFromBytes parses a PEM-encoded public key from a byte array
163+// and returns a crypto.PublicKey object.
164+func getPublicKeyFromBytes(bytes []byte) (crypto.PublicKey, error) {
165+ block, _ := pem.Decode(bytes)
167+ return nil, errors.New("failed to parse PEM block containing the public key")
169+ publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
171+ return nil, fmt.Errorf("failed to parse public key: %w", err)
128− verifierBuilder := newVerifierBuilder(ctx, opts.IssuerURL, provider.Endpoints().JWKsURL, provider.SupportedSigningAlgs())
173+
129− return verifierBuilder, provider, nil
174+ cryptoPublicKey, ok := publicKey.(crypto.PublicKey)
176+ return nil, errors.New("failed to cast public key to crypto.PublicKey")
179+ return cryptoPublicKey, nil
182+// newKeySetFromStatic create a StaticKeySet from a set of files
183+func newKeySetFromStatic(keys []string) (*oidc.StaticKeySet, error) {
184+ keySet := []crypto.PublicKey{}
185+ for _, keyFile := range keys {
186+ bytes, err := os.ReadFile(keyFile)
188+ return nil, fmt.Errorf("failed to read file: %w", err)
191+ publicKey, err := getPublicKeyFromBytes(bytes)
193+ return nil, fmt.Errorf("failed to create keys: %w", err)
195+ keySet = append(keySet, publicKey)
198+ return &oidc.StaticKeySet{PublicKeys: keySet}, nil
132// newVerifierBuilder returns a function to create a IDToken verifier from an OIDC config.
201// newVerifierBuilder returns a function to create a IDToken verifier from an OIDC config.
133−func newVerifierBuilder(ctx context.Context, issuerURL, jwksURL string, supportedSigningAlgs []string) verifierBuilder {
202+func newVerifierBuilder(issuerURL string, keySet oidc.KeySet, supportedSigningAlgs []string) verifierBuilder {
134− ctx = oidc.ClientContext(ctx, requests.DefaultHTTPClient)
135− keySet := oidc.NewRemoteKeySet(ctx, jwksURL)
136 return func(oidcConfig *oidc.Config) *oidc.IDTokenVerifier {
203 return func(oidcConfig *oidc.Config) *oidc.IDTokenVerifier {
137 if len(supportedSigningAlgs) > 0 {
204 if len(supportedSigningAlgs) > 0 {
138 oidcConfig.SupportedSigningAlgs = supportedSigningAlgs
205 oidcConfig.SupportedSigningAlgs = supportedSigningAlgs