1package vault
2
3import (
4	"context"
5	"crypto/ecdsa"
6	"crypto/elliptic"
7	"crypto/rand"
8	"crypto/rsa"
9	"encoding/base64"
10	"encoding/json"
11	"fmt"
12	"net/url"
13	"strings"
14	"time"
15
16	"github.com/hashicorp/errwrap"
17	"github.com/hashicorp/go-hclog"
18	"github.com/hashicorp/go-uuid"
19	"github.com/hashicorp/vault/helper/identity"
20	"github.com/hashicorp/vault/helper/namespace"
21	"github.com/hashicorp/vault/sdk/framework"
22	"github.com/hashicorp/vault/sdk/helper/base62"
23	"github.com/hashicorp/vault/sdk/helper/strutil"
24	"github.com/hashicorp/vault/sdk/logical"
25	"github.com/patrickmn/go-cache"
26	"golang.org/x/crypto/ed25519"
27	"gopkg.in/square/go-jose.v2"
28	"gopkg.in/square/go-jose.v2/jwt"
29)
30
31type oidcConfig struct {
32	Issuer string `json:"issuer"`
33
34	// effectiveIssuer is a calculated field and will be either Issuer (if
35	// that's set) or the Vault instance's api_addr.
36	effectiveIssuer string
37}
38
39type expireableKey struct {
40	KeyID    string    `json:"key_id"`
41	ExpireAt time.Time `json:"expire_at"`
42}
43
44type namedKey struct {
45	name             string
46	Algorithm        string           `json:"signing_algorithm"`
47	VerificationTTL  time.Duration    `json:"verification_ttl"`
48	RotationPeriod   time.Duration    `json:"rotation_period"`
49	KeyRing          []*expireableKey `json:"key_ring"`
50	SigningKey       *jose.JSONWebKey `json:"signing_key"`
51	NextRotation     time.Time        `json:"next_rotation"`
52	AllowedClientIDs []string         `json:"allowed_client_ids"`
53}
54
55type role struct {
56	TokenTTL time.Duration `json:"token_ttl"`
57	Key      string        `json:"key"`
58	Template string        `json:"template"`
59	ClientID string        `json:"client_id"`
60}
61
62// idToken contains the required OIDC fields.
63//
64// Templated claims will be merged into the final output. Those claims may
65// include top-level keys, but those keys may not overwrite any of the
66// required OIDC fields.
67type idToken struct {
68	Issuer    string `json:"iss"`       // api_addr or custom Issuer
69	Namespace string `json:"namespace"` // Namespace of issuer
70	Subject   string `json:"sub"`       // Entity ID
71	Audience  string `json:"aud"`       // role ID will be used here.
72	Expiry    int64  `json:"exp"`       // Expiration, as determined by the role.
73	IssuedAt  int64  `json:"iat"`       // Time of token creation
74}
75
76// discovery contains a subset of the required elements of OIDC discovery needed
77// for JWT verification libraries to use the .well-known endpoint.
78//
79// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
80type discovery struct {
81	Issuer      string   `json:"issuer"`
82	Keys        string   `json:"jwks_uri"`
83	Subjects    []string `json:"subject_types_supported"`
84	IDTokenAlgs []string `json:"id_token_signing_alg_values_supported"`
85}
86
87// oidcCache is a thin wrapper around go-cache to partition by namespace
88type oidcCache struct {
89	c *cache.Cache
90}
91
92const (
93	issuerPath           = "identity/oidc"
94	oidcTokensPrefix     = "oidc_tokens/"
95	oidcConfigStorageKey = oidcTokensPrefix + "config/"
96	namedKeyConfigPath   = oidcTokensPrefix + "named_keys/"
97	publicKeysConfigPath = oidcTokensPrefix + "public_keys/"
98	roleConfigPath       = oidcTokensPrefix + "roles/"
99)
100
101var requiredClaims = []string{"iat", "aud", "exp", "iss", "sub", "namespace"}
102var supportedAlgs = []string{
103	string(jose.RS256),
104	string(jose.RS384),
105	string(jose.RS512),
106	string(jose.ES256),
107	string(jose.ES384),
108	string(jose.ES512),
109	string(jose.EdDSA),
110}
111
112// pseudo-namespace for cache items that don't belong to any real namespace.
113var nilNamespace = &namespace.Namespace{ID: "__NIL_NAMESPACE"}
114
115func oidcPaths(i *IdentityStore) []*framework.Path {
116	return []*framework.Path{
117		{
118			Pattern: "oidc/config/?$",
119			Fields: map[string]*framework.FieldSchema{
120				"issuer": {
121					Type:        framework.TypeString,
122					Description: "Issuer URL to be used in the iss claim of the token. If not set, Vault's app_addr will be used.",
123				},
124			},
125			Callbacks: map[logical.Operation]framework.OperationFunc{
126				logical.ReadOperation:   i.pathOIDCReadConfig,
127				logical.UpdateOperation: i.pathOIDCUpdateConfig,
128			},
129			HelpSynopsis:    "OIDC configuration",
130			HelpDescription: "Update OIDC configuration in the identity backend",
131		},
132		{
133			Pattern: "oidc/key/" + framework.GenericNameRegex("name"),
134			Fields: map[string]*framework.FieldSchema{
135				"name": {
136					Type:        framework.TypeString,
137					Description: "Name of the key",
138				},
139
140				"rotation_period": {
141					Type:        framework.TypeDurationSecond,
142					Description: "How often to generate a new keypair.",
143					Default:     "24h",
144				},
145
146				"verification_ttl": {
147					Type:        framework.TypeDurationSecond,
148					Description: "Controls how long the public portion of a key will be available for verification after being rotated.",
149					Default:     "24h",
150				},
151
152				"algorithm": {
153					Type:        framework.TypeString,
154					Description: "Signing algorithm to use. This will default to RS256.",
155					Default:     "RS256",
156				},
157
158				"allowed_client_ids": &framework.FieldSchema{
159					Type:        framework.TypeCommaStringSlice,
160					Description: "Comma separated string or array of role client ids allowed to use this key for signing. If empty no roles are allowed. If \"*\" all roles are allowed.",
161				},
162			},
163			Callbacks: map[logical.Operation]framework.OperationFunc{
164				logical.CreateOperation: i.pathOIDCCreateUpdateKey,
165				logical.UpdateOperation: i.pathOIDCCreateUpdateKey,
166				logical.ReadOperation:   i.pathOIDCReadKey,
167				logical.DeleteOperation: i.pathOIDCDeleteKey,
168			},
169			ExistenceCheck:  i.pathOIDCKeyExistenceCheck,
170			HelpSynopsis:    "CRUD operations for OIDC keys.",
171			HelpDescription: "Create, Read, Update, and Delete OIDC named keys.",
172		},
173		{
174			Pattern: "oidc/key/" + framework.GenericNameRegex("name") + "/rotate/?$",
175			Fields: map[string]*framework.FieldSchema{
176				"name": {
177					Type:        framework.TypeString,
178					Description: "Name of the key",
179				},
180				"verification_ttl": {
181					Type:        framework.TypeDurationSecond,
182					Description: "Controls how long the public portion of a key will be available for verification after being rotated. Setting verification_ttl here will override the verification_ttl set on the key.",
183				},
184			},
185			Callbacks: map[logical.Operation]framework.OperationFunc{
186				logical.UpdateOperation: i.pathOIDCRotateKey,
187			},
188			HelpSynopsis:    "Rotate a named OIDC key.",
189			HelpDescription: "Manually rotate a named OIDC key. Rotating a named key will cause a new underlying signing key to be generated. The public portion of the underlying rotated signing key will continue to live for the verification_ttl duration.",
190		},
191		{
192			Pattern: "oidc/key/?$",
193			Callbacks: map[logical.Operation]framework.OperationFunc{
194				logical.ListOperation: i.pathOIDCListKey,
195			},
196			HelpSynopsis:    "List OIDC keys",
197			HelpDescription: "List all named OIDC keys",
198		},
199		{
200			Pattern: "oidc/.well-known/openid-configuration/?$",
201			Callbacks: map[logical.Operation]framework.OperationFunc{
202				logical.ReadOperation: i.pathOIDCDiscovery,
203			},
204			HelpSynopsis:    "Query OIDC configurations",
205			HelpDescription: "Query this path to retrieve the configured OIDC Issuer and Keys endpoints, Subjects, and signing algorithms used by the OIDC backend.",
206		},
207		{
208			Pattern: "oidc/.well-known/keys/?$",
209			Callbacks: map[logical.Operation]framework.OperationFunc{
210				logical.ReadOperation: i.pathOIDCReadPublicKeys,
211			},
212			HelpSynopsis:    "Retrieve public keys",
213			HelpDescription: "Query this path to retrieve the public portion of keys used to sign OIDC tokens. Clients can use this to validate the authenticity of the OIDC token claims.",
214		},
215		{
216			Pattern: "oidc/token/" + framework.GenericNameRegex("name"),
217			Fields: map[string]*framework.FieldSchema{
218				"name": {
219					Type:        framework.TypeString,
220					Description: "Name of the role",
221				},
222			},
223			Callbacks: map[logical.Operation]framework.OperationFunc{
224				logical.ReadOperation: i.pathOIDCGenerateToken,
225			},
226			HelpSynopsis:    "Generate an OIDC token",
227			HelpDescription: "Generate an OIDC token against a configured role. The vault token used to call this path must have a corresponding entity.",
228		},
229		{
230			Pattern: "oidc/role/" + framework.GenericNameRegex("name"),
231			Fields: map[string]*framework.FieldSchema{
232				"name": {
233					Type:        framework.TypeString,
234					Description: "Name of the role",
235				},
236				"key": {
237					Type:        framework.TypeString,
238					Description: "The OIDC key to use for generating tokens. The specified key must already exist.",
239				},
240				"template": {
241					Type:        framework.TypeString,
242					Description: "The template string to use for generating tokens. This may be in string-ified JSON or base64 format.",
243				},
244				"ttl": {
245					Type:        framework.TypeDurationSecond,
246					Description: "TTL of the tokens generated against the role.",
247					Default:     "24h",
248				},
249			},
250			Callbacks: map[logical.Operation]framework.OperationFunc{
251				logical.UpdateOperation: i.pathOIDCCreateUpdateRole,
252				logical.CreateOperation: i.pathOIDCCreateUpdateRole,
253				logical.ReadOperation:   i.pathOIDCReadRole,
254				logical.DeleteOperation: i.pathOIDCDeleteRole,
255			},
256			ExistenceCheck:  i.pathOIDCRoleExistenceCheck,
257			HelpSynopsis:    "CRUD operations on OIDC Roles",
258			HelpDescription: "Create, Read, Update, and Delete OIDC Roles. OIDC tokens are generated against roles which can be configured to determine how OIDC tokens are generated.",
259		},
260		{
261			Pattern: "oidc/role/?$",
262			Callbacks: map[logical.Operation]framework.OperationFunc{
263				logical.ListOperation: i.pathOIDCListRole,
264			},
265			HelpSynopsis:    "List configured OIDC roles",
266			HelpDescription: "List all configured OIDC roles in the identity backend.",
267		},
268		{
269			Pattern: "oidc/introspect/?$",
270			Fields: map[string]*framework.FieldSchema{
271				"token": {
272					Type:        framework.TypeString,
273					Description: "Token to verify",
274				},
275				"client_id": {
276					Type:        framework.TypeString,
277					Description: "Optional client_id to verify",
278				},
279			},
280			Callbacks: map[logical.Operation]framework.OperationFunc{
281				logical.UpdateOperation: i.pathOIDCIntrospect,
282			},
283			HelpSynopsis:    "Verify the authenticity of an OIDC token",
284			HelpDescription: "Use this path to verify the authenticity of an OIDC token and whether the associated entity is active and enabled.",
285		},
286	}
287}
288
289func (i *IdentityStore) pathOIDCReadConfig(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
290	c, err := i.getOIDCConfig(ctx, req.Storage)
291	if err != nil {
292		return nil, err
293	}
294
295	if c == nil {
296		return nil, nil
297	}
298
299	resp := &logical.Response{
300		Data: map[string]interface{}{
301			"issuer": c.Issuer,
302		},
303	}
304
305	if i.core.redirectAddr == "" && c.Issuer == "" {
306		resp.AddWarning(`Both "issuer" and Vault's "api_addr" are empty. ` +
307			`The issuer claim in generated tokens will not be network reachable.`)
308	}
309
310	return resp, nil
311}
312
313func (i *IdentityStore) pathOIDCUpdateConfig(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
314	var resp *logical.Response
315
316	ns, err := namespace.FromContext(ctx)
317	if err != nil {
318		return nil, err
319	}
320
321	issuerRaw, ok := d.GetOk("issuer")
322	if !ok {
323		return nil, nil
324	}
325
326	issuer := issuerRaw.(string)
327
328	if issuer != "" {
329		// verify that issuer is the correct format:
330		//   - http or https
331		//   - host name
332		//   - optional port
333		//   - nothing more
334		valid := false
335		if u, err := url.Parse(issuer); err == nil {
336			u2 := url.URL{
337				Scheme: u.Scheme,
338				Host:   u.Host,
339			}
340			valid = (*u == u2) &&
341				(u.Scheme == "http" || u.Scheme == "https") &&
342				u.Host != ""
343		}
344
345		if !valid {
346			return logical.ErrorResponse(
347				"invalid issuer, which must include only a scheme, host, " +
348					"and optional port (e.g. https://example.com:8200)"), nil
349		}
350
351		resp = &logical.Response{
352			Warnings: []string{`If "issuer" is set explicitly, all tokens must be ` +
353				`validated against that address, including those issued by secondary ` +
354				`clusters. Setting issuer to "" will restore the default behavior of ` +
355				`using the cluster's api_addr as the issuer.`},
356		}
357	}
358
359	c := oidcConfig{
360		Issuer: issuer,
361	}
362
363	entry, err := logical.StorageEntryJSON(oidcConfigStorageKey, c)
364	if err != nil {
365		return nil, err
366	}
367
368	if err := req.Storage.Put(ctx, entry); err != nil {
369		return nil, err
370	}
371
372	i.oidcCache.Flush(ns)
373
374	return resp, nil
375}
376
377func (i *IdentityStore) getOIDCConfig(ctx context.Context, s logical.Storage) (*oidcConfig, error) {
378	ns, err := namespace.FromContext(ctx)
379	if err != nil {
380		return nil, err
381	}
382
383	if v, ok := i.oidcCache.Get(ns, "config"); ok {
384		return v.(*oidcConfig), nil
385	}
386
387	var c oidcConfig
388	entry, err := s.Get(ctx, oidcConfigStorageKey)
389	if err != nil {
390		return nil, err
391	}
392
393	if entry != nil {
394		if err := entry.DecodeJSON(&c); err != nil {
395			return nil, err
396		}
397	}
398
399	c.effectiveIssuer = c.Issuer
400	if c.effectiveIssuer == "" {
401		c.effectiveIssuer = i.core.redirectAddr
402	}
403
404	c.effectiveIssuer += "/v1/" + ns.Path + issuerPath
405
406	i.oidcCache.SetDefault(ns, "config", &c)
407
408	return &c, nil
409}
410
411// handleOIDCCreateKey is used to create a new named key or update an existing one
412func (i *IdentityStore) pathOIDCCreateUpdateKey(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
413	ns, err := namespace.FromContext(ctx)
414	if err != nil {
415		return nil, err
416	}
417
418	defer i.oidcCache.Flush(ns)
419
420	name := d.Get("name").(string)
421
422	i.oidcLock.Lock()
423	defer i.oidcLock.Unlock()
424
425	var key namedKey
426	if req.Operation == logical.UpdateOperation {
427		entry, err := req.Storage.Get(ctx, namedKeyConfigPath+name)
428		if err != nil {
429			return nil, err
430		}
431		if entry != nil {
432			if err := entry.DecodeJSON(&key); err != nil {
433				return nil, err
434			}
435		}
436	}
437
438	if rotationPeriodRaw, ok := d.GetOk("rotation_period"); ok {
439		key.RotationPeriod = time.Duration(rotationPeriodRaw.(int)) * time.Second
440	} else if req.Operation == logical.CreateOperation {
441		key.RotationPeriod = time.Duration(d.Get("rotation_period").(int)) * time.Second
442	}
443
444	if key.RotationPeriod < 1*time.Minute {
445		return logical.ErrorResponse("rotation_period must be at least one minute"), nil
446	}
447
448	if verificationTTLRaw, ok := d.GetOk("verification_ttl"); ok {
449		key.VerificationTTL = time.Duration(verificationTTLRaw.(int)) * time.Second
450	} else if req.Operation == logical.CreateOperation {
451		key.VerificationTTL = time.Duration(d.Get("verification_ttl").(int)) * time.Second
452	}
453
454	if key.VerificationTTL > 10*key.RotationPeriod {
455		return logical.ErrorResponse("verification_ttl cannot be longer than 10x rotation_period"), nil
456	}
457
458	if allowedClientIDsRaw, ok := d.GetOk("allowed_client_ids"); ok {
459		key.AllowedClientIDs = allowedClientIDsRaw.([]string)
460	} else if req.Operation == logical.CreateOperation {
461		key.AllowedClientIDs = d.Get("allowed_client_ids").([]string)
462	}
463
464	prevAlgorithm := key.Algorithm
465	if algorithm, ok := d.GetOk("algorithm"); ok {
466		key.Algorithm = algorithm.(string)
467	} else if req.Operation == logical.CreateOperation {
468		key.Algorithm = d.Get("algorithm").(string)
469	}
470
471	if !strutil.StrListContains(supportedAlgs, key.Algorithm) {
472		return logical.ErrorResponse("unknown signing algorithm %q", key.Algorithm), nil
473	}
474
475	// Update next rotation time if it is unset or now earlier than previously set.
476	nextRotation := time.Now().Add(key.RotationPeriod)
477	if key.NextRotation.IsZero() || nextRotation.Before(key.NextRotation) {
478		key.NextRotation = nextRotation
479	}
480
481	// generate keys if creating a new key or changing algorithms
482	if key.Algorithm != prevAlgorithm {
483		signingKey, err := generateKeys(key.Algorithm)
484		if err != nil {
485			return nil, err
486		}
487
488		key.SigningKey = signingKey
489		key.KeyRing = append(key.KeyRing, &expireableKey{KeyID: signingKey.Public().KeyID})
490
491		if err := saveOIDCPublicKey(ctx, req.Storage, signingKey.Public()); err != nil {
492			return nil, err
493		}
494	}
495
496	// store named key
497	entry, err := logical.StorageEntryJSON(namedKeyConfigPath+name, key)
498	if err != nil {
499		return nil, err
500	}
501
502	if err := req.Storage.Put(ctx, entry); err != nil {
503		return nil, err
504	}
505
506	return nil, nil
507}
508
509// handleOIDCReadKey is used to read an existing key
510func (i *IdentityStore) pathOIDCReadKey(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
511	name := d.Get("name").(string)
512
513	i.oidcLock.RLock()
514	defer i.oidcLock.RUnlock()
515
516	entry, err := req.Storage.Get(ctx, namedKeyConfigPath+name)
517	if err != nil {
518		return nil, err
519	}
520	if entry == nil {
521		return logical.ErrorResponse("no named key found at %q", name), nil
522	}
523
524	var storedNamedKey namedKey
525	if err := entry.DecodeJSON(&storedNamedKey); err != nil {
526		return nil, err
527	}
528	return &logical.Response{
529		Data: map[string]interface{}{
530			"rotation_period":    int64(storedNamedKey.RotationPeriod.Seconds()),
531			"verification_ttl":   int64(storedNamedKey.VerificationTTL.Seconds()),
532			"algorithm":          storedNamedKey.Algorithm,
533			"allowed_client_ids": storedNamedKey.AllowedClientIDs,
534		},
535	}, nil
536}
537
538// handleOIDCDeleteKey is used to delete a key
539func (i *IdentityStore) pathOIDCDeleteKey(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
540	ns, err := namespace.FromContext(ctx)
541	if err != nil {
542		return nil, err
543	}
544
545	targetKeyName := d.Get("name").(string)
546
547	i.oidcLock.Lock()
548
549	// it is an error to delete a key that is actively referenced by a role
550	roleNames, err := req.Storage.List(ctx, roleConfigPath)
551	if err != nil {
552		return nil, err
553	}
554
555	var role *role
556	rolesReferencingTargetKeyName := make([]string, 0)
557	for _, roleName := range roleNames {
558		entry, err := req.Storage.Get(ctx, roleConfigPath+roleName)
559		if err != nil {
560			return nil, err
561		}
562		if entry != nil {
563			if err := entry.DecodeJSON(&role); err != nil {
564				return nil, err
565			}
566			if role.Key == targetKeyName {
567				rolesReferencingTargetKeyName = append(rolesReferencingTargetKeyName, roleName)
568			}
569		}
570	}
571
572	if len(rolesReferencingTargetKeyName) > 0 {
573		errorMessage := fmt.Sprintf("unable to delete key %q because it is currently referenced by these roles: %s",
574			targetKeyName, strings.Join(rolesReferencingTargetKeyName, ", "))
575		i.oidcLock.Unlock()
576		return logical.ErrorResponse(errorMessage), logical.ErrInvalidRequest
577	}
578
579	// key can safely be deleted now
580	err = req.Storage.Delete(ctx, namedKeyConfigPath+targetKeyName)
581	if err != nil {
582		return nil, err
583	}
584
585	i.oidcLock.Unlock()
586
587	_, err = i.expireOIDCPublicKeys(ctx, req.Storage)
588	if err != nil {
589		return nil, err
590	}
591
592	i.oidcCache.Flush(ns)
593
594	return nil, nil
595}
596
597// handleOIDCListKey is used to list named keys
598func (i *IdentityStore) pathOIDCListKey(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
599	i.oidcLock.RLock()
600	defer i.oidcLock.RUnlock()
601
602	keys, err := req.Storage.List(ctx, namedKeyConfigPath)
603	if err != nil {
604		return nil, err
605	}
606	return logical.ListResponse(keys), nil
607}
608
609// pathOIDCRotateKey is used to manually trigger a rotation on the named key
610func (i *IdentityStore) pathOIDCRotateKey(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
611	ns, err := namespace.FromContext(ctx)
612	if err != nil {
613		return nil, err
614	}
615
616	name := d.Get("name").(string)
617
618	i.oidcLock.Lock()
619	defer i.oidcLock.Unlock()
620
621	// load the named key and perform a rotation
622	entry, err := req.Storage.Get(ctx, namedKeyConfigPath+name)
623	if err != nil {
624		return nil, err
625	}
626	if entry == nil {
627		return logical.ErrorResponse("no named key found at %q", name), logical.ErrInvalidRequest
628	}
629
630	var storedNamedKey namedKey
631	if err := entry.DecodeJSON(&storedNamedKey); err != nil {
632		return nil, err
633	}
634	storedNamedKey.name = name
635
636	// call rotate with an appropriate overrideTTL where < 0 means no override
637	verificationTTLOverride := -1 * time.Second
638
639	if ttlRaw, ok := d.GetOk("verification_ttl"); ok {
640		verificationTTLOverride = time.Duration(ttlRaw.(int)) * time.Second
641	}
642
643	if err := storedNamedKey.rotate(ctx, req.Storage, verificationTTLOverride); err != nil {
644		return nil, err
645	}
646
647	i.oidcCache.Flush(ns)
648
649	return nil, nil
650}
651
652func (i *IdentityStore) pathOIDCKeyExistenceCheck(ctx context.Context, req *logical.Request, d *framework.FieldData) (bool, error) {
653	name := d.Get("name").(string)
654
655	i.oidcLock.RLock()
656	defer i.oidcLock.RUnlock()
657
658	entry, err := req.Storage.Get(ctx, namedKeyConfigPath+name)
659	if err != nil {
660		return false, err
661	}
662
663	return entry != nil, nil
664}
665
666// handleOIDCGenerateSignToken generates and signs an OIDC token
667func (i *IdentityStore) pathOIDCGenerateToken(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
668	ns, err := namespace.FromContext(ctx)
669	if err != nil {
670		return nil, err
671	}
672
673	roleName := d.Get("name").(string)
674
675	role, err := i.getOIDCRole(ctx, req.Storage, roleName)
676	if err != nil {
677		return nil, err
678	}
679	if role == nil {
680		return logical.ErrorResponse("role %q not found", roleName), nil
681	}
682
683	var key *namedKey
684
685	if keyRaw, found := i.oidcCache.Get(ns, "namedKeys/"+role.Key); found {
686		key = keyRaw.(*namedKey)
687	} else {
688		entry, _ := req.Storage.Get(ctx, namedKeyConfigPath+role.Key)
689		if entry == nil {
690			return logical.ErrorResponse("key %q not found", role.Key), nil
691		}
692
693		if err := entry.DecodeJSON(&key); err != nil {
694			return nil, err
695		}
696
697		i.oidcCache.SetDefault(ns, "namedKeys/"+role.Key, key)
698	}
699	// Validate that the role is allowed to sign with its key (the key could have been updated)
700	if !strutil.StrListContains(key.AllowedClientIDs, "*") && !strutil.StrListContains(key.AllowedClientIDs, role.ClientID) {
701		return logical.ErrorResponse("the key %q does not list the client ID of the role %q as an allowed client ID", role.Key, roleName), nil
702	}
703
704	// generate an OIDC token from entity data
705	if req.EntityID == "" {
706		return logical.ErrorResponse("no entity associated with the request's token"), nil
707	}
708
709	config, err := i.getOIDCConfig(ctx, req.Storage)
710	if err != nil {
711		return nil, err
712	}
713
714	now := time.Now()
715	idToken := idToken{
716		Issuer:    config.effectiveIssuer,
717		Namespace: ns.ID,
718		Subject:   req.EntityID,
719		Audience:  role.ClientID,
720		Expiry:    now.Add(role.TokenTTL).Unix(),
721		IssuedAt:  now.Unix(),
722	}
723
724	e, err := i.MemDBEntityByID(req.EntityID, true)
725	if err != nil {
726		return nil, err
727	}
728	if e == nil {
729		return nil, fmt.Errorf("error loading entity ID %q", req.EntityID)
730	}
731
732	groups, inheritedGroups, err := i.groupsByEntityID(e.ID)
733	if err != nil {
734		return nil, err
735	}
736
737	groups = append(groups, inheritedGroups...)
738
739	payload, err := idToken.generatePayload(i.Logger(), role.Template, e, groups)
740	if err != nil {
741		i.Logger().Warn("error populating OIDC token template", "error", err)
742	}
743
744	signedIdToken, err := key.signPayload(payload)
745	if err != nil {
746		return nil, errwrap.Wrapf("error signing OIDC token: {{err}}", err)
747	}
748
749	return &logical.Response{
750		Data: map[string]interface{}{
751			"token":     signedIdToken,
752			"client_id": role.ClientID,
753			"ttl":       int64(role.TokenTTL.Seconds()),
754		},
755	}, nil
756}
757
758func (tok *idToken) generatePayload(logger hclog.Logger, template string, entity *identity.Entity, groups []*identity.Group) ([]byte, error) {
759	output := map[string]interface{}{
760		"iss":       tok.Issuer,
761		"namespace": tok.Namespace,
762		"sub":       tok.Subject,
763		"aud":       tok.Audience,
764		"exp":       tok.Expiry,
765		"iat":       tok.IssuedAt,
766	}
767
768	// Parse and integrate the populated role template. Structural errors with the template _should_
769	// be caught during role configuration. Error found during runtime will be logged, but they will
770	// not block generation of the basic ID token. They should not be returned to the requester.
771	_, populatedTemplate, err := identity.PopulateString(identity.PopulateStringInput{
772		Mode:   identity.JSONTemplating,
773		String: template,
774		Entity: entity,
775		Groups: groups,
776		// namespace?
777	})
778
779	if err != nil {
780		logger.Warn("error populating OIDC token template", "template", template, "error", err)
781	}
782
783	if populatedTemplate != "" {
784		var parsed map[string]interface{}
785		if err := json.Unmarshal([]byte(populatedTemplate), &parsed); err != nil {
786			logger.Warn("error parsing OIDC template", "template", template, "err", err)
787		}
788
789		for k, v := range parsed {
790			if !strutil.StrListContains(requiredClaims, k) {
791				output[k] = v
792			} else {
793				logger.Warn("invalid top level OIDC template key", "template", template, "key", k)
794			}
795		}
796	}
797
798	payload, err := json.Marshal(output)
799	if err != nil {
800		return nil, err
801	}
802
803	return payload, nil
804}
805
806func (k *namedKey) signPayload(payload []byte) (string, error) {
807	signingKey := jose.SigningKey{Key: k.SigningKey, Algorithm: jose.SignatureAlgorithm(k.Algorithm)}
808	signer, err := jose.NewSigner(signingKey, &jose.SignerOptions{})
809	if err != nil {
810		return "", err
811	}
812
813	signature, err := signer.Sign(payload)
814	if err != nil {
815		return "", err
816	}
817
818	signedIdToken, err := signature.CompactSerialize()
819	if err != nil {
820		return "", err
821	}
822
823	return signedIdToken, nil
824}
825
826func (i *IdentityStore) pathOIDCRoleExistenceCheck(ctx context.Context, req *logical.Request, d *framework.FieldData) (bool, error) {
827	role, err := i.getOIDCRole(ctx, req.Storage, d.Get("name").(string))
828	if err != nil {
829		return false, err
830	}
831
832	return role != nil, nil
833}
834
835// handleOIDCCreateRole is used to create a new role or update an existing one
836func (i *IdentityStore) pathOIDCCreateUpdateRole(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
837	ns, err := namespace.FromContext(ctx)
838	if err != nil {
839		return nil, err
840	}
841
842	name := d.Get("name").(string)
843
844	var role role
845	if req.Operation == logical.UpdateOperation {
846		entry, err := req.Storage.Get(ctx, roleConfigPath+name)
847		if err != nil {
848			return nil, err
849		}
850		if entry != nil {
851			if err := entry.DecodeJSON(&role); err != nil {
852				return nil, err
853			}
854		}
855	}
856
857	if key, ok := d.GetOk("key"); ok {
858		role.Key = key.(string)
859	} else if req.Operation == logical.CreateOperation {
860		role.Key = d.Get("key").(string)
861	}
862
863	if template, ok := d.GetOk("template"); ok {
864		role.Template = template.(string)
865	} else if req.Operation == logical.CreateOperation {
866		role.Template = d.Get("template").(string)
867	}
868
869	// Attempt to decode as base64 and use that if it works
870	if decoded, err := base64.StdEncoding.DecodeString(role.Template); err == nil {
871		role.Template = string(decoded)
872	}
873
874	// Validate that template can be parsed and results in valid JSON
875	if role.Template != "" {
876		_, populatedTemplate, err := identity.PopulateString(identity.PopulateStringInput{
877			Mode:   identity.JSONTemplating,
878			String: role.Template,
879			Entity: new(identity.Entity),
880			Groups: make([]*identity.Group, 0),
881			// namespace?
882		})
883
884		if err != nil {
885			return logical.ErrorResponse("error parsing template: %s", err.Error()), nil
886		}
887
888		var tmp map[string]interface{}
889		if err := json.Unmarshal([]byte(populatedTemplate), &tmp); err != nil {
890			return logical.ErrorResponse("error parsing template JSON: %s", err.Error()), nil
891		}
892
893		for key := range tmp {
894			if strutil.StrListContains(requiredClaims, key) {
895				return logical.ErrorResponse(`top level key %q not allowed. Restricted keys: %s`,
896					key, strings.Join(requiredClaims, ", ")), nil
897			}
898		}
899	}
900
901	if ttl, ok := d.GetOk("ttl"); ok {
902		role.TokenTTL = time.Duration(ttl.(int)) * time.Second
903	} else if req.Operation == logical.CreateOperation {
904		role.TokenTTL = time.Duration(d.Get("ttl").(int)) * time.Second
905	}
906
907	// create role path
908	if role.ClientID == "" {
909		clientID, err := base62.Random(26)
910		if err != nil {
911			return nil, err
912		}
913		role.ClientID = clientID
914	}
915
916	// store role (which was either just created or updated)
917	entry, err := logical.StorageEntryJSON(roleConfigPath+name, role)
918	if err != nil {
919		return nil, err
920	}
921	if err := req.Storage.Put(ctx, entry); err != nil {
922		return nil, err
923	}
924
925	i.oidcCache.Flush(ns)
926	return nil, nil
927}
928
929// handleOIDCReadRole is used to read an existing role
930func (i *IdentityStore) pathOIDCReadRole(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
931	name := d.Get("name").(string)
932
933	role, err := i.getOIDCRole(ctx, req.Storage, name)
934	if err != nil {
935		return nil, err
936	}
937	if role == nil {
938		return nil, nil
939	}
940
941	return &logical.Response{
942		Data: map[string]interface{}{
943			"client_id": role.ClientID,
944			"key":       role.Key,
945			"template":  role.Template,
946			"ttl":       int64(role.TokenTTL.Seconds()),
947		},
948	}, nil
949}
950
951func (i *IdentityStore) getOIDCRole(ctx context.Context, s logical.Storage, roleName string) (*role, error) {
952	entry, err := s.Get(ctx, roleConfigPath+roleName)
953	if err != nil {
954		return nil, err
955	}
956
957	if entry == nil {
958		return nil, nil
959	}
960
961	var role role
962	if err := entry.DecodeJSON(&role); err != nil {
963		return nil, err
964	}
965
966	return &role, nil
967}
968
969// handleOIDCDeleteRole is used to delete a role if it exists
970func (i *IdentityStore) pathOIDCDeleteRole(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
971	name := d.Get("name").(string)
972	err := req.Storage.Delete(ctx, roleConfigPath+name)
973	if err != nil {
974		return nil, err
975	}
976	return nil, nil
977}
978
979// handleOIDCListRole is used to list stored a roles
980func (i *IdentityStore) pathOIDCListRole(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
981	roles, err := req.Storage.List(ctx, roleConfigPath)
982	if err != nil {
983		return nil, err
984	}
985	return logical.ListResponse(roles), nil
986}
987
988func (i *IdentityStore) pathOIDCDiscovery(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
989	var data []byte
990
991	ns, err := namespace.FromContext(ctx)
992	if err != nil {
993		return nil, err
994	}
995
996	if v, ok := i.oidcCache.Get(ns, "discoveryResponse"); ok {
997		data = v.([]byte)
998	} else {
999		c, err := i.getOIDCConfig(ctx, req.Storage)
1000		if err != nil {
1001			return nil, err
1002		}
1003
1004		disc := discovery{
1005			Issuer:      c.effectiveIssuer,
1006			Keys:        c.effectiveIssuer + "/.well-known/keys",
1007			Subjects:    []string{"public"},
1008			IDTokenAlgs: supportedAlgs,
1009		}
1010
1011		data, err = json.Marshal(disc)
1012		if err != nil {
1013			return nil, err
1014		}
1015
1016		i.oidcCache.SetDefault(ns, "discoveryResponse", data)
1017	}
1018
1019	resp := &logical.Response{
1020		Data: map[string]interface{}{
1021			logical.HTTPStatusCode:      200,
1022			logical.HTTPRawBody:         data,
1023			logical.HTTPContentType:     "application/json",
1024			logical.HTTPRawCacheControl: "max-age=3600",
1025		},
1026	}
1027
1028	return resp, nil
1029}
1030
1031// pathOIDCReadPublicKeys is used to retrieve all public keys so that clients can
1032// verify the validity of a signed OIDC token.
1033func (i *IdentityStore) pathOIDCReadPublicKeys(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
1034	var data []byte
1035
1036	ns, err := namespace.FromContext(ctx)
1037	if err != nil {
1038		return nil, err
1039	}
1040
1041	if v, ok := i.oidcCache.Get(ns, "jwksResponse"); ok {
1042		data = v.([]byte)
1043	} else {
1044		jwks, err := i.generatePublicJWKS(ctx, req.Storage)
1045		if err != nil {
1046			return nil, err
1047		}
1048
1049		data, err = json.Marshal(jwks)
1050		if err != nil {
1051			return nil, err
1052		}
1053
1054		i.oidcCache.SetDefault(ns, "jwksResponse", data)
1055	}
1056
1057	resp := &logical.Response{
1058		Data: map[string]interface{}{
1059			logical.HTTPStatusCode:  200,
1060			logical.HTTPRawBody:     data,
1061			logical.HTTPContentType: "application/json",
1062		},
1063	}
1064
1065	// set a Cache-Control header only if there are keys, if there aren't keys
1066	// then nextRun should not be used to set Cache-Control header because it chooses
1067	// a time in the future that isn't based on key rotation/expiration values
1068	keys, err := listOIDCPublicKeys(ctx, req.Storage)
1069	if err != nil {
1070		return nil, err
1071	}
1072	if len(keys) > 0 {
1073		if v, ok := i.oidcCache.Get(nilNamespace, "nextRun"); ok {
1074			now := time.Now()
1075			expireAt := v.(time.Time)
1076			if expireAt.After(now) {
1077				expireInSeconds := expireAt.Sub(time.Now()).Seconds()
1078				expireInString := fmt.Sprintf("max-age=%.0f", expireInSeconds)
1079				resp.Data[logical.HTTPRawCacheControl] = expireInString
1080			}
1081		}
1082	}
1083
1084	return resp, nil
1085}
1086
1087func (i *IdentityStore) pathOIDCIntrospect(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
1088	var claims jwt.Claims
1089
1090	// helper for preparing the non-standard introspection response
1091	introspectionResp := func(errorMsg string) (*logical.Response, error) {
1092		response := map[string]interface{}{
1093			"active": true,
1094		}
1095
1096		if errorMsg != "" {
1097			response["active"] = false
1098			response["error"] = errorMsg
1099		}
1100
1101		data, err := json.Marshal(response)
1102		if err != nil {
1103			return nil, err
1104		}
1105
1106		resp := &logical.Response{
1107			Data: map[string]interface{}{
1108				logical.HTTPStatusCode:  200,
1109				logical.HTTPRawBody:     data,
1110				logical.HTTPContentType: "application/json",
1111			},
1112		}
1113
1114		return resp, nil
1115	}
1116
1117	rawIDToken := d.Get("token").(string)
1118	clientID := d.Get("client_id").(string)
1119
1120	// validate basic JWT structure
1121	parsedJWT, err := jwt.ParseSigned(rawIDToken)
1122	if err != nil {
1123		return introspectionResp(fmt.Sprintf("error parsing token: %s", err.Error()))
1124	}
1125
1126	// validate signature
1127	jwks, err := i.generatePublicJWKS(ctx, req.Storage)
1128	if err != nil {
1129		return nil, err
1130	}
1131
1132	var valid bool
1133	for _, key := range jwks.Keys {
1134		if err := parsedJWT.Claims(key, &claims); err == nil {
1135			valid = true
1136			break
1137		}
1138	}
1139
1140	if !valid {
1141		return introspectionResp("unable to validate the token signature")
1142	}
1143
1144	// validate claims
1145	c, err := i.getOIDCConfig(ctx, req.Storage)
1146	if err != nil {
1147		return nil, err
1148	}
1149
1150	expected := jwt.Expected{
1151		Issuer: c.effectiveIssuer,
1152		Time:   time.Now(),
1153	}
1154
1155	if clientID != "" {
1156		expected.Audience = []string{clientID}
1157	}
1158
1159	if claimsErr := claims.Validate(expected); claimsErr != nil {
1160		return introspectionResp(fmt.Sprintf("error validating claims: %s", claimsErr.Error()))
1161	}
1162
1163	// validate entity exists and is active
1164	entity, err := i.MemDBEntityByID(claims.Subject, true)
1165	if err != nil {
1166		return nil, err
1167	}
1168	if entity == nil {
1169		return introspectionResp("entity was not found")
1170	} else if entity.Disabled {
1171		return introspectionResp("entity is disabled")
1172	}
1173
1174	return introspectionResp("")
1175}
1176
1177// namedKey.rotate(overrides) performs a key rotation on a namedKey and returns the
1178// verification_ttl that was applied. verification_ttl can be overridden with an
1179// overrideVerificationTTL value >= 0
1180func (k *namedKey) rotate(ctx context.Context, s logical.Storage, overrideVerificationTTL time.Duration) error {
1181	verificationTTL := k.VerificationTTL
1182
1183	if overrideVerificationTTL >= 0 {
1184		verificationTTL = overrideVerificationTTL
1185	}
1186
1187	// generate new key
1188	signingKey, err := generateKeys(k.Algorithm)
1189	if err != nil {
1190		return err
1191	}
1192	if err := saveOIDCPublicKey(ctx, s, signingKey.Public()); err != nil {
1193		return err
1194	}
1195
1196	now := time.Now()
1197
1198	// set the previous public key's expiry time
1199	for _, key := range k.KeyRing {
1200		if key.KeyID == k.SigningKey.KeyID {
1201			key.ExpireAt = now.Add(verificationTTL)
1202			break
1203		}
1204	}
1205	k.SigningKey = signingKey
1206	k.KeyRing = append(k.KeyRing, &expireableKey{KeyID: signingKey.KeyID})
1207	k.NextRotation = now.Add(k.RotationPeriod)
1208
1209	// store named key (it was modified when rotate was called on it)
1210	entry, err := logical.StorageEntryJSON(namedKeyConfigPath+k.name, k)
1211	if err != nil {
1212		return err
1213	}
1214	if err := s.Put(ctx, entry); err != nil {
1215		return err
1216	}
1217
1218	return nil
1219}
1220
1221// generateKeys returns a signingKey and publicKey pair
1222func generateKeys(algorithm string) (*jose.JSONWebKey, error) {
1223	var key interface{}
1224	var err error
1225
1226	switch algorithm {
1227	case "RS256", "RS384", "RS512":
1228		// 2048 bits is recommended by RSA Laboratories as a minimum post 2015
1229		if key, err = rsa.GenerateKey(rand.Reader, 2048); err != nil {
1230			return nil, err
1231		}
1232	case "ES256", "ES384", "ES512":
1233		var curve elliptic.Curve
1234
1235		switch algorithm {
1236		case "ES256":
1237			curve = elliptic.P256()
1238		case "ES384":
1239			curve = elliptic.P384()
1240		case "ES512":
1241			curve = elliptic.P521()
1242		}
1243
1244		if key, err = ecdsa.GenerateKey(curve, rand.Reader); err != nil {
1245			return nil, err
1246		}
1247	case "EdDSA":
1248		_, key, err = ed25519.GenerateKey(rand.Reader)
1249		if err != nil {
1250			return nil, err
1251		}
1252	default:
1253		return nil, fmt.Errorf("unknown algorithm %q", algorithm)
1254	}
1255
1256	id, err := uuid.GenerateUUID()
1257	if err != nil {
1258		return nil, err
1259	}
1260
1261	jwk := &jose.JSONWebKey{
1262		Key:       key,
1263		KeyID:     id,
1264		Algorithm: algorithm,
1265		Use:       "sig",
1266	}
1267
1268	return jwk, nil
1269}
1270
1271func saveOIDCPublicKey(ctx context.Context, s logical.Storage, key jose.JSONWebKey) error {
1272	entry, err := logical.StorageEntryJSON(publicKeysConfigPath+key.KeyID, key)
1273	if err != nil {
1274		return err
1275	}
1276	if err := s.Put(ctx, entry); err != nil {
1277		return err
1278	}
1279
1280	return nil
1281}
1282
1283func loadOIDCPublicKey(ctx context.Context, s logical.Storage, keyID string) (*jose.JSONWebKey, error) {
1284	entry, err := s.Get(ctx, publicKeysConfigPath+keyID)
1285	if err != nil {
1286		return nil, err
1287	}
1288
1289	var key jose.JSONWebKey
1290	if err := entry.DecodeJSON(&key); err != nil {
1291		return nil, err
1292	}
1293
1294	return &key, nil
1295}
1296
1297func listOIDCPublicKeys(ctx context.Context, s logical.Storage) ([]string, error) {
1298	keys, err := s.List(ctx, publicKeysConfigPath)
1299	if err != nil {
1300		return nil, err
1301	}
1302
1303	return keys, nil
1304}
1305
1306func (i *IdentityStore) generatePublicJWKS(ctx context.Context, s logical.Storage) (*jose.JSONWebKeySet, error) {
1307	ns, err := namespace.FromContext(ctx)
1308	if err != nil {
1309		return nil, err
1310	}
1311
1312	if jwksRaw, ok := i.oidcCache.Get(ns, "jwks"); ok {
1313		return jwksRaw.(*jose.JSONWebKeySet), nil
1314	}
1315
1316	if _, err := i.expireOIDCPublicKeys(ctx, s); err != nil {
1317		return nil, err
1318	}
1319
1320	keyIDs, err := listOIDCPublicKeys(ctx, s)
1321	if err != nil {
1322		return nil, err
1323	}
1324
1325	jwks := &jose.JSONWebKeySet{
1326		Keys: make([]jose.JSONWebKey, 0, len(keyIDs)),
1327	}
1328
1329	for _, keyID := range keyIDs {
1330		key, err := loadOIDCPublicKey(ctx, s, keyID)
1331		if err != nil {
1332			return nil, err
1333		}
1334		jwks.Keys = append(jwks.Keys, *key)
1335	}
1336
1337	i.oidcCache.SetDefault(ns, "jwks", jwks)
1338
1339	return jwks, nil
1340}
1341
1342func (i *IdentityStore) expireOIDCPublicKeys(ctx context.Context, s logical.Storage) (time.Time, error) {
1343	var didUpdate bool
1344
1345	i.oidcLock.Lock()
1346	defer i.oidcLock.Unlock()
1347
1348	ns, err := namespace.FromContext(ctx)
1349	if err != nil {
1350		return time.Time{}, err
1351	}
1352
1353	// nextExpiration will be the soonest expiration time of all keys. Initialize
1354	// here to a relatively distant time.
1355	nextExpiration := time.Now().Add(24 * time.Hour)
1356	now := time.Now()
1357
1358	publicKeyIDs, err := listOIDCPublicKeys(ctx, s)
1359	if err != nil {
1360		return now, err
1361	}
1362
1363	namedKeys, err := s.List(ctx, namedKeyConfigPath)
1364	if err != nil {
1365		return now, err
1366	}
1367
1368	usedKeys := make([]string, 0, 2*len(namedKeys))
1369
1370	for _, k := range namedKeys {
1371		entry, err := s.Get(ctx, namedKeyConfigPath+k)
1372		if err != nil {
1373			return now, err
1374		}
1375
1376		var key namedKey
1377		if err := entry.DecodeJSON(&key); err != nil {
1378			return now, err
1379		}
1380
1381		// Remove any expired keys from the keyring.
1382		keyRing := key.KeyRing
1383		var keyringUpdated bool
1384
1385		for i := 0; i < len(keyRing); i++ {
1386			k := keyRing[i]
1387			if !k.ExpireAt.IsZero() && k.ExpireAt.Before(now) {
1388				keyRing[i] = keyRing[len(keyRing)-1]
1389				keyRing = keyRing[:len(keyRing)-1]
1390
1391				keyringUpdated = true
1392				i--
1393				continue
1394			}
1395
1396			// Save a remaining key's next expiration if it is the earliest we've
1397			// seen (for use by the periodicFunc for scheduling).
1398			if !k.ExpireAt.IsZero() && k.ExpireAt.Before(nextExpiration) {
1399				nextExpiration = k.ExpireAt
1400			}
1401
1402			// Mark the KeyID as in use so it doesn't get deleted in the next step
1403			usedKeys = append(usedKeys, k.KeyID)
1404		}
1405
1406		// Persist any keyring updates if necessary
1407		if keyringUpdated {
1408			key.KeyRing = keyRing
1409			entry, err := logical.StorageEntryJSON(entry.Key, key)
1410			if err != nil {
1411				i.Logger().Error("error updating key", "key", key.name, "error", err)
1412			}
1413
1414			if err := s.Put(ctx, entry); err != nil {
1415				i.Logger().Error("error saving key", "key", key.name, "error", err)
1416
1417			}
1418			didUpdate = true
1419		}
1420	}
1421
1422	// Delete all public keys that were not determined to be not expired and in
1423	// use by some role.
1424	for _, keyID := range publicKeyIDs {
1425		if !strutil.StrListContains(usedKeys, keyID) {
1426			didUpdate = true
1427			if err := s.Delete(ctx, publicKeysConfigPath+keyID); err != nil {
1428				i.Logger().Error("error deleting OIDC public key", "key_id", keyID, "error", err)
1429				nextExpiration = now
1430			}
1431			i.Logger().Debug("deleted OIDC public key", "key_id", keyID)
1432		}
1433	}
1434
1435	if didUpdate {
1436		i.oidcCache.Flush(ns)
1437	}
1438
1439	return nextExpiration, nil
1440}
1441
1442func (i *IdentityStore) oidcKeyRotation(ctx context.Context, s logical.Storage) (time.Time, error) {
1443	// soonestRotation will be the soonest rotation time of all keys. Initialize
1444	// here to a relatively distant time.
1445	now := time.Now()
1446	soonestRotation := now.Add(24 * time.Hour)
1447
1448	i.oidcLock.Lock()
1449	defer i.oidcLock.Unlock()
1450
1451	keys, err := s.List(ctx, namedKeyConfigPath)
1452	if err != nil {
1453		return now, err
1454	}
1455
1456	for _, k := range keys {
1457		entry, err := s.Get(ctx, namedKeyConfigPath+k)
1458		if err != nil {
1459			return now, err
1460		}
1461
1462		if entry == nil {
1463			continue
1464		}
1465
1466		var key namedKey
1467		if err := entry.DecodeJSON(&key); err != nil {
1468			return now, err
1469		}
1470		key.name = k
1471
1472		// Future key rotation that is the earliest we've seen.
1473		if now.Before(key.NextRotation) && key.NextRotation.Before(soonestRotation) {
1474			soonestRotation = key.NextRotation
1475		}
1476
1477		// Key that is due to be rotated.
1478		if now.After(key.NextRotation) {
1479			i.Logger().Debug("rotating OIDC key", "key", key.name)
1480			if err := key.rotate(ctx, s, -1); err != nil {
1481				return now, err
1482			}
1483
1484			// Possibly save the new rotation time
1485			if key.NextRotation.Before(soonestRotation) {
1486				soonestRotation = key.NextRotation
1487			}
1488		}
1489	}
1490
1491	return soonestRotation, nil
1492}
1493
1494// oidcPeriodFunc is invoked by the backend's periodFunc and runs regular key
1495// rotations and expiration actions.
1496func (i *IdentityStore) oidcPeriodicFunc(ctx context.Context) {
1497	var nextRun time.Time
1498	now := time.Now()
1499
1500	nsPaths := i.listNamespacePaths()
1501
1502	if v, ok := i.oidcCache.Get(nilNamespace, "nextRun"); ok {
1503		nextRun = v.(time.Time)
1504	}
1505
1506	// The condition here is for performance, not precise timing. The actions can
1507	// be run at any time safely, but there is no need to invoke them (which
1508	// might be somewhat expensive if there are many roles/keys) if we're not
1509	// past any rotation/expiration TTLs.
1510	if now.After(nextRun) {
1511		// Initialize to a fairly distant next run time. This will be brought in
1512		// based on key rotation times.
1513		nextRun = now.Add(24 * time.Hour)
1514
1515		for _, nsPath := range nsPaths {
1516			s := i.core.router.MatchingStorageByAPIPath(ctx, nsPath+"identity/oidc")
1517
1518			if s == nil {
1519				continue
1520			}
1521
1522			nextRotation, err := i.oidcKeyRotation(ctx, s)
1523			if err != nil {
1524				i.Logger().Warn("error rotating OIDC keys", "err", err)
1525			}
1526
1527			nextExpiration, err := i.expireOIDCPublicKeys(ctx, s)
1528			if err != nil {
1529				i.Logger().Warn("error expiring OIDC public keys", "err", err)
1530			}
1531
1532			i.oidcCache.Flush(nilNamespace)
1533
1534			// re-run at the soonest expiration or rotation time
1535			if nextRotation.Before(nextRun) {
1536				nextRun = nextRotation
1537			}
1538
1539			if nextExpiration.Before(nextRun) {
1540				nextRun = nextExpiration
1541			}
1542		}
1543		i.oidcCache.SetDefault(nilNamespace, "nextRun", nextRun)
1544	}
1545}
1546
1547func newOIDCCache() *oidcCache {
1548	return &oidcCache{
1549		c: cache.New(cache.NoExpiration, cache.NoExpiration),
1550	}
1551}
1552
1553func (c *oidcCache) nskey(ns *namespace.Namespace, key string) string {
1554	return fmt.Sprintf("v0:%s:%s", ns.ID, key)
1555}
1556
1557func (c *oidcCache) Get(ns *namespace.Namespace, key string) (interface{}, bool) {
1558	return c.c.Get(c.nskey(ns, key))
1559}
1560
1561func (c *oidcCache) SetDefault(ns *namespace.Namespace, key string, obj interface{}) {
1562	c.c.SetDefault(c.nskey(ns, key), obj)
1563}
1564
1565func (c *oidcCache) Flush(ns *namespace.Namespace) {
1566	for itemKey := range c.c.Items() {
1567		if isNamespacedKey(itemKey, ns.ID) {
1568			c.c.Delete(itemKey)
1569		}
1570	}
1571}
1572
1573// isNamespacedKey returns true for a properly constructed namespaced key (<version>:<nsID>:<key>) where <nsID> is nsID
1574func isNamespacedKey(nskey, nsID string) bool {
1575	split := strings.Split(nskey, ":")
1576	return len(split) >= 3 && split[1] == nsID
1577}
1578