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