mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 17:52:32 +00:00
sys: adds identity_token_key to mounts/auth for enable/tune (#24962)
* sys: adds identity_token_key to mounts/auth for enable/tune * adds changelog * adds godoc on new tests * adds function for identityStoreKeyExists * use read lock, remove helper func * tune test in logical_system_test, remove router access method * fix key existence check in namespaces
This commit is contained in:
@@ -272,6 +272,8 @@ type MountConfigInput struct {
|
||||
PluginVersion string `json:"plugin_version,omitempty"`
|
||||
UserLockoutConfig *UserLockoutConfigInput `json:"user_lockout_config,omitempty"`
|
||||
DelegatedAuthAccessors []string `json:"delegated_auth_accessors,omitempty" mapstructure:"delegated_auth_accessors"`
|
||||
IdentityTokenKey string `json:"identity_token_key,omitempty"`
|
||||
|
||||
// Deprecated: This field will always be blank for newer server responses.
|
||||
PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"`
|
||||
}
|
||||
@@ -305,6 +307,7 @@ type MountConfigOutput struct {
|
||||
AllowedManagedKeys []string `json:"allowed_managed_keys,omitempty" mapstructure:"allowed_managed_keys"`
|
||||
UserLockoutConfig *UserLockoutConfigOutput `json:"user_lockout_config,omitempty"`
|
||||
DelegatedAuthAccessors []string `json:"delegated_auth_accessors,omitempty" mapstructure:"delegated_auth_accessors"`
|
||||
IdentityTokenKey string `json:"identity_token_key,omitempty"`
|
||||
|
||||
// Deprecated: This field will always be blank for newer server responses.
|
||||
PluginName string `json:"plugin_name,omitempty" mapstructure:"plugin_name"`
|
||||
|
||||
3
changelog/24962.txt
Normal file
3
changelog/24962.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
sys: adds configuration of the key used to sign plugin identity tokens during mount enable and tune
|
||||
```
|
||||
@@ -1288,7 +1288,7 @@ func TestOIDC_pathOIDCKeyExistenceCheck(t *testing.T) {
|
||||
t.Fatalf("Expected existence check to return false but instead returned: %t", exists)
|
||||
}
|
||||
|
||||
// Populte storage with a namedKey
|
||||
// Populate storage with a namedKey
|
||||
namedKey := &namedKey{}
|
||||
entry, _ := logical.StorageEntryJSON(namedKeyConfigPath+keyName, namedKey)
|
||||
if err := storage.Put(ctx, entry); err != nil {
|
||||
|
||||
@@ -1277,6 +1277,9 @@ func (b *SystemBackend) mountInfo(ctx context.Context, entry *MountEntry) map[st
|
||||
if rawVal, ok := entry.synthesizedConfigCache.Load("allowed_managed_keys"); ok {
|
||||
entryConfig["allowed_managed_keys"] = rawVal.([]string)
|
||||
}
|
||||
if rawVal, ok := entry.synthesizedConfigCache.Load("identity_token_key"); ok {
|
||||
entryConfig["identity_token_key"] = rawVal.(string)
|
||||
}
|
||||
if entry.Table == credentialTableType {
|
||||
entryConfig["token_type"] = entry.Config.TokenType.String()
|
||||
}
|
||||
@@ -1498,6 +1501,26 @@ func (b *SystemBackend) handleMount(ctx context.Context, req *logical.Request, d
|
||||
config.DelegatedAuthAccessors = apiConfig.DelegatedAuthAccessors
|
||||
}
|
||||
|
||||
if apiConfig.IdentityTokenKey != "" {
|
||||
storage := b.Core.router.MatchingStorageByAPIPath(ctx, mountPathIdentity)
|
||||
if storage == nil {
|
||||
return nil, errors.New("failed to find identity storage")
|
||||
}
|
||||
|
||||
identityStore := b.Core.IdentityStore()
|
||||
identityStore.oidcLock.RLock()
|
||||
defer identityStore.oidcLock.RUnlock()
|
||||
k, err := identityStore.getNamedKey(ctx, storage, apiConfig.IdentityTokenKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed getting key %q: %w", apiConfig.IdentityTokenKey, err)
|
||||
}
|
||||
if k == nil {
|
||||
return logical.ErrorResponse("key %q does not exist", apiConfig.IdentityTokenKey), nil
|
||||
}
|
||||
|
||||
config.IdentityTokenKey = apiConfig.IdentityTokenKey
|
||||
}
|
||||
|
||||
// Create the mount entry
|
||||
me := &MountEntry{
|
||||
Table: mountTableType,
|
||||
@@ -1954,6 +1977,10 @@ func (b *SystemBackend) handleTuneReadCommon(ctx context.Context, path string) (
|
||||
resp.Data["allowed_managed_keys"] = rawVal.([]string)
|
||||
}
|
||||
|
||||
if rawVal, ok := mountEntry.synthesizedConfigCache.Load("identity_token_key"); ok {
|
||||
resp.Data["identity_token_key"] = rawVal.(string)
|
||||
}
|
||||
|
||||
if mountEntry.Config.UserLockoutConfig != nil {
|
||||
resp.Data["user_lockout_counter_reset_duration"] = int64(mountEntry.Config.UserLockoutConfig.LockoutCounterReset.Seconds())
|
||||
resp.Data["user_lockout_threshold"] = mountEntry.Config.UserLockoutConfig.LockoutThreshold
|
||||
@@ -2245,6 +2272,50 @@ func (b *SystemBackend) handleTuneWriteCommon(ctx context.Context, path string,
|
||||
}
|
||||
}
|
||||
|
||||
if rawVal, ok := data.GetOk("identity_token_key"); ok {
|
||||
identityTokenKey := rawVal.(string)
|
||||
|
||||
if identityTokenKey != "" {
|
||||
storage := b.Core.router.MatchingStorageByAPIPath(ctx, mountPathIdentity)
|
||||
if storage == nil {
|
||||
return nil, errors.New("failed to find identity storage")
|
||||
}
|
||||
|
||||
identityStore := b.Core.IdentityStore()
|
||||
identityStore.oidcLock.RLock()
|
||||
defer identityStore.oidcLock.RUnlock()
|
||||
k, err := identityStore.getNamedKey(ctx, storage, identityTokenKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed getting key %q: %w", identityTokenKey, err)
|
||||
}
|
||||
if k == nil {
|
||||
return logical.ErrorResponse("key %q does not exist", identityTokenKey), nil
|
||||
}
|
||||
}
|
||||
|
||||
oldVal := mountEntry.Config.IdentityTokenKey
|
||||
mountEntry.Config.IdentityTokenKey = identityTokenKey
|
||||
|
||||
// Update the mount table
|
||||
var err error
|
||||
switch {
|
||||
case strings.HasPrefix(path, "auth/"):
|
||||
err = b.Core.persistAuth(ctx, b.Core.auth, &mountEntry.Local)
|
||||
default:
|
||||
err = b.Core.persistMounts(ctx, b.Core.mounts, &mountEntry.Local)
|
||||
}
|
||||
if err != nil {
|
||||
mountEntry.Config.IdentityTokenKey = oldVal
|
||||
return handleError(err)
|
||||
}
|
||||
|
||||
mountEntry.SyncCache()
|
||||
|
||||
if b.Core.logger.IsInfo() {
|
||||
b.Core.logger.Info("mount tuning of identity_token_key successful", "path", path)
|
||||
}
|
||||
}
|
||||
|
||||
if rawVal, ok := data.GetOk("audit_non_hmac_request_keys"); ok {
|
||||
auditNonHMACRequestKeys := rawVal.([]string)
|
||||
|
||||
@@ -3000,6 +3071,26 @@ func (b *SystemBackend) handleEnableAuth(ctx context.Context, req *logical.Reque
|
||||
config.AllowedManagedKeys = apiConfig.AllowedManagedKeys
|
||||
}
|
||||
|
||||
if apiConfig.IdentityTokenKey != "" {
|
||||
storage := b.Core.router.MatchingStorageByAPIPath(ctx, mountPathIdentity)
|
||||
if storage == nil {
|
||||
return nil, errors.New("failed to find identity storage")
|
||||
}
|
||||
|
||||
identityStore := b.Core.IdentityStore()
|
||||
identityStore.oidcLock.RLock()
|
||||
defer identityStore.oidcLock.RUnlock()
|
||||
k, err := identityStore.getNamedKey(ctx, storage, apiConfig.IdentityTokenKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed getting key %q: %w", apiConfig.IdentityTokenKey, err)
|
||||
}
|
||||
if k == nil {
|
||||
return logical.ErrorResponse("key %q does not exist", apiConfig.IdentityTokenKey), nil
|
||||
}
|
||||
|
||||
config.IdentityTokenKey = apiConfig.IdentityTokenKey
|
||||
}
|
||||
|
||||
// Create the mount entry
|
||||
me := &MountEntry{
|
||||
Table: credentialTableType,
|
||||
@@ -6445,6 +6536,10 @@ This path responds to the following HTTP methods.
|
||||
"Whether the container runtime is run as a non-privileged (non-root) user.",
|
||||
"",
|
||||
},
|
||||
"identity_token_key": {
|
||||
"The name of the key used to sign plugin identity tokens. Defaults to the default key.",
|
||||
"",
|
||||
},
|
||||
"leases": {
|
||||
`View or list lease metadata.`,
|
||||
`
|
||||
|
||||
@@ -3721,6 +3721,11 @@ func (b *SystemBackend) authPaths() []*framework.Path {
|
||||
Type: framework.TypeString,
|
||||
Description: strings.TrimSpace(sysHelp["plugin-catalog_version"][0]),
|
||||
},
|
||||
"identity_token_key": {
|
||||
Type: framework.TypeString,
|
||||
Description: strings.TrimSpace(sysHelp["identity_token_key"][0]),
|
||||
Required: false,
|
||||
},
|
||||
},
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
logical.ReadOperation: &framework.PathOperation{
|
||||
@@ -3807,6 +3812,10 @@ func (b *SystemBackend) authPaths() []*framework.Path {
|
||||
Type: framework.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
"identity_token_key": {
|
||||
Type: framework.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
@@ -4573,6 +4582,10 @@ func (b *SystemBackend) mountPaths() []*framework.Path {
|
||||
Type: framework.TypeMap,
|
||||
Description: strings.TrimSpace(sysHelp["tune_user_lockout_config"][0]),
|
||||
},
|
||||
"identity_token_key": {
|
||||
Type: framework.TypeString,
|
||||
Description: strings.TrimSpace(sysHelp["identity_token_key"][0]),
|
||||
},
|
||||
},
|
||||
|
||||
Operations: map[logical.Operation]framework.OperationHandler{
|
||||
@@ -4671,6 +4684,10 @@ func (b *SystemBackend) mountPaths() []*framework.Path {
|
||||
Type: framework.TypeBool,
|
||||
Required: false,
|
||||
},
|
||||
"identity_token_key": {
|
||||
Type: framework.TypeString,
|
||||
Required: false,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
|
||||
@@ -433,6 +433,244 @@ func TestSystemBackend_mount_force_no_cache(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestSystemBackend_mount_secret_identity_token_key ensures that the identity
|
||||
// token key can be specified at secret mount enable time.
|
||||
func TestSystemBackend_mount_secret_identity_token_key(t *testing.T) {
|
||||
ctx := namespace.RootContext(nil)
|
||||
core, b, _ := testCoreSystemBackend(t)
|
||||
|
||||
// Create a test key
|
||||
testKey := "test_key"
|
||||
resp, err := core.identityStore.HandleRequest(ctx, &logical.Request{
|
||||
Storage: core.identityStore.view,
|
||||
Path: fmt.Sprintf("oidc/key/%s", testKey),
|
||||
Operation: logical.CreateOperation,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, resp)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
mountPath string
|
||||
keyName string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "enable secret mount with default key",
|
||||
mountPath: "mounts/dev/",
|
||||
keyName: defaultKeyName,
|
||||
},
|
||||
{
|
||||
name: "enable secret mount with empty key",
|
||||
mountPath: "mounts/test/",
|
||||
keyName: "",
|
||||
},
|
||||
{
|
||||
name: "enable secret mount with existing key",
|
||||
mountPath: "mounts/int/",
|
||||
keyName: testKey,
|
||||
},
|
||||
{
|
||||
name: "enable secret mount with key that does not exist",
|
||||
mountPath: "mounts/prod/",
|
||||
keyName: "does_not_exist_key",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Enable the secret mount
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, tt.mountPath)
|
||||
req.Data["type"] = "kv"
|
||||
req.Data["config"] = map[string]interface{}{
|
||||
"identity_token_key": tt.keyName,
|
||||
}
|
||||
resp, err := b.HandleRequest(ctx, req)
|
||||
if tt.wantErr {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, fmt.Errorf("key %q does not exist", tt.keyName), resp.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, resp)
|
||||
|
||||
// Expect identity token key set on the mount entry
|
||||
mountEntry := core.router.MatchingMountEntry(ctx, strings.TrimPrefix(tt.mountPath, "mounts/"))
|
||||
require.NotNil(t, mountEntry)
|
||||
require.Equal(t, tt.keyName, mountEntry.Config.IdentityTokenKey)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSystemBackend_mount_auth_identity_token_key ensures that the identity
|
||||
// token key can be specified at auth mount enable time.
|
||||
func TestSystemBackend_mount_auth_identity_token_key(t *testing.T) {
|
||||
ctx := namespace.RootContext(nil)
|
||||
core, b, _ := testCoreSystemBackend(t)
|
||||
core.credentialBackends["userpass"] = credUserpass.Factory
|
||||
|
||||
// Create a test key
|
||||
testKey := "test_key"
|
||||
resp, err := core.identityStore.HandleRequest(ctx, &logical.Request{
|
||||
Storage: core.identityStore.view,
|
||||
Path: fmt.Sprintf("oidc/key/%s", testKey),
|
||||
Operation: logical.CreateOperation,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, resp)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
mountPath string
|
||||
keyName string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "enable auth mount with default key",
|
||||
mountPath: "auth/dev/",
|
||||
keyName: defaultKeyName,
|
||||
},
|
||||
{
|
||||
name: "enable auth mount with empty key",
|
||||
mountPath: "auth/test/",
|
||||
keyName: "",
|
||||
},
|
||||
{
|
||||
name: "enable auth mount with existing key",
|
||||
mountPath: "auth/int/",
|
||||
keyName: testKey,
|
||||
},
|
||||
{
|
||||
name: "enable auth mount with key that does not exist",
|
||||
mountPath: "auth/prod/",
|
||||
keyName: "does_not_exist_key",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Enable the auth mount
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, tt.mountPath)
|
||||
req.Data["type"] = "userpass"
|
||||
req.Data["config"] = map[string]interface{}{
|
||||
"identity_token_key": tt.keyName,
|
||||
}
|
||||
resp, err := b.HandleRequest(ctx, req)
|
||||
if tt.wantErr {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, fmt.Errorf("key %q does not exist", tt.keyName), resp.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, resp)
|
||||
|
||||
// Expect identity token key set on the mount entry
|
||||
mountEntry := core.router.MatchingMountEntry(ctx, tt.mountPath)
|
||||
require.NotNil(t, mountEntry)
|
||||
require.Equal(t, tt.keyName, mountEntry.Config.IdentityTokenKey)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSystemBackend_tune_identity_token_key ensures that the identity
|
||||
// token key can be tuned for existing auth and secret mounts.
|
||||
func TestSystemBackend_tune_identity_token_key(t *testing.T) {
|
||||
ctx := namespace.RootContext(nil)
|
||||
core, b, _ := testCoreSystemBackend(t)
|
||||
core.credentialBackends["userpass"] = credUserpass.Factory
|
||||
|
||||
// Enable an auth mount with an empty identity token key
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, "auth/dev/")
|
||||
req.Data["type"] = "userpass"
|
||||
resp, err := b.HandleRequest(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, resp)
|
||||
|
||||
// Enable a secret mount with the default key
|
||||
req = logical.TestRequest(t, logical.UpdateOperation, "mounts/test/")
|
||||
req.Data["type"] = "kv"
|
||||
req.Data["config"] = map[string]interface{}{
|
||||
"identity_token_key": defaultKeyName,
|
||||
}
|
||||
resp, err = b.HandleRequest(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, resp)
|
||||
|
||||
// Create a test key
|
||||
testKey := "test_key"
|
||||
resp, err = core.identityStore.HandleRequest(ctx, &logical.Request{
|
||||
Storage: core.identityStore.view,
|
||||
Path: fmt.Sprintf("oidc/key/%s", testKey),
|
||||
Operation: logical.CreateOperation,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, resp)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
keyName string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "tune mounts to default key",
|
||||
keyName: defaultKeyName,
|
||||
},
|
||||
{
|
||||
name: "tune mounts to empty key",
|
||||
keyName: "",
|
||||
},
|
||||
{
|
||||
name: "tune mounts with existing key",
|
||||
keyName: testKey,
|
||||
},
|
||||
{
|
||||
name: "tune mounts with key that does not exist",
|
||||
keyName: "does_not_exist_key",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Tune the auth mount
|
||||
req = logical.TestRequest(t, logical.UpdateOperation, "auth/dev/tune")
|
||||
req.Data["identity_token_key"] = tt.keyName
|
||||
resp, err := b.HandleRequest(ctx, req)
|
||||
if tt.wantErr {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, fmt.Errorf("key %q does not exist", tt.keyName), resp.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, resp)
|
||||
|
||||
// Expect identity token key set on the auth mount entry
|
||||
mountEntry := core.router.MatchingMountEntry(ctx, "auth/dev/")
|
||||
require.NotNil(t, mountEntry)
|
||||
require.Equal(t, tt.keyName, mountEntry.Config.IdentityTokenKey)
|
||||
|
||||
// Tune the secret mount
|
||||
req = logical.TestRequest(t, logical.UpdateOperation, "mounts/test/tune")
|
||||
req.Data["identity_token_key"] = tt.keyName
|
||||
resp, err = b.HandleRequest(ctx, req)
|
||||
if tt.wantErr {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, fmt.Errorf("key %q does not exist", tt.keyName), resp.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, resp)
|
||||
|
||||
// Expect identity token key set on the secret mount entry
|
||||
mountEntry = core.router.MatchingMountEntry(ctx, "test/")
|
||||
require.NotNil(t, mountEntry)
|
||||
require.Equal(t, tt.keyName, mountEntry.Config.IdentityTokenKey)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystemBackend_mount_invalid(t *testing.T) {
|
||||
b := testSystemBackend(t)
|
||||
|
||||
|
||||
@@ -366,6 +366,7 @@ type MountConfig struct {
|
||||
AllowedManagedKeys []string `json:"allowed_managed_keys,omitempty" mapstructure:"allowed_managed_keys"`
|
||||
UserLockoutConfig *UserLockoutConfig `json:"user_lockout_config,omitempty" mapstructure:"user_lockout_config"`
|
||||
DelegatedAuthAccessors []string `json:"delegated_auth_accessors,omitempty" mapstructure:"delegated_auth_accessors"`
|
||||
IdentityTokenKey string `json:"identity_token_key,omitempty" mapstructure:"identity_token_key"`
|
||||
|
||||
// PluginName is the name of the plugin registered in the catalog.
|
||||
//
|
||||
@@ -402,6 +403,7 @@ type APIMountConfig struct {
|
||||
UserLockoutConfig *UserLockoutConfig `json:"user_lockout_config,omitempty" mapstructure:"user_lockout_config"`
|
||||
PluginVersion string `json:"plugin_version,omitempty" mapstructure:"plugin_version"`
|
||||
DelegatedAuthAccessors []string `json:"delegated_auth_accessors,omitempty" mapstructure:"delegated_auth_accessors"`
|
||||
IdentityTokenKey string `json:"identity_token_key,omitempty" mapstructure:"identity_token_key"`
|
||||
|
||||
// PluginName is the name of the plugin registered in the catalog.
|
||||
//
|
||||
@@ -509,6 +511,12 @@ func (e *MountEntry) SyncCache() {
|
||||
} else {
|
||||
e.synthesizedConfigCache.Store("delegated_auth_accessors", e.Config.DelegatedAuthAccessors)
|
||||
}
|
||||
|
||||
if len(e.Config.IdentityTokenKey) == 0 {
|
||||
e.synthesizedConfigCache.Delete("identity_token_key")
|
||||
} else {
|
||||
e.synthesizedConfigCache.Store("identity_token_key", e.Config.IdentityTokenKey)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *MountEntry) Deserialize() map[string]interface{} {
|
||||
|
||||
Reference in New Issue
Block a user