mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 19:17:58 +00:00
Filter identity token keys (#12780)
* filter identity token keys * Update test cases to associate keys with roles * use getOIDCRole helper * add func comment and test assertion * add changelog * remove unnecessary code * build list of keys to return by starting with a list of roles * move comment * update changelog
This commit is contained in:
committed by
GitHub
parent
452e47cbf2
commit
ee40205a62
3
changelog/12780.txt
Normal file
3
changelog/12780.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:improvement
|
||||||
|
identity/token: Only return keys from the `.well-known/keys` endpoint that are being used by roles to sign/verify tokens.
|
||||||
|
```
|
||||||
@@ -613,6 +613,27 @@ func (i *IdentityStore) pathOIDCReadKey(ctx context.Context, req *logical.Reques
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
// rolesReferencingTargetKeyName returns a map of role names to roles
|
// rolesReferencingTargetKeyName returns a map of role names to roles
|
||||||
// referencing targetKeyName.
|
// referencing targetKeyName.
|
||||||
//
|
//
|
||||||
@@ -1538,13 +1559,28 @@ func (i *IdentityStore) generatePublicJWKS(ctx context.Context, s logical.Storag
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
keyIDs, err := listOIDCPublicKeys(ctx, s)
|
jwks := &jose.JSONWebKeySet{
|
||||||
|
Keys: make([]jose.JSONWebKey, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
// only return keys that are associated with a role
|
||||||
|
roleNames, err := s.List(ctx, roleConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
jwks := &jose.JSONWebKeySet{
|
for _, roleName := range roleNames {
|
||||||
Keys: make([]jose.JSONWebKey, 0, len(keyIDs)),
|
role, err := i.getOIDCRole(ctx, s, roleName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if role == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
keyIDs, err := i.keyIDsByName(ctx, s, role.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, keyID := range keyIDs {
|
for _, keyID := range keyIDs {
|
||||||
@@ -1554,6 +1590,7 @@ func (i *IdentityStore) generatePublicJWKS(ctx context.Context, s logical.Storag
|
|||||||
}
|
}
|
||||||
jwks.Keys = append(jwks.Keys, *key)
|
jwks.Keys = append(jwks.Keys, *key)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := i.oidcCache.SetDefault(ns, "jwks", jwks); err != nil {
|
if err := i.oidcCache.SetDefault(ns, "jwks", jwks); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package vault
|
package vault
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -637,6 +638,43 @@ func TestOIDC_Path_OIDCKey_DeleteWithExistingClient(t *testing.T) {
|
|||||||
expectError(t, resp, err)
|
expectError(t, resp, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestOIDC_PublicKeys_NoRole tests that public keys are not returned by the
|
||||||
|
// oidc/.well-known/keys endpoint when they are not associated with a role
|
||||||
|
func TestOIDC_PublicKeys_NoRole(t *testing.T) {
|
||||||
|
c, _, _ := TestCoreUnsealed(t)
|
||||||
|
ctx := namespace.RootContext(nil)
|
||||||
|
s := &logical.InmemStorage{}
|
||||||
|
|
||||||
|
// Create a test key "test-key"
|
||||||
|
resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
|
||||||
|
Path: "oidc/key/test-key",
|
||||||
|
Operation: logical.CreateOperation,
|
||||||
|
Storage: s,
|
||||||
|
})
|
||||||
|
expectSuccess(t, resp, err)
|
||||||
|
|
||||||
|
// .well-known/keys should contain 0 public keys
|
||||||
|
assertPublicKeyCount(t, ctx, s, c, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertPublicKeyCount(t *testing.T, ctx context.Context, s logical.Storage, c *Core, keyCount int) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
// .well-known/keys should contain keyCount public keys
|
||||||
|
resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
|
||||||
|
Path: "oidc/.well-known/keys",
|
||||||
|
Operation: logical.ReadOperation,
|
||||||
|
Storage: s,
|
||||||
|
})
|
||||||
|
expectSuccess(t, resp, err)
|
||||||
|
// parse response
|
||||||
|
responseJWKS := &jose.JSONWebKeySet{}
|
||||||
|
json.Unmarshal(resp.Data["http_raw_body"].([]byte), responseJWKS)
|
||||||
|
if len(responseJWKS.Keys) != keyCount {
|
||||||
|
t.Fatalf("expected %d public keys but instead got %d", keyCount, len(responseJWKS.Keys))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestOIDC_PublicKeys tests that public keys are updated by
|
// TestOIDC_PublicKeys tests that public keys are updated by
|
||||||
// key creation, rotation, and deletion
|
// key creation, rotation, and deletion
|
||||||
func TestOIDC_PublicKeys(t *testing.T) {
|
func TestOIDC_PublicKeys(t *testing.T) {
|
||||||
@@ -651,23 +689,22 @@ func TestOIDC_PublicKeys(t *testing.T) {
|
|||||||
Storage: storage,
|
Storage: storage,
|
||||||
})
|
})
|
||||||
|
|
||||||
// .well-known/keys should contain 2 public keys
|
// Create a test role "test-role"
|
||||||
resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
|
c.identityStore.HandleRequest(ctx, &logical.Request{
|
||||||
Path: "oidc/.well-known/keys",
|
Path: "oidc/role/test-role",
|
||||||
Operation: logical.ReadOperation,
|
Operation: logical.CreateOperation,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"key": "test-key",
|
||||||
|
},
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
})
|
})
|
||||||
expectSuccess(t, resp, err)
|
|
||||||
// parse response
|
// .well-known/keys should contain 2 public keys
|
||||||
responseJWKS := &jose.JSONWebKeySet{}
|
assertPublicKeyCount(t, ctx, storage, c, 2)
|
||||||
json.Unmarshal(resp.Data["http_raw_body"].([]byte), responseJWKS)
|
|
||||||
if len(responseJWKS.Keys) != 2 {
|
|
||||||
t.Fatalf("expected 2 public keys but instead got %d", len(responseJWKS.Keys))
|
|
||||||
}
|
|
||||||
|
|
||||||
// rotate test-key a few times, each rotate should increase the length of public keys returned
|
// rotate test-key a few times, each rotate should increase the length of public keys returned
|
||||||
// by the .well-known endpoint
|
// by the .well-known endpoint
|
||||||
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
|
resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
|
||||||
Path: "oidc/key/test-key/rotate",
|
Path: "oidc/key/test-key/rotate",
|
||||||
Operation: logical.UpdateOperation,
|
Operation: logical.UpdateOperation,
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
@@ -681,45 +718,47 @@ func TestOIDC_PublicKeys(t *testing.T) {
|
|||||||
expectSuccess(t, resp, err)
|
expectSuccess(t, resp, err)
|
||||||
|
|
||||||
// .well-known/keys should contain 4 public keys
|
// .well-known/keys should contain 4 public keys
|
||||||
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
|
assertPublicKeyCount(t, ctx, storage, c, 4)
|
||||||
Path: "oidc/.well-known/keys",
|
|
||||||
Operation: logical.ReadOperation,
|
|
||||||
Storage: storage,
|
|
||||||
})
|
|
||||||
expectSuccess(t, resp, err)
|
|
||||||
// parse response
|
|
||||||
json.Unmarshal(resp.Data["http_raw_body"].([]byte), responseJWKS)
|
|
||||||
if len(responseJWKS.Keys) != 4 {
|
|
||||||
t.Fatalf("expected 4 public keys but instead got %d", len(responseJWKS.Keys))
|
|
||||||
}
|
|
||||||
|
|
||||||
// create another named key
|
// create another named key "test-key2"
|
||||||
c.identityStore.HandleRequest(ctx, &logical.Request{
|
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
|
||||||
Path: "oidc/key/test-key2",
|
Path: "oidc/key/test-key2",
|
||||||
Operation: logical.CreateOperation,
|
Operation: logical.CreateOperation,
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
})
|
})
|
||||||
|
expectSuccess(t, resp, err)
|
||||||
|
|
||||||
|
// Create a test role "test-role2"
|
||||||
|
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
|
||||||
|
Path: "oidc/role/test-role2",
|
||||||
|
Operation: logical.CreateOperation,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"key": "test-key2",
|
||||||
|
},
|
||||||
|
Storage: storage,
|
||||||
|
})
|
||||||
|
expectSuccess(t, resp, err)
|
||||||
|
// .well-known/keys should contain 6 public keys
|
||||||
|
assertPublicKeyCount(t, ctx, storage, c, 6)
|
||||||
|
|
||||||
|
// delete test role that references "test-key"
|
||||||
|
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
|
||||||
|
Path: "oidc/role/test-role",
|
||||||
|
Operation: logical.DeleteOperation,
|
||||||
|
Storage: storage,
|
||||||
|
})
|
||||||
|
expectSuccess(t, resp, err)
|
||||||
// delete test key
|
// delete test key
|
||||||
c.identityStore.HandleRequest(ctx, &logical.Request{
|
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
|
||||||
Path: "oidc/key/test-key",
|
Path: "oidc/key/test-key",
|
||||||
Operation: logical.DeleteOperation,
|
Operation: logical.DeleteOperation,
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
})
|
})
|
||||||
|
|
||||||
// .well-known/keys should contain 2 public key, all of the public keys
|
|
||||||
// from named key "test-key" should have been deleted
|
|
||||||
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
|
|
||||||
Path: "oidc/.well-known/keys",
|
|
||||||
Operation: logical.ReadOperation,
|
|
||||||
Storage: storage,
|
|
||||||
})
|
|
||||||
expectSuccess(t, resp, err)
|
expectSuccess(t, resp, err)
|
||||||
// parse response
|
|
||||||
json.Unmarshal(resp.Data["http_raw_body"].([]byte), responseJWKS)
|
// .well-known/keys should contain 2 public keys, all of the public keys
|
||||||
if len(responseJWKS.Keys) != 2 {
|
// from named key "test-key" should have been deleted
|
||||||
t.Fatalf("expected 2 public keys but instead got %d", len(responseJWKS.Keys))
|
assertPublicKeyCount(t, ctx, storage, c, 2)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestOIDC_SignIDToken tests acquiring a signed token and verifying the public portion
|
// TestOIDC_SignIDToken tests acquiring a signed token and verifying the public portion
|
||||||
|
|||||||
Reference in New Issue
Block a user