Merge remote-tracking branch 'remotes/from/ce/main'

This commit is contained in:
hc-github-team-secure-vault-core 2026-03-24 12:10:53 +00:00
commit 407557ce87
9 changed files with 132 additions and 34 deletions

View File

@ -57,6 +57,14 @@ var sudoPaths = map[string]*regexp.Regexp{
"/sys/storage/raft/snapshot-auto/config": regexp.MustCompile(`^/sys/storage/raft/snapshot-auto/config/?$`),
"/sys/storage/raft/snapshot-auto/config/{name}": regexp.MustCompile(`^/sys/storage/raft/snapshot-auto/config/[^/]+$`),
"/sys/reporting/scan": regexp.MustCompile(`^/sys/reporting/scan$`),
// activation-flags paths requiring sudo
"/sys/activation-flags/oauth-resource-server/activate": regexp.MustCompile(`^/sys/activation-flags/oauth-resource-server/activate$`),
"/sys/activation-flags/oauth-resource-server/deactivate": regexp.MustCompile(`^/sys/activation-flags/oauth-resource-server/deactivate$`),
// OAuth resource server profile paths requiring sudo
"/sys/config/oauth-resource-server/{name}": regexp.MustCompile(`^/sys/config/oauth-resource-server/[^/]+$`),
"/sys/config/oauth-resource-server": regexp.MustCompile(`^/sys/config/oauth-resource-server$`),
}
func SudoPaths() map[string]*regexp.Regexp {

2
go.mod
View File

@ -41,6 +41,8 @@ require (
github.com/Azure/azure-storage-blob-go v0.15.0
github.com/Azure/go-autorest/autorest v0.11.29
github.com/Azure/go-autorest/autorest/adal v0.9.24
github.com/MicahParks/jwkset v0.11.0
github.com/MicahParks/keyfunc/v3 v3.7.0
github.com/ProtonMail/go-crypto v1.3.0
github.com/ProtonMail/gopenpgp/v3 v3.2.1
github.com/SAP/go-hdb v1.10.1

4
go.sum
View File

@ -147,6 +147,10 @@ github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/MicahParks/jwkset v0.11.0 h1:yc0zG+jCvZpWgFDFmvs8/8jqqVBG9oyIbmBtmjOhoyQ=
github.com/MicahParks/jwkset v0.11.0/go.mod h1:U2oRhRaLgDCLjtpGL2GseNKGmZtLs/3O7p+OZaL5vo0=
github.com/MicahParks/keyfunc/v3 v3.7.0 h1:pdafUNyq+p3ZlvjJX1HWFP7MA3+cLpDtg69U3kITJGM=
github.com/MicahParks/keyfunc/v3 v3.7.0/go.mod h1:z66bkCviwqfg2YUp+Jcc/xRE9IXLcMq6DrgV/+Htru0=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=

View File

@ -1094,7 +1094,7 @@ func (m *ExpirationManager) revokeCommon(ctx context.Context, leaseID string, fo
// Delete the secondary index, but only if it's a leased secret (not auth)
if le.Secret != nil {
var indexToken string
// Maintain secondary index by token, except for orphan batch tokens
// Maintain secondary index by token, except for orphan batch tokens and ent tokens
switch le.ClientTokenType {
case logical.TokenTypeBatch:
te, err := m.tokenStore.lookupBatchTokenInternal(ctx, le.ClientToken)
@ -1108,6 +1108,9 @@ func (m *ExpirationManager) revokeCommon(ctx context.Context, leaseID string, fo
// parent
indexToken = te.Parent
}
case logical.TokenTypeEnt:
// ent tokens don't maintain parent relationships; just use the token itself
indexToken = le.ClientToken
default:
indexToken = le.ClientToken
}
@ -1656,6 +1659,15 @@ func (m *ExpirationManager) Register(ctx context.Context, req *logical.Request,
}
}
// If the token is an ent token, derive TTL from the ent token
if te.Type == logical.TokenTypeEnt {
entTokenExpireTime := deriveExpireTimeFromEntToken(te)
if !entTokenExpireTime.IsZero() && le.ExpireTime.After(entTokenExpireTime) {
// Use the ent token's expiration time for the lease
le.ExpireTime = entTokenExpireTime
}
}
// Acquire the lock here so persistEntry and updatePending are atomic,
// although it is *very unlikely* that anybody could grab the lease ID
// before this function returns. (They could find it in an index, or
@ -1714,6 +1726,14 @@ func (m *ExpirationManager) RegisterAuth(ctx context.Context, te *logical.TokenE
authExpirationTime := auth.ExpirationTime()
// For ent tokens, derive expiration from ent token
if te.Type == logical.TokenTypeEnt {
entTokenExpireTime := deriveExpireTimeFromEntToken(te)
if !entTokenExpireTime.IsZero() {
authExpirationTime = entTokenExpireTime
}
}
if te.TTL == 0 && authExpirationTime.IsZero() && (len(te.Policies) != 1 || te.Policies[0] != "root") {
return errors.New("refusing to register a lease for a non-root token with no TTL")
}
@ -1815,6 +1835,19 @@ func (m *ExpirationManager) FetchLeaseTimesByToken(ctx context.Context, te *logi
}, nil
}
if te.Type == logical.TokenTypeEnt {
entTokenExpireTime := deriveExpireTimeFromEntToken(te)
if !entTokenExpireTime.IsZero() {
issueTime := time.Unix(te.CreationTime, 0)
return &leaseEntry{
IssueTime: issueTime,
ExpireTime: entTokenExpireTime,
ClientTokenType: logical.TokenTypeEnt,
}, nil
}
return nil, errors.New("enterprise token has no valid expiration time")
}
tokenNS, err := NamespaceByID(ctx, te.NamespaceID, m.core)
if err != nil {
return nil, err
@ -2109,6 +2142,11 @@ func (m *ExpirationManager) revokeEntry(ctx context.Context, le *leaseEntry) err
return errors.New("batch tokens cannot be revoked")
}
// ent tokens are managed by external IdPs and should not be revoked through Vault backends
if le.ClientTokenType == logical.TokenTypeEnt {
return errors.New("enterprise tokens are managed by external IdPs and cannot be revoked by Vault")
}
if err := m.tokenStore.revokeTree(ctx, le); err != nil {
return fmt.Errorf("failed to revoke token: %w", err)
}
@ -2159,6 +2197,10 @@ func (m *ExpirationManager) renewAuthEntry(ctx context.Context, req *logical.Req
return logical.ErrorResponse("batch tokens cannot be renewed"), nil
}
if le.ClientTokenType == logical.TokenTypeEnt {
return logical.ErrorResponse("enterprise tokens cannot be renewed"), nil
}
auth := *le.Auth
auth.IssueTime = le.IssueTime
auth.Increment = increment
@ -2297,28 +2339,31 @@ func (m *ExpirationManager) deleteEntry(ctx context.Context, le *leaseEntry) err
// createIndexByToken creates a secondary index from the token to a lease entry
func (m *ExpirationManager) createIndexByToken(ctx context.Context, le *leaseEntry, token string) error {
tokenNS := namespace.RootNamespace
saltCtx := namespace.ContextWithNamespace(ctx, namespace.RootNamespace)
_, nsID := namespace.SplitIDFromString(token)
if nsID != "" && !IsEnterpriseToken(token) {
var err error
tokenNS, err = NamespaceByID(ctx, nsID, m.core)
if err != nil {
return err
}
if tokenNS != nil {
saltCtx = namespace.ContextWithNamespace(ctx, tokenNS)
}
}
var tokenNS *namespace.Namespace
var saltCtx context.Context
var err error
// If it's an enterprise token, we cannot get the ID from the token,
// so let's get it from the lease.
if IsEnterpriseToken(token) {
ns, err := m.getNamespaceFromLeaseID(ctx, le.LeaseID)
// fetch the namespace from the lease rather than the req context to allow for cross namespace access
tokenNS, err = m.getNamespaceFromLeaseID(ctx, le.LeaseID)
if err != nil {
return err
}
tokenNS = ns
saltCtx = namespace.ContextWithNamespace(ctx, tokenNS)
} else {
tokenNS = namespace.RootNamespace
saltCtx = namespace.ContextWithNamespace(ctx, namespace.RootNamespace)
_, nsID := namespace.SplitIDFromString(token)
if nsID != "" {
var err error
tokenNS, err = NamespaceByID(ctx, nsID, m.core)
if err != nil {
return err
}
if tokenNS != nil {
saltCtx = namespace.ContextWithNamespace(ctx, tokenNS)
}
}
}
saltedID, err := m.tokenStore.SaltID(saltCtx, token)
@ -2379,26 +2424,39 @@ func (m *ExpirationManager) indexByToken(ctx context.Context, le *leaseEntry) (*
// removeIndexByToken removes the secondary index from the token to a lease entry
func (m *ExpirationManager) removeIndexByToken(ctx context.Context, le *leaseEntry, token string) error {
tokenNS := namespace.RootNamespace
saltCtx := namespace.ContextWithNamespace(ctx, namespace.RootNamespace)
_, nsID := namespace.SplitIDFromString(token)
if nsID != "" {
var err error
tokenNS, err = NamespaceByID(ctx, nsID, m.core)
var tokenNS *namespace.Namespace
var saltCtx context.Context
var err error
if IsEnterpriseToken(token) {
tokenNS, err = namespace.FromContext(ctx)
if err != nil {
return err
}
if tokenNS != nil {
saltCtx = namespace.ContextWithNamespace(ctx, tokenNS)
}
// Downgrade logic for old-style (V0) namespace leases that had its
// secondary index live in the root namespace. This reverts to the old
// behavior of looking for the secondary index on these leases in the
// root namespace to be cleaned up properly. We set it here because the
// old behavior used the namespace's token store salt for its saltCtx.
if le.Version < 1 {
tokenNS = namespace.RootNamespace
saltCtx = namespace.ContextWithNamespace(ctx, tokenNS)
} else {
tokenNS = namespace.RootNamespace
saltCtx = namespace.ContextWithNamespace(ctx, namespace.RootNamespace)
_, nsID := namespace.SplitIDFromString(token)
if nsID != "" {
var err error
tokenNS, err = NamespaceByID(ctx, nsID, m.core)
if err != nil {
return err
}
if tokenNS != nil {
saltCtx = namespace.ContextWithNamespace(ctx, tokenNS)
}
// Downgrade logic for old-style (V0) namespace leases that had its
// secondary index live in the root namespace. This reverts to the old
// behavior of looking for the secondary index on these leases in the
// root namespace to be cleaned up properly. We set it here because the
// old behavior used the namespace's token store salt for its saltCtx.
if le.Version < 1 {
tokenNS = namespace.RootNamespace
}
}
}
@ -2984,6 +3042,9 @@ func (le *leaseEntry) renewable() (bool, error) {
case le.ClientTokenType == logical.TokenTypeBatch:
return false, nil
case le.ClientTokenType == logical.TokenTypeEnt:
return false, fmt.Errorf("enterprise tokens cannot be renewed")
// Determine if the lease is expired
case le.ExpireTime.Before(time.Now()):
return false, fmt.Errorf("lease expired")

View File

@ -7,6 +7,7 @@ package vault
import (
"fmt"
"time"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/logical"
@ -35,3 +36,7 @@ func (m *ExpirationManager) collectLeases() (map[*namespace.Namespace][]string,
func (m *ExpirationManager) removeIrrevocableLeasesEnabled(c *Core) bool {
return false
}
func deriveExpireTimeFromEntToken(_ *logical.TokenEntry) time.Time {
return time.Time{}
}

View File

@ -199,6 +199,9 @@ func NewSystemBackend(core *Core, logger log.Logger, config *logical.BackendConf
// to declare them here so that the generated OpenAPI spec gets their sudo status correct.
"seal",
"step-down",
"activation-flags/oauth-resource-server/activate",
"activation-flags/oauth-resource-server/deactivate",
"config/oauth-resource-server/*",
},
Unauthenticated: unauthenticatedPaths,

View File

@ -134,6 +134,13 @@ var (
"config/group-policy-application$": {operations: []logical.Operation{logical.ReadOperation, logical.UpdateOperation}},
})...)
paths = append(paths, buildEnterpriseOnlyPaths(map[string]enterprisePathStub{
"activation-flags/oauth-resource-server/activate": {operations: []logical.Operation{logical.UpdateOperation}},
"activation-flags/oauth-resource-server/deactivate": {operations: []logical.Operation{logical.UpdateOperation}},
"config/oauth-resource-server/": {operations: []logical.Operation{logical.ListOperation}},
"config/oauth-resource-server/" + framework.GenericNameRegex("name"): {parameters: []string{"name"}, operations: []logical.Operation{logical.DeleteOperation, logical.ReadOperation, logical.UpdateOperation}},
})...)
// reporting paths
paths = append(paths, buildEnterpriseOnlyPaths(map[string]enterprisePathStub{
"reporting/scan$": {operations: []logical.Operation{logical.UpdateOperation}},

View File

@ -2707,7 +2707,11 @@ func (c *Core) RegisterAuth(ctx context.Context, tokenTTL time.Duration, path st
return err
}
}
case logical.TokenTypeEnt:
// Ensure it's not marked renewable since enterprise tokens are not renewable
auth.Renewable = false
}
return nil
}

View File

@ -21,6 +21,10 @@ func (c *Core) createAndStoreEnterpriseTokenEntry(ctx context.Context, req *logi
return nil
}
func isActivationFlagEnabledForEnterpriseToken(c *Core) bool {
return false
}
func getEnterpriseTokenMetadata(_ map[string]interface{}) string {
return ""
}