2019-06-21 17:23:39 +00:00
package vault
import (
"context"
2019-06-27 15:34:48 +00:00
"crypto/ecdsa"
"crypto/elliptic"
2019-06-21 17:23:39 +00:00
"crypto/rand"
"crypto/rsa"
"encoding/base64"
"encoding/json"
2019-12-17 18:43:38 +00:00
"errors"
2019-06-21 17:23:39 +00:00
"fmt"
2021-09-09 18:47:42 +00:00
"math"
mathrand "math/rand"
2019-07-03 03:15:43 +00:00
"net/url"
2021-07-29 01:34:52 +00:00
"sort"
2019-06-21 17:23:39 +00:00
"strings"
"time"
2019-07-02 21:46:22 +00:00
"github.com/hashicorp/go-hclog"
2021-07-16 00:17:31 +00:00
"github.com/hashicorp/go-secure-stdlib/base62"
"github.com/hashicorp/go-secure-stdlib/strutil"
2019-06-21 17:23:39 +00:00
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/helper/identity"
2019-07-03 03:15:43 +00:00
"github.com/hashicorp/vault/helper/namespace"
2019-06-21 17:23:39 +00:00
"github.com/hashicorp/vault/sdk/framework"
2020-01-06 18:16:52 +00:00
"github.com/hashicorp/vault/sdk/helper/identitytpl"
2019-06-21 17:23:39 +00:00
"github.com/hashicorp/vault/sdk/logical"
2019-07-03 03:15:43 +00:00
"github.com/patrickmn/go-cache"
2019-07-02 21:46:22 +00:00
"golang.org/x/crypto/ed25519"
2019-06-21 17:23:39 +00:00
"gopkg.in/square/go-jose.v2"
2019-07-02 21:46:22 +00:00
"gopkg.in/square/go-jose.v2/jwt"
2019-06-21 17:23:39 +00:00
)
type oidcConfig struct {
Issuer string ` json:"issuer" `
// effectiveIssuer is a calculated field and will be either Issuer (if
// that's set) or the Vault instance's api_addr.
effectiveIssuer string
}
type expireableKey struct {
KeyID string ` json:"key_id" `
ExpireAt time . Time ` json:"expire_at" `
}
type namedKey struct {
2019-07-02 21:46:22 +00:00
name string
Algorithm string ` json:"signing_algorithm" `
VerificationTTL time . Duration ` json:"verification_ttl" `
RotationPeriod time . Duration ` json:"rotation_period" `
KeyRing [ ] * expireableKey ` json:"key_ring" `
SigningKey * jose . JSONWebKey ` json:"signing_key" `
2021-09-09 18:47:42 +00:00
NextSigningKey * jose . JSONWebKey ` json:"next_signing_key" `
2019-07-02 21:46:22 +00:00
NextRotation time . Time ` json:"next_rotation" `
AllowedClientIDs [ ] string ` json:"allowed_client_ids" `
2019-06-21 17:23:39 +00:00
}
type role struct {
TokenTTL time . Duration ` json:"token_ttl" `
Key string ` json:"key" `
Template string ` json:"template" `
ClientID string ` json:"client_id" `
}
// idToken contains the required OIDC fields.
//
// Templated claims will be merged into the final output. Those claims may
// include top-level keys, but those keys may not overwrite any of the
// required OIDC fields.
type idToken struct {
2021-10-14 01:59:36 +00:00
Issuer string ` json:"iss" ` // api_addr or custom Issuer
Namespace string ` json:"namespace" ` // Namespace of issuer
Subject string ` json:"sub" ` // Entity ID
Audience string ` json:"aud" ` // Role or client ID will be used here.
Expiry int64 ` json:"exp" ` // Expiration, as determined by the role or client.
IssuedAt int64 ` json:"iat" ` // Time of token creation
Nonce string ` json:"nonce" ` // Nonce given in OIDC authentication requests
AuthTime int64 ` json:"auth_time" ` // AuthTime given in OIDC authentication requests
AccessTokenHash string ` json:"at_hash" ` // Access token hash value
CodeHash string ` json:"c_hash" ` // Authorization code hash value
2019-06-21 17:23:39 +00:00
}
2019-07-03 03:15:43 +00:00
// discovery contains a subset of the required elements of OIDC discovery needed
// for JWT verification libraries to use the .well-known endpoint.
//
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
2019-06-21 17:23:39 +00:00
type discovery struct {
2019-10-02 15:59:57 +00:00
Issuer string ` json:"issuer" `
Keys string ` json:"jwks_uri" `
ResponseTypes [ ] string ` json:"response_types_supported" `
Subjects [ ] string ` json:"subject_types_supported" `
IDTokenAlgs [ ] string ` json:"id_token_signing_alg_values_supported" `
2019-07-03 03:15:43 +00:00
}
// oidcCache is a thin wrapper around go-cache to partition by namespace
type oidcCache struct {
c * cache . Cache
2019-06-21 17:23:39 +00:00
}
2019-12-17 18:43:38 +00:00
var errNilNamespace = errors . New ( "nil namespace in oidc cache request" )
2019-06-21 17:23:39 +00:00
const (
2019-07-03 20:52:29 +00:00
issuerPath = "identity/oidc"
2019-06-21 17:23:39 +00:00
oidcTokensPrefix = "oidc_tokens/"
oidcConfigStorageKey = oidcTokensPrefix + "config/"
namedKeyConfigPath = oidcTokensPrefix + "named_keys/"
publicKeysConfigPath = oidcTokensPrefix + "public_keys/"
roleConfigPath = oidcTokensPrefix + "roles/"
)
2021-04-08 16:43:39 +00:00
var (
2021-11-22 17:42:22 +00:00
reservedClaims = [ ] string {
2021-10-14 01:59:36 +00:00
"iat" , "aud" , "exp" , "iss" ,
"sub" , "namespace" , "nonce" ,
"auth_time" , "at_hash" , "c_hash" ,
}
supportedAlgs = [ ] string {
2021-04-08 16:43:39 +00:00
string ( jose . RS256 ) ,
string ( jose . RS384 ) ,
string ( jose . RS512 ) ,
string ( jose . ES256 ) ,
string ( jose . ES384 ) ,
string ( jose . ES512 ) ,
string ( jose . EdDSA ) ,
}
)
2019-07-03 03:15:43 +00:00
// pseudo-namespace for cache items that don't belong to any real namespace.
2019-12-17 18:43:38 +00:00
var noNamespace = & namespace . Namespace { ID : "__NO_NAMESPACE" }
2019-06-21 17:23:39 +00:00
func oidcPaths ( i * IdentityStore ) [ ] * framework . Path {
return [ ] * framework . Path {
{
Pattern : "oidc/config/?$" ,
Fields : map [ string ] * framework . FieldSchema {
"issuer" : {
Type : framework . TypeString ,
Description : "Issuer URL to be used in the iss claim of the token. If not set, Vault's app_addr will be used." ,
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . ReadOperation : i . pathOIDCReadConfig ,
logical . UpdateOperation : i . pathOIDCUpdateConfig ,
} ,
HelpSynopsis : "OIDC configuration" ,
HelpDescription : "Update OIDC configuration in the identity backend" ,
} ,
{
Pattern : "oidc/key/" + framework . GenericNameRegex ( "name" ) ,
Fields : map [ string ] * framework . FieldSchema {
"name" : {
Type : framework . TypeString ,
Description : "Name of the key" ,
} ,
"rotation_period" : {
Type : framework . TypeDurationSecond ,
Description : "How often to generate a new keypair." ,
Default : "24h" ,
} ,
"verification_ttl" : {
Type : framework . TypeDurationSecond ,
Description : "Controls how long the public portion of a key will be available for verification after being rotated." ,
Default : "24h" ,
} ,
"algorithm" : {
Type : framework . TypeString ,
2019-06-27 15:34:48 +00:00
Description : "Signing algorithm to use. This will default to RS256." ,
2019-06-21 17:23:39 +00:00
Default : "RS256" ,
} ,
2019-07-02 21:46:22 +00:00
2021-04-08 16:43:39 +00:00
"allowed_client_ids" : {
2019-07-02 21:46:22 +00:00
Type : framework . TypeCommaStringSlice ,
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." ,
} ,
2019-06-21 17:23:39 +00:00
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . CreateOperation : i . pathOIDCCreateUpdateKey ,
logical . UpdateOperation : i . pathOIDCCreateUpdateKey ,
logical . ReadOperation : i . pathOIDCReadKey ,
logical . DeleteOperation : i . pathOIDCDeleteKey ,
} ,
ExistenceCheck : i . pathOIDCKeyExistenceCheck ,
HelpSynopsis : "CRUD operations for OIDC keys." ,
HelpDescription : "Create, Read, Update, and Delete OIDC named keys." ,
} ,
{
Pattern : "oidc/key/" + framework . GenericNameRegex ( "name" ) + "/rotate/?$" ,
Fields : map [ string ] * framework . FieldSchema {
"name" : {
Type : framework . TypeString ,
Description : "Name of the key" ,
} ,
"verification_ttl" : {
Type : framework . TypeDurationSecond ,
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." ,
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . UpdateOperation : i . pathOIDCRotateKey ,
} ,
HelpSynopsis : "Rotate a named OIDC key." ,
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." ,
} ,
{
Pattern : "oidc/key/?$" ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . ListOperation : i . pathOIDCListKey ,
} ,
HelpSynopsis : "List OIDC keys" ,
HelpDescription : "List all named OIDC keys" ,
} ,
{
Pattern : "oidc/.well-known/openid-configuration/?$" ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . ReadOperation : i . pathOIDCDiscovery ,
} ,
HelpSynopsis : "Query OIDC configurations" ,
2019-10-02 15:59:57 +00:00
HelpDescription : "Query this path to retrieve the configured OIDC Issuer and Keys endpoints, response types, subject types, and signing algorithms used by the OIDC backend." ,
2019-06-21 17:23:39 +00:00
} ,
{
Pattern : "oidc/.well-known/keys/?$" ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . ReadOperation : i . pathOIDCReadPublicKeys ,
} ,
HelpSynopsis : "Retrieve public keys" ,
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." ,
} ,
{
Pattern : "oidc/token/" + framework . GenericNameRegex ( "name" ) ,
Fields : map [ string ] * framework . FieldSchema {
"name" : {
Type : framework . TypeString ,
Description : "Name of the role" ,
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . ReadOperation : i . pathOIDCGenerateToken ,
} ,
HelpSynopsis : "Generate an OIDC token" ,
HelpDescription : "Generate an OIDC token against a configured role. The vault token used to call this path must have a corresponding entity." ,
} ,
{
Pattern : "oidc/role/" + framework . GenericNameRegex ( "name" ) ,
Fields : map [ string ] * framework . FieldSchema {
"name" : {
Type : framework . TypeString ,
Description : "Name of the role" ,
} ,
"key" : {
Type : framework . TypeString ,
Description : "The OIDC key to use for generating tokens. The specified key must already exist." ,
2021-09-08 15:46:58 +00:00
Required : true ,
2019-06-21 17:23:39 +00:00
} ,
"template" : {
Type : framework . TypeString ,
Description : "The template string to use for generating tokens. This may be in string-ified JSON or base64 format." ,
} ,
"ttl" : {
Type : framework . TypeDurationSecond ,
Description : "TTL of the tokens generated against the role." ,
Default : "24h" ,
} ,
2020-02-14 07:15:35 +00:00
"client_id" : {
Type : framework . TypeString ,
Description : "Optional client_id" ,
} ,
2019-06-21 17:23:39 +00:00
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . UpdateOperation : i . pathOIDCCreateUpdateRole ,
logical . CreateOperation : i . pathOIDCCreateUpdateRole ,
logical . ReadOperation : i . pathOIDCReadRole ,
logical . DeleteOperation : i . pathOIDCDeleteRole ,
} ,
ExistenceCheck : i . pathOIDCRoleExistenceCheck ,
HelpSynopsis : "CRUD operations on OIDC Roles" ,
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." ,
} ,
{
Pattern : "oidc/role/?$" ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . ListOperation : i . pathOIDCListRole ,
} ,
HelpSynopsis : "List configured OIDC roles" ,
HelpDescription : "List all configured OIDC roles in the identity backend." ,
} ,
{
Pattern : "oidc/introspect/?$" ,
Fields : map [ string ] * framework . FieldSchema {
"token" : {
Type : framework . TypeString ,
Description : "Token to verify" ,
} ,
"client_id" : {
Type : framework . TypeString ,
Description : "Optional client_id to verify" ,
} ,
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . UpdateOperation : i . pathOIDCIntrospect ,
} ,
HelpSynopsis : "Verify the authenticity of an OIDC token" ,
HelpDescription : "Use this path to verify the authenticity of an OIDC token and whether the associated entity is active and enabled." ,
} ,
}
}
func ( i * IdentityStore ) pathOIDCReadConfig ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
c , err := i . getOIDCConfig ( ctx , req . Storage )
if err != nil {
return nil , err
}
if c == nil {
return nil , nil
}
resp := & logical . Response {
Data : map [ string ] interface { } {
"issuer" : c . Issuer ,
} ,
}
2021-08-30 19:31:11 +00:00
if i . redirectAddr == "" && c . Issuer == "" {
2019-06-21 17:23:39 +00:00
resp . AddWarning ( ` Both "issuer" and Vault's "api_addr" are empty. ` +
` The issuer claim in generated tokens will not be network reachable. ` )
}
return resp , nil
}
func ( i * IdentityStore ) pathOIDCUpdateConfig ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
2019-07-03 03:15:43 +00:00
var resp * logical . Response
ns , err := namespace . FromContext ( ctx )
if err != nil {
return nil , err
}
issuerRaw , ok := d . GetOk ( "issuer" )
2019-06-21 17:23:39 +00:00
if ! ok {
return nil , nil
}
2019-07-03 03:15:43 +00:00
issuer := issuerRaw . ( string )
if issuer != "" {
// verify that issuer is the correct format:
// - http or https
// - host name
// - optional port
// - nothing more
valid := false
if u , err := url . Parse ( issuer ) ; err == nil {
u2 := url . URL {
Scheme : u . Scheme ,
Host : u . Host ,
}
valid = ( * u == u2 ) &&
( u . Scheme == "http" || u . Scheme == "https" ) &&
u . Host != ""
}
if ! valid {
return logical . ErrorResponse (
"invalid issuer, which must include only a scheme, host, " +
"and optional port (e.g. https://example.com:8200)" ) , nil
}
resp = & logical . Response {
Warnings : [ ] string { ` If "issuer" is set explicitly, all tokens must be ` +
` validated against that address, including those issued by secondary ` +
` clusters. Setting issuer to "" will restore the default behavior of ` +
` using the cluster's api_addr as the issuer. ` } ,
}
}
2019-06-21 17:23:39 +00:00
c := oidcConfig {
2019-07-03 03:15:43 +00:00
Issuer : issuer ,
2019-06-21 17:23:39 +00:00
}
entry , err := logical . StorageEntryJSON ( oidcConfigStorageKey , c )
if err != nil {
return nil , err
}
if err := req . Storage . Put ( ctx , entry ) ; err != nil {
return nil , err
}
2019-12-17 18:43:38 +00:00
if err := i . oidcCache . Flush ( ns ) ; err != nil {
return nil , err
}
2019-06-21 17:23:39 +00:00
2019-07-03 03:15:43 +00:00
return resp , nil
2019-06-21 17:23:39 +00:00
}
func ( i * IdentityStore ) getOIDCConfig ( ctx context . Context , s logical . Storage ) ( * oidcConfig , error ) {
2019-07-03 03:15:43 +00:00
ns , err := namespace . FromContext ( ctx )
if err != nil {
return nil , err
}
2019-12-17 18:43:38 +00:00
v , ok , err := i . oidcCache . Get ( ns , "config" )
if err != nil {
return nil , err
}
if ok {
2019-06-21 17:23:39 +00:00
return v . ( * oidcConfig ) , nil
}
var c oidcConfig
entry , err := s . Get ( ctx , oidcConfigStorageKey )
if err != nil {
return nil , err
}
if entry != nil {
if err := entry . DecodeJSON ( & c ) ; err != nil {
return nil , err
}
}
c . effectiveIssuer = c . Issuer
if c . effectiveIssuer == "" {
2021-08-30 19:31:11 +00:00
c . effectiveIssuer = i . redirectAddr
2019-06-21 17:23:39 +00:00
}
2019-07-03 20:52:29 +00:00
c . effectiveIssuer += "/v1/" + ns . Path + issuerPath
2019-07-03 03:15:43 +00:00
2019-12-17 18:43:38 +00:00
if err := i . oidcCache . SetDefault ( ns , "config" , & c ) ; err != nil {
return nil , err
}
2019-06-21 17:23:39 +00:00
return & c , nil
}
// handleOIDCCreateKey is used to create a new named key or update an existing one
func ( i * IdentityStore ) pathOIDCCreateUpdateKey ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
2019-07-03 03:15:43 +00:00
ns , err := namespace . FromContext ( ctx )
if err != nil {
return nil , err
}
2019-06-21 17:23:39 +00:00
name := d . Get ( "name" ) . ( string )
2019-07-03 03:15:43 +00:00
i . oidcLock . Lock ( )
defer i . oidcLock . Unlock ( )
2019-06-21 17:23:39 +00:00
var key namedKey
2019-07-02 21:46:22 +00:00
if req . Operation == logical . UpdateOperation {
entry , err := req . Storage . Get ( ctx , namedKeyConfigPath + name )
if err != nil {
2019-06-21 17:23:39 +00:00
return nil , err
}
2019-07-02 21:46:22 +00:00
if entry != nil {
if err := entry . DecodeJSON ( & key ) ; err != nil {
return nil , err
}
}
2019-06-21 17:23:39 +00:00
}
if rotationPeriodRaw , ok := d . GetOk ( "rotation_period" ) ; ok {
key . RotationPeriod = time . Duration ( rotationPeriodRaw . ( int ) ) * time . Second
} else if req . Operation == logical . CreateOperation {
key . RotationPeriod = time . Duration ( d . Get ( "rotation_period" ) . ( int ) ) * time . Second
}
2019-07-02 21:46:22 +00:00
if key . RotationPeriod < 1 * time . Minute {
return logical . ErrorResponse ( "rotation_period must be at least one minute" ) , nil
}
2019-06-21 17:23:39 +00:00
if verificationTTLRaw , ok := d . GetOk ( "verification_ttl" ) ; ok {
key . VerificationTTL = time . Duration ( verificationTTLRaw . ( int ) ) * time . Second
} else if req . Operation == logical . CreateOperation {
key . VerificationTTL = time . Duration ( d . Get ( "verification_ttl" ) . ( int ) ) * time . Second
}
if key . VerificationTTL > 10 * key . RotationPeriod {
return logical . ErrorResponse ( "verification_ttl cannot be longer than 10x rotation_period" ) , nil
}
2021-07-29 01:34:52 +00:00
if req . Operation == logical . UpdateOperation {
// ensure any roles referencing this key do not already have a token_ttl
// greater than the key's verification_ttl
roles , err := i . rolesReferencingTargetKeyName ( ctx , req , name )
if err != nil {
return nil , err
}
for _ , role := range roles {
if role . TokenTTL > key . VerificationTTL {
errorMessage := fmt . Sprintf (
"unable to update key %q because it is currently referenced by one or more roles with a token ttl greater than %d seconds" ,
name ,
key . VerificationTTL / time . Second ,
)
return logical . ErrorResponse ( errorMessage ) , nil
}
}
2021-10-01 17:47:40 +00:00
// ensure any clients referencing this key do not already have a id_token_ttl
// greater than the key's verification_ttl
clients , err := i . clientsReferencingTargetKeyName ( ctx , req , name )
if err != nil {
return nil , err
}
for _ , client := range clients {
if client . IDTokenTTL > key . VerificationTTL {
errorMessage := fmt . Sprintf (
"unable to update key %q because it is currently referenced by one or more clients with an id_token_ttl greater than %d seconds" ,
name ,
key . VerificationTTL / time . Second ,
)
return logical . ErrorResponse ( errorMessage ) , nil
}
}
2021-07-29 01:34:52 +00:00
}
2019-07-02 21:46:22 +00:00
if allowedClientIDsRaw , ok := d . GetOk ( "allowed_client_ids" ) ; ok {
key . AllowedClientIDs = allowedClientIDsRaw . ( [ ] string )
} else if req . Operation == logical . CreateOperation {
key . AllowedClientIDs = d . Get ( "allowed_client_ids" ) . ( [ ] string )
}
2019-06-27 15:34:48 +00:00
prevAlgorithm := key . Algorithm
2019-06-21 17:23:39 +00:00
if algorithm , ok := d . GetOk ( "algorithm" ) ; ok {
key . Algorithm = algorithm . ( string )
} else if req . Operation == logical . CreateOperation {
key . Algorithm = d . Get ( "algorithm" ) . ( string )
}
2019-07-03 03:15:43 +00:00
if ! strutil . StrListContains ( supportedAlgs , key . Algorithm ) {
2019-06-21 17:23:39 +00:00
return logical . ErrorResponse ( "unknown signing algorithm %q" , key . Algorithm ) , nil
}
2021-09-09 18:47:42 +00:00
now := time . Now ( )
2019-06-21 17:23:39 +00:00
// Update next rotation time if it is unset or now earlier than previously set.
2021-09-09 18:47:42 +00:00
nextRotation := now . Add ( key . RotationPeriod )
2019-06-21 17:23:39 +00:00
if key . NextRotation . IsZero ( ) || nextRotation . Before ( key . NextRotation ) {
key . NextRotation = nextRotation
}
2021-09-09 18:47:42 +00:00
// generate current and next keys if creating a new key or changing algorithms
2019-06-27 15:34:48 +00:00
if key . Algorithm != prevAlgorithm {
2022-01-24 18:05:49 +00:00
err = key . generateAndSetKey ( ctx , i . Logger ( ) , req . Storage )
2019-06-21 17:23:39 +00:00
if err != nil {
return nil , err
}
2021-11-29 21:10:58 +00:00
err = key . generateAndSetNextKey ( ctx , i . Logger ( ) , req . Storage )
2021-09-09 18:47:42 +00:00
if err != nil {
return nil , err
}
2019-06-21 17:23:39 +00:00
}
2019-12-17 18:43:38 +00:00
if err := i . oidcCache . Flush ( ns ) ; err != nil {
return nil , err
}
2019-06-21 17:23:39 +00:00
// store named key
2019-07-02 21:46:22 +00:00
entry , err := logical . StorageEntryJSON ( namedKeyConfigPath + name , key )
2019-06-21 17:23:39 +00:00
if err != nil {
return nil , err
}
if err := req . Storage . Put ( ctx , entry ) ; err != nil {
return nil , err
}
return nil , nil
}
// handleOIDCReadKey is used to read an existing key
func ( i * IdentityStore ) pathOIDCReadKey ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
name := d . Get ( "name" ) . ( string )
2019-07-03 03:15:43 +00:00
i . oidcLock . RLock ( )
defer i . oidcLock . RUnlock ( )
2019-06-21 17:23:39 +00:00
entry , err := req . Storage . Get ( ctx , namedKeyConfigPath + name )
if err != nil {
return nil , err
}
if entry == nil {
2019-08-07 16:46:45 +00:00
return nil , nil
2019-06-21 17:23:39 +00:00
}
var storedNamedKey namedKey
if err := entry . DecodeJSON ( & storedNamedKey ) ; err != nil {
return nil , err
}
return & logical . Response {
Data : map [ string ] interface { } {
2019-07-02 21:46:22 +00:00
"rotation_period" : int64 ( storedNamedKey . RotationPeriod . Seconds ( ) ) ,
"verification_ttl" : int64 ( storedNamedKey . VerificationTTL . Seconds ( ) ) ,
"algorithm" : storedNamedKey . Algorithm ,
"allowed_client_ids" : storedNamedKey . AllowedClientIDs ,
2019-06-21 17:23:39 +00:00
} ,
} , nil
}
2021-10-12 16:14:03 +00:00
// keyIDsByName will return a slice of key IDs for the given key name
func ( i * IdentityStore ) keyIDsByName ( ctx context . Context , s logical . Storage , name string ) ( [ ] string , error ) {
var keyIDs [ ] string
entry , err := s . Get ( ctx , namedKeyConfigPath + name )
if err != nil {
return keyIDs , err
}
if entry == nil {
return keyIDs , nil
}
var key namedKey
if err := entry . DecodeJSON ( & key ) ; err != nil {
return keyIDs , err
}
for _ , k := range key . KeyRing {
keyIDs = append ( keyIDs , k . KeyID )
}
return keyIDs , nil
}
2021-08-23 13:42:31 +00:00
// rolesReferencingTargetKeyName returns a map of role names to roles
// referencing targetKeyName.
//
2021-07-29 01:34:52 +00:00
// Note: this is not threadsafe. It is to be called with Lock already held.
func ( i * IdentityStore ) rolesReferencingTargetKeyName ( ctx context . Context , req * logical . Request , targetKeyName string ) ( map [ string ] role , error ) {
2019-06-21 17:23:39 +00:00
roleNames , err := req . Storage . List ( ctx , roleConfigPath )
if err != nil {
return nil , err
}
2021-07-29 01:34:52 +00:00
var tempRole role
roles := make ( map [ string ] role )
2019-06-21 17:23:39 +00:00
for _ , roleName := range roleNames {
entry , err := req . Storage . Get ( ctx , roleConfigPath + roleName )
if err != nil {
return nil , err
}
if entry != nil {
2021-07-29 01:34:52 +00:00
if err := entry . DecodeJSON ( & tempRole ) ; err != nil {
2019-06-21 17:23:39 +00:00
return nil , err
}
2021-07-29 01:34:52 +00:00
if tempRole . Key == targetKeyName {
roles [ roleName ] = tempRole
2019-06-21 17:23:39 +00:00
}
}
}
2021-07-29 01:34:52 +00:00
return roles , nil
}
// roleNamesReferencingTargetKeyName returns a slice of strings of role
2021-08-23 13:42:31 +00:00
// names referencing targetKeyName.
//
2021-07-29 01:34:52 +00:00
// Note: this is not threadsafe. It is to be called with Lock already held.
func ( i * IdentityStore ) roleNamesReferencingTargetKeyName ( ctx context . Context , req * logical . Request , targetKeyName string ) ( [ ] string , error ) {
roles , err := i . rolesReferencingTargetKeyName ( ctx , req , targetKeyName )
if err != nil {
return nil , err
}
var names [ ] string
for key , _ := range roles {
names = append ( names , key )
}
sort . Strings ( names )
return names , nil
}
// handleOIDCDeleteKey is used to delete a key
func ( i * IdentityStore ) pathOIDCDeleteKey ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
ns , err := namespace . FromContext ( ctx )
if err != nil {
return nil , err
}
targetKeyName := d . Get ( "name" ) . ( string )
i . oidcLock . Lock ( )
roleNames , err := i . roleNamesReferencingTargetKeyName ( ctx , req , targetKeyName )
if err != nil {
2021-10-25 16:59:26 +00:00
i . oidcLock . Unlock ( )
2021-07-29 01:34:52 +00:00
return nil , err
}
if len ( roleNames ) > 0 {
2019-06-21 17:23:39 +00:00
errorMessage := fmt . Sprintf ( "unable to delete key %q because it is currently referenced by these roles: %s" ,
2021-07-29 01:34:52 +00:00
targetKeyName , strings . Join ( roleNames , ", " ) )
2019-07-03 03:15:43 +00:00
i . oidcLock . Unlock ( )
2019-06-21 17:23:39 +00:00
return logical . ErrorResponse ( errorMessage ) , logical . ErrInvalidRequest
}
2021-08-23 13:42:31 +00:00
clientNames , err := i . clientNamesReferencingTargetKeyName ( ctx , req , targetKeyName )
if err != nil {
2021-10-25 16:59:26 +00:00
i . oidcLock . Unlock ( )
2021-08-23 13:42:31 +00:00
return nil , err
}
if len ( clientNames ) > 0 {
errorMessage := fmt . Sprintf ( "unable to delete key %q because it is currently referenced by these clients: %s" ,
targetKeyName , strings . Join ( clientNames , ", " ) )
i . oidcLock . Unlock ( )
return logical . ErrorResponse ( errorMessage ) , logical . ErrInvalidRequest
}
2019-06-21 17:23:39 +00:00
// key can safely be deleted now
err = req . Storage . Delete ( ctx , namedKeyConfigPath + targetKeyName )
if err != nil {
2021-04-07 23:48:40 +00:00
i . oidcLock . Unlock ( )
2019-06-21 17:23:39 +00:00
return nil , err
}
2019-07-03 03:15:43 +00:00
i . oidcLock . Unlock ( )
2019-06-21 17:23:39 +00:00
_ , err = i . expireOIDCPublicKeys ( ctx , req . Storage )
if err != nil {
return nil , err
}
2019-12-17 18:43:38 +00:00
if err := i . oidcCache . Flush ( ns ) ; err != nil {
return nil , err
}
2019-06-21 17:23:39 +00:00
return nil , nil
}
// handleOIDCListKey is used to list named keys
func ( i * IdentityStore ) pathOIDCListKey ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
2019-07-03 03:15:43 +00:00
i . oidcLock . RLock ( )
defer i . oidcLock . RUnlock ( )
2019-06-21 17:23:39 +00:00
keys , err := req . Storage . List ( ctx , namedKeyConfigPath )
if err != nil {
return nil , err
}
return logical . ListResponse ( keys ) , nil
}
// pathOIDCRotateKey is used to manually trigger a rotation on the named key
func ( i * IdentityStore ) pathOIDCRotateKey ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
2019-07-03 03:15:43 +00:00
ns , err := namespace . FromContext ( ctx )
if err != nil {
return nil , err
}
2019-06-21 17:23:39 +00:00
name := d . Get ( "name" ) . ( string )
2019-07-03 03:15:43 +00:00
i . oidcLock . Lock ( )
defer i . oidcLock . Unlock ( )
2019-06-21 17:23:39 +00:00
// load the named key and perform a rotation
entry , err := req . Storage . Get ( ctx , namedKeyConfigPath + name )
if err != nil {
return nil , err
}
if entry == nil {
return logical . ErrorResponse ( "no named key found at %q" , name ) , logical . ErrInvalidRequest
}
var storedNamedKey namedKey
if err := entry . DecodeJSON ( & storedNamedKey ) ; err != nil {
return nil , err
}
2019-07-02 21:46:22 +00:00
storedNamedKey . name = name
2019-06-21 17:23:39 +00:00
// call rotate with an appropriate overrideTTL where < 0 means no override
verificationTTLOverride := - 1 * time . Second
if ttlRaw , ok := d . GetOk ( "verification_ttl" ) ; ok {
verificationTTLOverride = time . Duration ( ttlRaw . ( int ) ) * time . Second
}
2021-09-09 18:47:42 +00:00
if err := storedNamedKey . rotate ( ctx , i . Logger ( ) , req . Storage , verificationTTLOverride ) ; err != nil {
2019-06-21 17:23:39 +00:00
return nil , err
}
2019-12-17 18:43:38 +00:00
if err := i . oidcCache . Flush ( ns ) ; err != nil {
return nil , err
}
2019-06-21 17:23:39 +00:00
return nil , nil
}
func ( i * IdentityStore ) pathOIDCKeyExistenceCheck ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( bool , error ) {
name := d . Get ( "name" ) . ( string )
2019-07-03 03:15:43 +00:00
i . oidcLock . RLock ( )
defer i . oidcLock . RUnlock ( )
2019-06-21 17:23:39 +00:00
entry , err := req . Storage . Get ( ctx , namedKeyConfigPath + name )
if err != nil {
return false , err
}
return entry != nil , nil
}
// handleOIDCGenerateSignToken generates and signs an OIDC token
func ( i * IdentityStore ) pathOIDCGenerateToken ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
2019-07-03 03:15:43 +00:00
ns , err := namespace . FromContext ( ctx )
if err != nil {
return nil , err
}
2019-06-21 17:23:39 +00:00
roleName := d . Get ( "name" ) . ( string )
role , err := i . getOIDCRole ( ctx , req . Storage , roleName )
if err != nil {
return nil , err
}
if role == nil {
return logical . ErrorResponse ( "role %q not found" , roleName ) , nil
}
2021-10-14 01:59:36 +00:00
key , err := i . getNamedKey ( ctx , req . Storage , role . Key )
2019-12-17 18:43:38 +00:00
if err != nil {
return nil , err
}
2021-10-14 01:59:36 +00:00
if key == nil {
return logical . ErrorResponse ( "key %q not found" , role . Key ) , nil
2019-06-21 17:23:39 +00:00
}
2021-10-14 01:59:36 +00:00
2019-07-02 21:46:22 +00:00
// Validate that the role is allowed to sign with its key (the key could have been updated)
if ! strutil . StrListContains ( key . AllowedClientIDs , "*" ) && ! strutil . StrListContains ( key . AllowedClientIDs , role . ClientID ) {
2019-07-03 16:31:31 +00:00
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
2019-07-02 21:46:22 +00:00
}
2019-06-21 17:23:39 +00:00
// generate an OIDC token from entity data
if req . EntityID == "" {
return logical . ErrorResponse ( "no entity associated with the request's token" ) , nil
}
config , err := i . getOIDCConfig ( ctx , req . Storage )
if err != nil {
return nil , err
}
2021-07-29 01:34:52 +00:00
retResp := & logical . Response { }
expiry := role . TokenTTL
if expiry > key . VerificationTTL {
expiry = key . VerificationTTL
retResp . AddWarning ( fmt . Sprintf ( "a role's token ttl cannot be longer " +
"than the verification_ttl of the key it references, setting token ttl to %d" , expiry ) )
}
2019-06-21 17:23:39 +00:00
now := time . Now ( )
idToken := idToken {
2019-07-03 03:15:43 +00:00
Issuer : config . effectiveIssuer ,
Namespace : ns . ID ,
Subject : req . EntityID ,
Audience : role . ClientID ,
2021-07-29 01:34:52 +00:00
Expiry : now . Add ( expiry ) . Unix ( ) ,
2019-07-03 03:15:43 +00:00
IssuedAt : now . Unix ( ) ,
2019-06-21 17:23:39 +00:00
}
e , err := i . MemDBEntityByID ( req . EntityID , true )
if err != nil {
return nil , err
}
if e == nil {
return nil , fmt . Errorf ( "error loading entity ID %q" , req . EntityID )
}
groups , inheritedGroups , err := i . groupsByEntityID ( e . ID )
if err != nil {
return nil , err
}
groups = append ( groups , inheritedGroups ... )
2021-10-14 01:59:36 +00:00
// Parse and integrate the populated template. Structural errors with the template _should_
// be caught during configuration. Error found during runtime will be logged, but they will
// not block generation of the basic ID token. They should not be returned to the requester.
_ , populatedTemplate , err := identitytpl . PopulateString ( identitytpl . PopulateStringInput {
Mode : identitytpl . JSONTemplating ,
String : role . Template ,
Entity : identity . ToSDKEntity ( e ) ,
Groups : identity . ToSDKGroups ( groups ) ,
NamespaceID : ns . ID ,
} )
if err != nil {
i . Logger ( ) . Warn ( "error populating OIDC token template" , "template" , role . Template , "error" , err )
}
payload , err := idToken . generatePayload ( i . Logger ( ) , populatedTemplate )
2019-06-21 17:23:39 +00:00
if err != nil {
i . Logger ( ) . Warn ( "error populating OIDC token template" , "error" , err )
}
signedIdToken , err := key . signPayload ( payload )
if err != nil {
2021-05-11 17:12:54 +00:00
return nil , fmt . Errorf ( "error signing OIDC token: %w" , err )
2019-06-21 17:23:39 +00:00
}
2021-07-29 01:34:52 +00:00
retResp . Data = map [ string ] interface { } {
"token" : signedIdToken ,
"client_id" : role . ClientID ,
"ttl" : int64 ( role . TokenTTL . Seconds ( ) ) ,
}
return retResp , nil
2019-06-21 17:23:39 +00:00
}
2021-10-14 01:59:36 +00:00
func ( i * IdentityStore ) getNamedKey ( ctx context . Context , s logical . Storage , name string ) ( * namedKey , error ) {
ns , err := namespace . FromContext ( ctx )
if err != nil {
return nil , err
}
// Attempt to get the key from the cache
keyRaw , found , err := i . oidcCache . Get ( ns , "namedKeys/" + name )
if err != nil {
return nil , err
}
if key , ok := keyRaw . ( * namedKey ) ; ok && found {
return key , nil
}
// Fall back to reading the key from storage
entry , err := s . Get ( ctx , namedKeyConfigPath + name )
if err != nil {
return nil , err
}
if entry == nil {
return nil , nil
}
var key namedKey
if err := entry . DecodeJSON ( & key ) ; err != nil {
return nil , err
}
// Cache the key
if err := i . oidcCache . SetDefault ( ns , "namedKeys/" + name , & key ) ; err != nil {
i . logger . Warn ( "failed to cache key" , "error" , err )
}
return & key , nil
}
func ( tok * idToken ) generatePayload ( logger hclog . Logger , templates ... string ) ( [ ] byte , error ) {
2019-06-21 17:23:39 +00:00
output := map [ string ] interface { } {
2019-07-03 03:15:43 +00:00
"iss" : tok . Issuer ,
"namespace" : tok . Namespace ,
"sub" : tok . Subject ,
"aud" : tok . Audience ,
"exp" : tok . Expiry ,
"iat" : tok . IssuedAt ,
2019-06-21 17:23:39 +00:00
}
2021-11-22 17:42:22 +00:00
// Copy optional claims into output
2021-10-14 01:59:36 +00:00
if len ( tok . Nonce ) > 0 {
output [ "nonce" ] = tok . Nonce
}
if tok . AuthTime > 0 {
output [ "auth_time" ] = tok . AuthTime
}
if len ( tok . AccessTokenHash ) > 0 {
output [ "at_hash" ] = tok . AccessTokenHash
}
if len ( tok . CodeHash ) > 0 {
output [ "c_hash" ] = tok . CodeHash
}
// Merge each of the populated JSON templates into output
err := mergeJSONTemplates ( logger , output , templates ... )
2019-06-21 17:23:39 +00:00
if err != nil {
2021-10-14 01:59:36 +00:00
logger . Error ( "failed to populate templates for ID token generation" , "error" , err )
return nil , err
2019-06-21 17:23:39 +00:00
}
2021-10-14 01:59:36 +00:00
payload , err := json . Marshal ( output )
if err != nil {
return nil , err
}
return payload , nil
}
// mergeJSONTemplates will merge each of the given JSON templates into the given
// output map. It will simply merge the top-level keys of the unmarshalled JSON
// templates into output, which means that any conflicting keys will be overwritten.
func mergeJSONTemplates ( logger hclog . Logger , output map [ string ] interface { } , templates ... string ) error {
for _ , template := range templates {
2019-06-21 17:23:39 +00:00
var parsed map [ string ] interface { }
2021-10-14 01:59:36 +00:00
if err := json . Unmarshal ( [ ] byte ( template ) , & parsed ) ; err != nil {
2019-06-21 17:23:39 +00:00
logger . Warn ( "error parsing OIDC template" , "template" , template , "err" , err )
}
for k , v := range parsed {
2021-11-22 17:42:22 +00:00
if ! strutil . StrListContains ( reservedClaims , k ) {
2019-06-21 17:23:39 +00:00
output [ k ] = v
} else {
logger . Warn ( "invalid top level OIDC template key" , "template" , template , "key" , k )
}
}
}
2021-10-14 01:59:36 +00:00
return nil
2019-06-21 17:23:39 +00:00
}
2022-01-24 18:05:49 +00:00
// generateAndSetKey will generate new signing and public key pairs and set
// them as the SigningKey.
func ( k * namedKey ) generateAndSetKey ( ctx context . Context , logger hclog . Logger , s logical . Storage ) error {
signingKey , err := generateKeys ( k . Algorithm )
if err != nil {
return err
}
k . SigningKey = signingKey
k . KeyRing = append ( k . KeyRing , & expireableKey { KeyID : signingKey . Public ( ) . KeyID } )
if err := saveOIDCPublicKey ( ctx , s , signingKey . Public ( ) ) ; err != nil {
return err
}
logger . Debug ( "generated OIDC public key to sign JWTs" , "key_id" , signingKey . Public ( ) . KeyID )
return nil
}
2021-11-29 21:10:58 +00:00
// generateAndSetNextKey will generate new signing and public key pairs and set
// them as the NextSigningKey.
func ( k * namedKey ) generateAndSetNextKey ( ctx context . Context , logger hclog . Logger , s logical . Storage ) error {
signingKey , err := generateKeys ( k . Algorithm )
if err != nil {
return err
}
k . NextSigningKey = signingKey
k . KeyRing = append ( k . KeyRing , & expireableKey { KeyID : signingKey . Public ( ) . KeyID } )
if err := saveOIDCPublicKey ( ctx , s , signingKey . Public ( ) ) ; err != nil {
return err
}
logger . Debug ( "generated OIDC public key for future use" , "key_id" , signingKey . Public ( ) . KeyID )
return nil
}
2019-06-21 17:23:39 +00:00
func ( k * namedKey ) signPayload ( payload [ ] byte ) ( string , error ) {
2022-01-24 18:05:49 +00:00
if k . SigningKey == nil {
return "" , fmt . Errorf ( "signing key is nil; rotate the key and try again" )
}
2019-06-21 17:23:39 +00:00
signingKey := jose . SigningKey { Key : k . SigningKey , Algorithm : jose . SignatureAlgorithm ( k . Algorithm ) }
signer , err := jose . NewSigner ( signingKey , & jose . SignerOptions { } )
if err != nil {
return "" , err
}
signature , err := signer . Sign ( payload )
if err != nil {
return "" , err
}
signedIdToken , err := signature . CompactSerialize ( )
if err != nil {
return "" , err
}
return signedIdToken , nil
}
func ( i * IdentityStore ) pathOIDCRoleExistenceCheck ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( bool , error ) {
role , err := i . getOIDCRole ( ctx , req . Storage , d . Get ( "name" ) . ( string ) )
if err != nil {
return false , err
}
return role != nil , nil
}
2021-07-29 01:34:52 +00:00
// pathOIDCCreateUpdateRole is used to create a new role or update an existing one
2019-06-21 17:23:39 +00:00
func ( i * IdentityStore ) pathOIDCCreateUpdateRole ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
2019-07-03 03:15:43 +00:00
ns , err := namespace . FromContext ( ctx )
if err != nil {
return nil , err
}
2019-06-21 17:23:39 +00:00
name := d . Get ( "name" ) . ( string )
2021-09-08 15:46:58 +00:00
i . oidcLock . Lock ( )
defer i . oidcLock . Unlock ( )
2019-06-21 17:23:39 +00:00
var role role
2019-07-02 21:46:22 +00:00
if req . Operation == logical . UpdateOperation {
entry , err := req . Storage . Get ( ctx , roleConfigPath + name )
if err != nil {
2019-06-21 17:23:39 +00:00
return nil , err
}
2019-07-02 21:46:22 +00:00
if entry != nil {
if err := entry . DecodeJSON ( & role ) ; err != nil {
return nil , err
}
}
2019-06-21 17:23:39 +00:00
}
if key , ok := d . GetOk ( "key" ) ; ok {
role . Key = key . ( string )
} else if req . Operation == logical . CreateOperation {
role . Key = d . Get ( "key" ) . ( string )
}
2021-09-08 15:46:58 +00:00
if role . Key == "" {
return logical . ErrorResponse ( "the key parameter is required" ) , nil
}
2019-06-21 17:23:39 +00:00
if template , ok := d . GetOk ( "template" ) ; ok {
role . Template = template . ( string )
} else if req . Operation == logical . CreateOperation {
role . Template = d . Get ( "template" ) . ( string )
}
// Attempt to decode as base64 and use that if it works
if decoded , err := base64 . StdEncoding . DecodeString ( role . Template ) ; err == nil {
role . Template = string ( decoded )
}
// Validate that template can be parsed and results in valid JSON
if role . Template != "" {
2020-01-06 18:16:52 +00:00
_ , populatedTemplate , err := identitytpl . PopulateString ( identitytpl . PopulateStringInput {
Mode : identitytpl . JSONTemplating ,
2019-06-21 17:23:39 +00:00
String : role . Template ,
2020-01-06 18:16:52 +00:00
Entity : new ( logical . Entity ) ,
Groups : make ( [ ] * logical . Group , 0 ) ,
2019-06-21 17:23:39 +00:00
// namespace?
} )
if err != nil {
return logical . ErrorResponse ( "error parsing template: %s" , err . Error ( ) ) , nil
}
var tmp map [ string ] interface { }
if err := json . Unmarshal ( [ ] byte ( populatedTemplate ) , & tmp ) ; err != nil {
return logical . ErrorResponse ( "error parsing template JSON: %s" , err . Error ( ) ) , nil
}
for key := range tmp {
2021-11-22 17:42:22 +00:00
if strutil . StrListContains ( reservedClaims , key ) {
2019-06-21 17:23:39 +00:00
return logical . ErrorResponse ( ` top level key %q not allowed. Restricted keys: %s ` ,
2021-11-22 17:42:22 +00:00
key , strings . Join ( reservedClaims , ", " ) ) , nil
2019-06-21 17:23:39 +00:00
}
}
}
if ttl , ok := d . GetOk ( "ttl" ) ; ok {
role . TokenTTL = time . Duration ( ttl . ( int ) ) * time . Second
} else if req . Operation == logical . CreateOperation {
role . TokenTTL = time . Duration ( d . Get ( "ttl" ) . ( int ) ) * time . Second
}
2021-08-04 18:01:13 +00:00
// get the key referenced by this role if it exists
2021-07-29 01:34:52 +00:00
var key namedKey
entry , err := req . Storage . Get ( ctx , namedKeyConfigPath + role . Key )
if err != nil {
return nil , err
}
2021-09-08 15:46:58 +00:00
if entry == nil {
return logical . ErrorResponse ( "cannot find key %q" , role . Key ) , nil
}
2021-07-29 01:34:52 +00:00
2021-09-08 15:46:58 +00:00
if err := entry . DecodeJSON ( & key ) ; err != nil {
return nil , err
}
if role . TokenTTL > key . VerificationTTL {
return logical . ErrorResponse ( "a role's token ttl cannot be longer than the verification_ttl of the key it references" ) , nil
2021-07-29 01:34:52 +00:00
}
2020-02-14 07:15:35 +00:00
if clientID , ok := d . GetOk ( "client_id" ) ; ok {
role . ClientID = clientID . ( string )
}
2019-06-21 17:23:39 +00:00
// create role path
if role . ClientID == "" {
clientID , err := base62 . Random ( 26 )
if err != nil {
return nil , err
}
role . ClientID = clientID
}
// store role (which was either just created or updated)
2021-07-29 01:34:52 +00:00
entry , err = logical . StorageEntryJSON ( roleConfigPath + name , role )
2019-06-21 17:23:39 +00:00
if err != nil {
return nil , err
}
if err := req . Storage . Put ( ctx , entry ) ; err != nil {
return nil , err
}
2019-12-17 18:43:38 +00:00
if err := i . oidcCache . Flush ( ns ) ; err != nil {
return nil , err
}
2019-06-21 17:23:39 +00:00
return nil , nil
}
// handleOIDCReadRole is used to read an existing role
func ( i * IdentityStore ) pathOIDCReadRole ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
name := d . Get ( "name" ) . ( string )
role , err := i . getOIDCRole ( ctx , req . Storage , name )
if err != nil {
return nil , err
}
if role == nil {
return nil , nil
}
return & logical . Response {
Data : map [ string ] interface { } {
"client_id" : role . ClientID ,
"key" : role . Key ,
"template" : role . Template ,
"ttl" : int64 ( role . TokenTTL . Seconds ( ) ) ,
} ,
} , nil
}
func ( i * IdentityStore ) getOIDCRole ( ctx context . Context , s logical . Storage , roleName string ) ( * role , error ) {
entry , err := s . Get ( ctx , roleConfigPath + roleName )
if err != nil {
return nil , err
}
if entry == nil {
return nil , nil
}
var role role
if err := entry . DecodeJSON ( & role ) ; err != nil {
return nil , err
}
return & role , nil
}
// handleOIDCDeleteRole is used to delete a role if it exists
func ( i * IdentityStore ) pathOIDCDeleteRole ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
name := d . Get ( "name" ) . ( string )
err := req . Storage . Delete ( ctx , roleConfigPath + name )
if err != nil {
return nil , err
}
return nil , nil
}
// handleOIDCListRole is used to list stored a roles
func ( i * IdentityStore ) pathOIDCListRole ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
roles , err := req . Storage . List ( ctx , roleConfigPath )
if err != nil {
return nil , err
}
return logical . ListResponse ( roles ) , nil
}
func ( i * IdentityStore ) pathOIDCDiscovery ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
var data [ ] byte
2019-07-03 03:15:43 +00:00
ns , err := namespace . FromContext ( ctx )
if err != nil {
return nil , err
}
2019-12-17 18:43:38 +00:00
v , ok , err := i . oidcCache . Get ( ns , "discoveryResponse" )
if err != nil {
return nil , err
}
if ok {
2019-06-21 17:23:39 +00:00
data = v . ( [ ] byte )
} else {
c , err := i . getOIDCConfig ( ctx , req . Storage )
if err != nil {
return nil , err
}
disc := discovery {
2019-10-02 15:59:57 +00:00
Issuer : c . effectiveIssuer ,
Keys : c . effectiveIssuer + "/.well-known/keys" ,
ResponseTypes : [ ] string { "id_token" } ,
Subjects : [ ] string { "public" } ,
IDTokenAlgs : supportedAlgs ,
2019-06-21 17:23:39 +00:00
}
data , err = json . Marshal ( disc )
if err != nil {
return nil , err
}
2019-12-17 18:43:38 +00:00
if err := i . oidcCache . SetDefault ( ns , "discoveryResponse" , data ) ; err != nil {
return nil , err
}
2019-06-21 17:23:39 +00:00
}
resp := & logical . Response {
Data : map [ string ] interface { } {
2021-10-14 01:59:36 +00:00
logical . HTTPStatusCode : 200 ,
logical . HTTPRawBody : data ,
logical . HTTPContentType : "application/json" ,
logical . HTTPCacheControlHeader : "max-age=3600" ,
2019-06-21 17:23:39 +00:00
} ,
}
return resp , nil
}
2021-09-09 18:47:42 +00:00
// getKeysCacheControlHeader returns the cache control header for all public
// keys at the .well-known/keys endpoint
func ( i * IdentityStore ) getKeysCacheControlHeader ( ) ( string , error ) {
// if jwksCacheControlMaxAge is set use that, otherwise fall back on the
// more conservative nextRun values
jwksCacheControlMaxAge , ok , err := i . oidcCache . Get ( noNamespace , "jwksCacheControlMaxAge" )
if err != nil {
return "" , err
}
if ok {
maxDuration := int64 ( jwksCacheControlMaxAge . ( time . Duration ) )
randDuration := mathrand . Int63n ( maxDuration )
durationInSeconds := time . Duration ( randDuration ) . Seconds ( )
return fmt . Sprintf ( "max-age=%.0f" , durationInSeconds ) , nil
}
nextRun , ok , err := i . oidcCache . Get ( noNamespace , "nextRun" )
if err != nil {
return "" , err
}
if ok {
now := time . Now ( )
expireAt := nextRun . ( time . Time )
if expireAt . After ( now ) {
i . Logger ( ) . Debug ( "use nextRun value for Cache Control header" , "nextRun" , nextRun )
expireInSeconds := expireAt . Sub ( time . Now ( ) ) . Seconds ( )
return fmt . Sprintf ( "max-age=%.0f" , expireInSeconds ) , nil
}
}
return "" , nil
}
2019-06-21 17:23:39 +00:00
// pathOIDCReadPublicKeys is used to retrieve all public keys so that clients can
// verify the validity of a signed OIDC token.
func ( i * IdentityStore ) pathOIDCReadPublicKeys ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
var data [ ] byte
2019-07-03 03:15:43 +00:00
ns , err := namespace . FromContext ( ctx )
if err != nil {
return nil , err
}
2019-12-17 18:43:38 +00:00
v , ok , err := i . oidcCache . Get ( ns , "jwksResponse" )
if err != nil {
return nil , err
}
if ok {
2019-06-21 17:23:39 +00:00
data = v . ( [ ] byte )
} else {
jwks , err := i . generatePublicJWKS ( ctx , req . Storage )
if err != nil {
return nil , err
}
data , err = json . Marshal ( jwks )
if err != nil {
return nil , err
}
2019-12-17 18:43:38 +00:00
if err := i . oidcCache . SetDefault ( ns , "jwksResponse" , data ) ; err != nil {
return nil , err
}
2019-06-21 17:23:39 +00:00
}
resp := & logical . Response {
Data : map [ string ] interface { } {
logical . HTTPStatusCode : 200 ,
logical . HTTPRawBody : data ,
logical . HTTPContentType : "application/json" ,
} ,
}
2021-09-09 18:47:42 +00:00
// set a Cache-Control header only if there are keys
2019-07-15 18:04:45 +00:00
keys , err := listOIDCPublicKeys ( ctx , req . Storage )
if err != nil {
return nil , err
}
if len ( keys ) > 0 {
2021-09-09 18:47:42 +00:00
header , err := i . getKeysCacheControlHeader ( )
2019-12-17 18:43:38 +00:00
if err != nil {
return nil , err
}
2021-09-09 18:47:42 +00:00
if header != "" {
2021-10-14 01:59:36 +00:00
resp . Data [ logical . HTTPCacheControlHeader ] = header
2019-07-15 18:04:45 +00:00
}
}
2019-06-21 17:23:39 +00:00
return resp , nil
}
func ( i * IdentityStore ) pathOIDCIntrospect ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
var claims jwt . Claims
// helper for preparing the non-standard introspection response
introspectionResp := func ( errorMsg string ) ( * logical . Response , error ) {
response := map [ string ] interface { } {
"active" : true ,
}
if errorMsg != "" {
response [ "active" ] = false
response [ "error" ] = errorMsg
}
data , err := json . Marshal ( response )
if err != nil {
return nil , err
}
resp := & logical . Response {
Data : map [ string ] interface { } {
logical . HTTPStatusCode : 200 ,
logical . HTTPRawBody : data ,
logical . HTTPContentType : "application/json" ,
} ,
}
return resp , nil
}
rawIDToken := d . Get ( "token" ) . ( string )
clientID := d . Get ( "client_id" ) . ( string )
// validate basic JWT structure
parsedJWT , err := jwt . ParseSigned ( rawIDToken )
if err != nil {
return introspectionResp ( fmt . Sprintf ( "error parsing token: %s" , err . Error ( ) ) )
}
// validate signature
jwks , err := i . generatePublicJWKS ( ctx , req . Storage )
if err != nil {
return nil , err
}
var valid bool
for _ , key := range jwks . Keys {
if err := parsedJWT . Claims ( key , & claims ) ; err == nil {
valid = true
break
}
}
if ! valid {
return introspectionResp ( "unable to validate the token signature" )
}
// validate claims
c , err := i . getOIDCConfig ( ctx , req . Storage )
if err != nil {
return nil , err
}
expected := jwt . Expected {
Issuer : c . effectiveIssuer ,
Time : time . Now ( ) ,
}
if clientID != "" {
expected . Audience = [ ] string { clientID }
}
if claimsErr := claims . Validate ( expected ) ; claimsErr != nil {
return introspectionResp ( fmt . Sprintf ( "error validating claims: %s" , claimsErr . Error ( ) ) )
}
// validate entity exists and is active
entity , err := i . MemDBEntityByID ( claims . Subject , true )
if err != nil {
return nil , err
}
if entity == nil {
return introspectionResp ( "entity was not found" )
} else if entity . Disabled {
return introspectionResp ( "entity is disabled" )
}
return introspectionResp ( "" )
}
2021-09-09 18:47:42 +00:00
// namedKey.rotate(overrides) performs a key rotation on a namedKey.
// verification_ttl can be overridden with an overrideVerificationTTL value >= 0
func ( k * namedKey ) rotate ( ctx context . Context , logger hclog . Logger , s logical . Storage , overrideVerificationTTL time . Duration ) error {
2019-06-21 17:23:39 +00:00
verificationTTL := k . VerificationTTL
if overrideVerificationTTL >= 0 {
verificationTTL = overrideVerificationTTL
}
now := time . Now ( )
2022-01-24 18:05:49 +00:00
if k . SigningKey != nil {
// set the previous public key's expiry time
for _ , key := range k . KeyRing {
if key . KeyID == k . SigningKey . KeyID {
key . ExpireAt = now . Add ( verificationTTL )
break
}
2019-06-21 17:23:39 +00:00
}
2022-01-24 18:05:49 +00:00
} else {
// this can occur for keys generated before vault 1.9.0 but rotated on
// vault 1.9.0
logger . Debug ( "nil signing key detected on rotation" )
2019-06-21 17:23:39 +00:00
}
2021-09-09 18:47:42 +00:00
2021-11-29 21:10:58 +00:00
if k . NextSigningKey == nil {
2022-01-24 18:05:49 +00:00
logger . Debug ( "nil next signing key detected on rotation" )
2021-11-29 21:10:58 +00:00
// keys will not have a NextSigningKey if they were generated before
// vault 1.9
err := k . generateAndSetNextKey ( ctx , logger , s )
if err != nil {
return err
}
}
2022-01-24 18:05:49 +00:00
2021-11-29 21:10:58 +00:00
// do the rotation
2021-09-09 18:47:42 +00:00
k . SigningKey = k . NextSigningKey
2019-06-21 17:23:39 +00:00
k . NextRotation = now . Add ( k . RotationPeriod )
2021-11-29 21:10:58 +00:00
// now that we have rotated, generate a new NextSigningKey
err := k . generateAndSetNextKey ( ctx , logger , s )
if err != nil {
return err
}
2019-06-21 17:23:39 +00:00
// store named key (it was modified when rotate was called on it)
2019-07-02 21:46:22 +00:00
entry , err := logical . StorageEntryJSON ( namedKeyConfigPath + k . name , k )
2019-06-21 17:23:39 +00:00
if err != nil {
return err
}
if err := s . Put ( ctx , entry ) ; err != nil {
return err
}
2021-09-09 18:47:42 +00:00
logger . Debug ( "rotated OIDC public key, now using" , "key_id" , k . SigningKey . Public ( ) . KeyID )
2019-06-21 17:23:39 +00:00
return nil
}
// generateKeys returns a signingKey and publicKey pair
func generateKeys ( algorithm string ) ( * jose . JSONWebKey , error ) {
2019-06-27 15:34:48 +00:00
var key interface { }
var err error
switch algorithm {
case "RS256" , "RS384" , "RS512" :
// 2048 bits is recommended by RSA Laboratories as a minimum post 2015
if key , err = rsa . GenerateKey ( rand . Reader , 2048 ) ; err != nil {
return nil , err
}
case "ES256" , "ES384" , "ES512" :
var curve elliptic . Curve
switch algorithm {
case "ES256" :
curve = elliptic . P256 ( )
case "ES384" :
curve = elliptic . P384 ( )
case "ES512" :
curve = elliptic . P521 ( )
}
if key , err = ecdsa . GenerateKey ( curve , rand . Reader ) ; err != nil {
return nil , err
}
case "EdDSA" :
_ , key , err = ed25519 . GenerateKey ( rand . Reader )
if err != nil {
return nil , err
}
default :
return nil , fmt . Errorf ( "unknown algorithm %q" , algorithm )
2019-06-21 17:23:39 +00:00
}
id , err := uuid . GenerateUUID ( )
if err != nil {
return nil , err
}
jwk := & jose . JSONWebKey {
Key : key ,
KeyID : id ,
Algorithm : algorithm ,
Use : "sig" ,
}
return jwk , nil
}
func saveOIDCPublicKey ( ctx context . Context , s logical . Storage , key jose . JSONWebKey ) error {
entry , err := logical . StorageEntryJSON ( publicKeysConfigPath + key . KeyID , key )
if err != nil {
return err
}
if err := s . Put ( ctx , entry ) ; err != nil {
return err
}
return nil
}
func loadOIDCPublicKey ( ctx context . Context , s logical . Storage , keyID string ) ( * jose . JSONWebKey , error ) {
entry , err := s . Get ( ctx , publicKeysConfigPath + keyID )
if err != nil {
return nil , err
}
2021-10-19 00:29:47 +00:00
if entry == nil {
return nil , fmt . Errorf ( "could not find key with ID %s" , keyID )
}
2019-06-21 17:23:39 +00:00
var key jose . JSONWebKey
if err := entry . DecodeJSON ( & key ) ; err != nil {
return nil , err
}
return & key , nil
}
func listOIDCPublicKeys ( ctx context . Context , s logical . Storage ) ( [ ] string , error ) {
keys , err := s . List ( ctx , publicKeysConfigPath )
if err != nil {
return nil , err
}
return keys , nil
}
func ( i * IdentityStore ) generatePublicJWKS ( ctx context . Context , s logical . Storage ) ( * jose . JSONWebKeySet , error ) {
2019-07-03 03:15:43 +00:00
ns , err := namespace . FromContext ( ctx )
if err != nil {
return nil , err
}
2019-12-17 18:43:38 +00:00
jwksRaw , ok , err := i . oidcCache . Get ( ns , "jwks" )
if err != nil {
return nil , err
}
if ok {
2019-06-21 17:23:39 +00:00
return jwksRaw . ( * jose . JSONWebKeySet ) , nil
}
if _ , err := i . expireOIDCPublicKeys ( ctx , s ) ; err != nil {
return nil , err
}
2021-10-12 16:14:03 +00:00
jwks := & jose . JSONWebKeySet {
Keys : make ( [ ] jose . JSONWebKey , 0 ) ,
}
// only return keys that are associated with a role
roleNames , err := s . List ( ctx , roleConfigPath )
2019-06-21 17:23:39 +00:00
if err != nil {
return nil , err
}
2021-10-12 16:14:03 +00:00
for _ , roleName := range roleNames {
role , err := i . getOIDCRole ( ctx , s , roleName )
if err != nil {
return nil , err
}
if role == nil {
continue
}
2019-06-21 17:23:39 +00:00
2021-10-12 16:14:03 +00:00
keyIDs , err := i . keyIDsByName ( ctx , s , role . Key )
2019-06-21 17:23:39 +00:00
if err != nil {
return nil , err
}
2021-10-12 16:14:03 +00:00
for _ , keyID := range keyIDs {
key , err := loadOIDCPublicKey ( ctx , s , keyID )
if err != nil {
return nil , err
}
jwks . Keys = append ( jwks . Keys , * key )
}
2019-06-21 17:23:39 +00:00
}
2019-12-17 18:43:38 +00:00
if err := i . oidcCache . SetDefault ( ns , "jwks" , jwks ) ; err != nil {
return nil , err
}
2019-06-21 17:23:39 +00:00
return jwks , nil
}
func ( i * IdentityStore ) expireOIDCPublicKeys ( ctx context . Context , s logical . Storage ) ( time . Time , error ) {
var didUpdate bool
2019-07-03 03:15:43 +00:00
i . oidcLock . Lock ( )
defer i . oidcLock . Unlock ( )
ns , err := namespace . FromContext ( ctx )
if err != nil {
return time . Time { } , err
}
2019-06-21 17:23:39 +00:00
// nextExpiration will be the soonest expiration time of all keys. Initialize
// here to a relatively distant time.
nextExpiration := time . Now ( ) . Add ( 24 * time . Hour )
now := time . Now ( )
publicKeyIDs , err := listOIDCPublicKeys ( ctx , s )
if err != nil {
return now , err
}
2022-01-24 18:05:49 +00:00
keyNames , err := s . List ( ctx , namedKeyConfigPath )
2019-06-21 17:23:39 +00:00
if err != nil {
return now , err
}
2022-01-14 23:35:27 +00:00
usedKeys := make ( [ ] string , 0 )
2019-06-21 17:23:39 +00:00
2022-01-24 18:05:49 +00:00
for _ , keyName := range keyNames {
entry , err := s . Get ( ctx , namedKeyConfigPath + keyName )
2019-06-21 17:23:39 +00:00
if err != nil {
return now , err
}
2021-10-19 00:29:47 +00:00
if entry == nil {
2022-01-24 18:05:49 +00:00
i . Logger ( ) . Warn ( "could not find key to update" , "key" , keyName )
2021-10-19 00:29:47 +00:00
continue
}
2019-06-21 17:23:39 +00:00
var key namedKey
if err := entry . DecodeJSON ( & key ) ; err != nil {
return now , err
}
// Remove any expired keys from the keyring.
keyRing := key . KeyRing
var keyringUpdated bool
2022-01-24 18:05:49 +00:00
for j := 0 ; j < len ( keyRing ) ; j ++ {
k := keyRing [ j ]
2019-06-21 17:23:39 +00:00
if ! k . ExpireAt . IsZero ( ) && k . ExpireAt . Before ( now ) {
2022-01-24 18:05:49 +00:00
keyRing [ j ] = keyRing [ len ( keyRing ) - 1 ]
2019-06-21 17:23:39 +00:00
keyRing = keyRing [ : len ( keyRing ) - 1 ]
keyringUpdated = true
2022-01-24 18:05:49 +00:00
j --
2019-06-21 17:23:39 +00:00
continue
}
// Save a remaining key's next expiration if it is the earliest we've
// seen (for use by the periodicFunc for scheduling).
if ! k . ExpireAt . IsZero ( ) && k . ExpireAt . Before ( nextExpiration ) {
nextExpiration = k . ExpireAt
}
// Mark the KeyID as in use so it doesn't get deleted in the next step
usedKeys = append ( usedKeys , k . KeyID )
}
// Persist any keyring updates if necessary
if keyringUpdated {
key . KeyRing = keyRing
entry , err := logical . StorageEntryJSON ( entry . Key , key )
if err != nil {
2019-07-02 21:46:22 +00:00
i . Logger ( ) . Error ( "error updating key" , "key" , key . name , "error" , err )
2019-06-21 17:23:39 +00:00
}
if err := s . Put ( ctx , entry ) ; err != nil {
2019-07-02 21:46:22 +00:00
i . Logger ( ) . Error ( "error saving key" , "key" , key . name , "error" , err )
2019-06-21 17:23:39 +00:00
}
didUpdate = true
}
}
// Delete all public keys that were not determined to be not expired and in
// use by some role.
for _ , keyID := range publicKeyIDs {
if ! strutil . StrListContains ( usedKeys , keyID ) {
didUpdate = true
if err := s . Delete ( ctx , publicKeysConfigPath + keyID ) ; err != nil {
i . Logger ( ) . Error ( "error deleting OIDC public key" , "key_id" , keyID , "error" , err )
nextExpiration = now
}
i . Logger ( ) . Debug ( "deleted OIDC public key" , "key_id" , keyID )
}
}
if didUpdate {
2019-12-17 18:43:38 +00:00
if err := i . oidcCache . Flush ( ns ) ; err != nil {
i . Logger ( ) . Error ( "error flushing oidc cache" , "error" , err )
}
2019-06-21 17:23:39 +00:00
}
return nextExpiration , nil
}
2021-09-09 18:47:42 +00:00
// oidcKeyRotation will rotate any keys that are due to be rotated.
//
// It will return the time of the soonest rotation and the minimum
// verificationTTL or minimum rotationPeriod out of all the current keys.
func ( i * IdentityStore ) oidcKeyRotation ( ctx context . Context , s logical . Storage ) ( time . Time , time . Duration , error ) {
2019-06-21 17:23:39 +00:00
// soonestRotation will be the soonest rotation time of all keys. Initialize
// here to a relatively distant time.
now := time . Now ( )
soonestRotation := now . Add ( 24 * time . Hour )
2021-09-09 18:47:42 +00:00
jwksClientCacheDuration := time . Duration ( math . MaxInt64 )
2019-07-03 03:15:43 +00:00
i . oidcLock . Lock ( )
defer i . oidcLock . Unlock ( )
2019-06-21 17:23:39 +00:00
keys , err := s . List ( ctx , namedKeyConfigPath )
if err != nil {
2021-09-09 18:47:42 +00:00
return now , jwksClientCacheDuration , err
2019-06-21 17:23:39 +00:00
}
for _ , k := range keys {
entry , err := s . Get ( ctx , namedKeyConfigPath + k )
if err != nil {
2021-09-09 18:47:42 +00:00
return now , jwksClientCacheDuration , err
2019-06-21 17:23:39 +00:00
}
2019-07-03 03:15:43 +00:00
if entry == nil {
continue
}
2019-06-21 17:23:39 +00:00
var key namedKey
if err := entry . DecodeJSON ( & key ) ; err != nil {
2021-09-09 18:47:42 +00:00
return now , jwksClientCacheDuration , err
2019-06-21 17:23:39 +00:00
}
2019-07-02 21:46:22 +00:00
key . name = k
2019-06-21 17:23:39 +00:00
2021-09-09 18:47:42 +00:00
if key . VerificationTTL < jwksClientCacheDuration {
jwksClientCacheDuration = key . VerificationTTL
}
if key . RotationPeriod < jwksClientCacheDuration {
jwksClientCacheDuration = key . RotationPeriod
}
2019-06-21 17:23:39 +00:00
// Future key rotation that is the earliest we've seen.
if now . Before ( key . NextRotation ) && key . NextRotation . Before ( soonestRotation ) {
soonestRotation = key . NextRotation
}
// Key that is due to be rotated.
if now . After ( key . NextRotation ) {
2019-07-02 21:46:22 +00:00
i . Logger ( ) . Debug ( "rotating OIDC key" , "key" , key . name )
2021-09-09 18:47:42 +00:00
if err := key . rotate ( ctx , i . Logger ( ) , s , - 1 ) ; err != nil {
return now , jwksClientCacheDuration , err
2019-06-21 17:23:39 +00:00
}
// Possibly save the new rotation time
if key . NextRotation . Before ( soonestRotation ) {
soonestRotation = key . NextRotation
}
}
}
2021-09-09 18:47:42 +00:00
return soonestRotation , jwksClientCacheDuration , nil
2019-06-21 17:23:39 +00:00
}
// oidcPeriodFunc is invoked by the backend's periodFunc and runs regular key
// rotations and expiration actions.
2019-07-03 03:15:43 +00:00
func ( i * IdentityStore ) oidcPeriodicFunc ( ctx context . Context ) {
var nextRun time . Time
now := time . Now ( )
2019-12-17 18:43:38 +00:00
v , ok , err := i . oidcCache . Get ( noNamespace , "nextRun" )
if err != nil {
i . Logger ( ) . Error ( "error reading oidc cache" , "err" , err )
return
}
if ok {
2019-06-21 17:23:39 +00:00
nextRun = v . ( time . Time )
}
// The condition here is for performance, not precise timing. The actions can
// be run at any time safely, but there is no need to invoke them (which
// might be somewhat expensive if there are many roles/keys) if we're not
// past any rotation/expiration TTLs.
2019-07-03 03:15:43 +00:00
if now . After ( nextRun ) {
// Initialize to a fairly distant next run time. This will be brought in
// based on key rotation times.
nextRun = now . Add ( 24 * time . Hour )
2021-09-09 18:47:42 +00:00
minJwksClientCacheDuration := time . Duration ( math . MaxInt64 )
2019-06-21 17:23:39 +00:00
2021-08-30 19:31:11 +00:00
for _ , ns := range i . namespacer . ListNamespaces ( ) {
2020-02-26 20:56:19 +00:00
nsPath := ns . Path
2021-08-30 19:31:11 +00:00
s := i . router . MatchingStorageByAPIPath ( ctx , nsPath + "identity/oidc" )
2019-07-03 03:15:43 +00:00
if s == nil {
continue
}
2021-09-09 18:47:42 +00:00
nextRotation , jwksClientCacheDuration , err := i . oidcKeyRotation ( ctx , s )
2019-07-03 03:15:43 +00:00
if err != nil {
i . Logger ( ) . Warn ( "error rotating OIDC keys" , "err" , err )
}
nextExpiration , err := i . expireOIDCPublicKeys ( ctx , s )
if err != nil {
i . Logger ( ) . Warn ( "error expiring OIDC public keys" , "err" , err )
}
2020-02-26 20:56:19 +00:00
if err := i . oidcCache . Flush ( ns ) ; err != nil {
2019-12-17 18:43:38 +00:00
i . Logger ( ) . Error ( "error flushing oidc cache" , "err" , err )
}
2019-06-21 17:23:39 +00:00
2019-07-03 03:15:43 +00:00
// re-run at the soonest expiration or rotation time
if nextRotation . Before ( nextRun ) {
nextRun = nextRotation
}
2019-06-21 17:23:39 +00:00
2019-07-03 03:15:43 +00:00
if nextExpiration . Before ( nextRun ) {
nextRun = nextExpiration
}
2021-09-09 18:47:42 +00:00
if jwksClientCacheDuration < minJwksClientCacheDuration {
minJwksClientCacheDuration = jwksClientCacheDuration
}
2019-06-21 17:23:39 +00:00
}
2021-09-09 18:47:42 +00:00
2019-12-17 18:43:38 +00:00
if err := i . oidcCache . SetDefault ( noNamespace , "nextRun" , nextRun ) ; err != nil {
i . Logger ( ) . Error ( "error setting oidc cache" , "err" , err )
}
2021-09-09 18:47:42 +00:00
if minJwksClientCacheDuration < math . MaxInt64 {
// the OIDC JWKS endpoint returns a Cache-Control HTTP header time between
// 0 and the minimum verificationTTL or minimum rotationPeriod out of all
// keys, whichever value is lower.
//
// This smooths calls from services validating JWTs to Vault, while
// ensuring that operators can assert that servers honoring the
// Cache-Control header will always have a superset of all valid keys, and
// not trust any keys longer than a jwksCacheControlMaxAge duration after a
// key is rotated out of signing use
if err := i . oidcCache . SetDefault ( noNamespace , "jwksCacheControlMaxAge" , minJwksClientCacheDuration ) ; err != nil {
i . Logger ( ) . Error ( "error setting jwksCacheControlMaxAge in oidc cache" , "err" , err )
}
}
2019-06-21 17:23:39 +00:00
}
}
2019-07-03 03:15:43 +00:00
2021-09-27 17:55:29 +00:00
func newOIDCCache ( defaultExpiration , cleanupInterval time . Duration ) * oidcCache {
2019-07-03 03:15:43 +00:00
return & oidcCache {
2021-09-27 17:55:29 +00:00
c : cache . New ( defaultExpiration , cleanupInterval ) ,
2019-07-03 03:15:43 +00:00
}
}
func ( c * oidcCache ) nskey ( ns * namespace . Namespace , key string ) string {
return fmt . Sprintf ( "v0:%s:%s" , ns . ID , key )
}
2019-12-17 18:43:38 +00:00
func ( c * oidcCache ) Get ( ns * namespace . Namespace , key string ) ( interface { } , bool , error ) {
if ns == nil {
return nil , false , errNilNamespace
}
v , found := c . c . Get ( c . nskey ( ns , key ) )
return v , found , nil
2019-07-03 03:15:43 +00:00
}
2019-12-17 18:43:38 +00:00
func ( c * oidcCache ) SetDefault ( ns * namespace . Namespace , key string , obj interface { } ) error {
if ns == nil {
return errNilNamespace
}
2019-07-03 03:15:43 +00:00
c . c . SetDefault ( c . nskey ( ns , key ) , obj )
2019-12-17 18:43:38 +00:00
return nil
2019-07-03 03:15:43 +00:00
}
2021-10-14 01:59:36 +00:00
func ( c * oidcCache ) Delete ( ns * namespace . Namespace , key string ) error {
if ns == nil {
return errNilNamespace
}
c . c . Delete ( c . nskey ( ns , key ) )
return nil
}
2019-12-17 18:43:38 +00:00
func ( c * oidcCache ) Flush ( ns * namespace . Namespace ) error {
if ns == nil {
return errNilNamespace
}
2020-02-26 20:56:19 +00:00
// Remove all items from the provided namespace as well as the shared, "no namespace" section.
2019-07-23 16:47:33 +00:00
for itemKey := range c . c . Items ( ) {
2019-12-17 18:43:38 +00:00
if isTargetNamespacedKey ( itemKey , [ ] string { noNamespace . ID , ns . ID } ) {
2019-07-23 16:47:33 +00:00
c . c . Delete ( itemKey )
}
}
2019-12-17 18:43:38 +00:00
return nil
2019-07-23 16:47:33 +00:00
}
2019-07-27 02:53:40 +00:00
// isTargetNamespacedKey returns true for a properly constructed namespaced key (<version>:<nsID>:<key>)
// where <nsID> matches any targeted nsID
func isTargetNamespacedKey ( nskey string , nsTargets [ ] string ) bool {
2019-07-23 16:47:33 +00:00
split := strings . Split ( nskey , ":" )
2019-07-27 02:53:40 +00:00
return len ( split ) >= 3 && strutil . StrListContains ( nsTargets , split [ 1 ] )
2019-07-03 03:15:43 +00:00
}