Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sys: adds identity_token_key to mounts/auth for enable/tune #24962

Merged
merged 10 commits into from
Jan 22, 2024
3 changes: 3 additions & 0 deletions api/sys_mounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
Expand Down Expand Up @@ -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"`
Expand Down
3 changes: 3 additions & 0 deletions changelog/24962.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
sys: adds configuration of the key used to sign plugin identity tokens during mount enable and tune
```
113 changes: 113 additions & 0 deletions http/sys_mount_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package http

import (
"context"
"encoding/json"
"fmt"
"reflect"
Expand All @@ -12,11 +13,13 @@ import (

"github.com/fatih/structs"
"github.com/go-test/deep"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/helper/testhelpers/corehelpers"
"github.com/hashicorp/vault/helper/versions"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault"
"github.com/stretchr/testify/require"
)

func TestSysMounts(t *testing.T) {
Expand Down Expand Up @@ -1829,6 +1832,116 @@ func TestSysTuneMount_passthroughRequestHeaders(t *testing.T) {
}
}

// TestSysTuneMount_identityTokenKey ensures that the identity token key for plugin
// mounts can also be tuned to keys that exist in the identity store.
func TestSysTuneMount_identityTokenKey(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
defer ln.Close()
TestServerAuth(t, addr, token)

expectedResponse := func(key string) map[string]any {
data := map[string]any{
"description": "key/value secret storage",
"default_lease_ttl": json.Number("2764800"),
"max_lease_ttl": json.Number("2764800"),
"options": map[string]interface{}{"version": "1"},
"force_no_cache": false,
}
resp := map[string]any{
"lease_id": "",
"renewable": false,
"lease_duration": json.Number("0"),
"wrap_info": nil,
"warnings": nil,
"auth": nil,
"mount_type": "system",
"data": data,
"description": "key/value secret storage",
"default_lease_ttl": json.Number("2764800"),
"max_lease_ttl": json.Number("2764800"),
"options": map[string]interface{}{"version": "1"},
"force_no_cache": false,
}
if key != "" {
resp["identity_token_key"] = key
data["identity_token_key"] = key
}

return resp
}

// Mount tune to a key that doesn't exist, expect bad request
resp := testHttpPost(t, token, addr+"/v1/sys/mounts/secret/tune", map[string]interface{}{
"identity_token_key": "test_key",
})
testResponseStatus(t, resp, 400)

// Create a new key
ctx := namespace.RootContext(context.Background())
res, err := core.IdentityStore().HandleRequest(ctx, &logical.Request{
Storage: core.RouterAccess().StorageByAPIPath(ctx, "identity/"),
Path: "oidc/key/test_key",
Operation: logical.CreateOperation,
Data: map[string]interface{}{
"allowed_client_ids": "*",
},
})
require.Nil(t, err)
require.False(t, res.IsError())

// Mount tune to the existing key
resp = testHttpPost(t, token, addr+"/v1/sys/mounts/secret/tune", map[string]interface{}{
"identity_token_key": "test_key",
})
testResponseStatus(t, resp, 204)

// Expect the key in the mount tune response
resp = testHttpGet(t, token, addr+"/v1/sys/mounts/secret/tune")
testResponseStatus(t, resp, 200)
actual := map[string]interface{}{}
expected := expectedResponse("test_key")
testResponseBody(t, resp, &actual)
expected["request_id"] = actual["request_id"]
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, actual)
}

// Mount tune to unset the key
resp = testHttpPost(t, token, addr+"/v1/sys/mounts/secret/tune", map[string]interface{}{
"identity_token_key": "",
})
testResponseStatus(t, resp, 204)

// Expect the key removed in the mount tune response
resp = testHttpGet(t, token, addr+"/v1/sys/mounts/secret/tune")
testResponseStatus(t, resp, 200)
actual = map[string]interface{}{}
expected = expectedResponse("")
testResponseBody(t, resp, &actual)
expected["request_id"] = actual["request_id"]
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, actual)
}

// Mount tune to the default key
resp = testHttpPost(t, token, addr+"/v1/sys/mounts/secret/tune", map[string]interface{}{
"identity_token_key": "default",
})
testResponseStatus(t, resp, 204)

// Expect the key in the mount tune response
resp = testHttpGet(t, token, addr+"/v1/sys/mounts/secret/tune")
testResponseStatus(t, resp, 200)
actual = map[string]interface{}{}
expected = expectedResponse("default")
testResponseBody(t, resp, &actual)
expected["request_id"] = actual["request_id"]
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad:\nExpected: %#v\nActual:%#v", expected, actual)
}
}

func TestSysTuneMount_allowedManagedKeys(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
Expand Down
2 changes: 1 addition & 1 deletion vault/identity_store_oidc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
86 changes: 86 additions & 0 deletions vault/logical_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down Expand Up @@ -1498,6 +1501,23 @@ func (b *SystemBackend) handleMount(ctx context.Context, req *logical.Request, d
config.DelegatedAuthAccessors = apiConfig.DelegatedAuthAccessors
}

if apiConfig.IdentityTokenKey != "" {
config.IdentityTokenKey = apiConfig.IdentityTokenKey

// Ensure the key exists in the identity store
identityStore := b.Core.IdentityStore()
identityStoreView := identityStore.view
identityStore.oidcLock.Lock()
defer identityStore.oidcLock.Unlock()
k, err := identityStore.getNamedKey(ctx, identityStoreView, config.IdentityTokenKey)
if err != nil {
return handleError(err)
}
if k == nil {
return handleError(fmt.Errorf("key %q does not exist", config.IdentityTokenKey))
}
}

// Create the mount entry
me := &MountEntry{
Table: mountTableType,
Expand Down Expand Up @@ -1954,6 +1974,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
Expand Down Expand Up @@ -2245,6 +2269,47 @@ func (b *SystemBackend) handleTuneWriteCommon(ctx context.Context, path string,
}
}

if rawVal, ok := data.GetOk("identity_token_key"); ok {
identityTokenKey := rawVal.(string)

// Ensure the key exists in the identity store
if identityTokenKey != "" {
identityStore := b.Core.IdentityStore()
identityStoreView := identityStore.view
identityStore.oidcLock.Lock()
defer identityStore.oidcLock.Unlock()
k, err := identityStore.getNamedKey(ctx, identityStoreView, identityTokenKey)
if err != nil {
return handleError(err)
}
if k == nil {
return handleError(fmt.Errorf("key %q does not exist", identityTokenKey))
}
}

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)

Expand Down Expand Up @@ -3000,6 +3065,23 @@ func (b *SystemBackend) handleEnableAuth(ctx context.Context, req *logical.Reque
config.AllowedManagedKeys = apiConfig.AllowedManagedKeys
}

if apiConfig.IdentityTokenKey != "" {
config.IdentityTokenKey = apiConfig.IdentityTokenKey

// Ensure the key exists in the identity store
identityStore := b.Core.IdentityStore()
identityStoreView := identityStore.view
identityStore.oidcLock.Lock()
defer identityStore.oidcLock.Unlock()
k, err := identityStore.getNamedKey(ctx, identityStoreView, config.IdentityTokenKey)
if err != nil {
return handleError(err)
}
if k == nil {
return handleError(fmt.Errorf("key %q does not exist", config.IdentityTokenKey))
}
}

// Create the mount entry
me := &MountEntry{
Table: credentialTableType,
Expand Down Expand Up @@ -6445,6 +6527,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.`,
`
Expand Down
17 changes: 17 additions & 0 deletions vault/logical_system_paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -3807,6 +3812,10 @@ func (b *SystemBackend) authPaths() []*framework.Path {
Type: framework.TypeString,
Required: false,
},
"identity_token_key": {
Type: framework.TypeString,
Required: false,
},
},
}},
},
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -4671,6 +4684,10 @@ func (b *SystemBackend) mountPaths() []*framework.Path {
Type: framework.TypeBool,
Required: false,
},
"identity_token_key": {
Type: framework.TypeString,
Required: false,
},
},
}},
},
Expand Down
Loading
Loading