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

This commit is contained in:
hc-github-team-secure-vault-core 2025-12-12 21:02:34 +00:00
commit 7ecd47f104
6 changed files with 163 additions and 36 deletions

View File

@ -223,9 +223,7 @@ type DisplayAttributes struct {
// to UI inputs where only arrays are valid. For example params with Type: framework.TypeCommaStringSlice
Description string `json:"description,omitempty"`
// Value is a sample value to display for this field. This may be used
// to indicate a default value, but it is for display only and completely separate
// from any Default member handling.
// Value is used as the default value by the UI.
Value interface{} `json:"value,omitempty"`
// Sensitive indicates that the value should be masked by default in the UI.

View File

@ -1476,7 +1476,7 @@ func (i *IdentityStore) MemDBUpsertEntityInTxn(txn *memdb.Txn, entity *identity.
"entity_id": entity.ID,
"entity_namespace_id": entity.NamespaceID,
"metadata": entity.Metadata,
"aliases": entity.Aliases,
"aliases": aliasesToEntityObservationAliases(entity.Aliases),
"policies": entity.Policies,
"creation_time": entity.CreationTime.AsTime().Format(time.RFC3339),
"last_updated_time": entity.LastUpdateTime.AsTime().Format(time.RFC3339),
@ -1491,6 +1491,31 @@ func (i *IdentityStore) MemDBUpsertEntityInTxn(txn *memdb.Txn, entity *identity.
return nil
}
// EntityObservationAliases is a minimal version of aliases to contain only
// salient information for entity upsert observations.
type EntityObservationAliases struct {
Id string `json:"id"`
MountPath string `json:"mount_path"`
MountType string `json:"mount_type"`
MountAccessor string `json:"mount_accessor"`
}
// aliasesToEntityObservationAliases translated a list of *identity.Alias into a list of EntityObservationAliases
func aliasesToEntityObservationAliases(aliases []*identity.Alias) []*EntityObservationAliases {
entityObservationAliases := make([]*EntityObservationAliases, 0, len(aliases))
for _, alias := range aliases {
if alias != nil {
entityObservationAliases = append(entityObservationAliases, &EntityObservationAliases{
Id: alias.ID,
MountPath: alias.MountPath,
MountType: alias.MountType,
MountAccessor: alias.MountAccessor,
})
}
}
return entityObservationAliases
}
func (i *IdentityStore) MemDBEntityByIDInTxn(txn *memdb.Txn, entityID string, clone bool) (*identity.Entity, error) {
if entityID == "" {
return nil, fmt.Errorf("missing entity id")

View File

@ -3625,7 +3625,7 @@ func (b *SystemBackend) handlePoliciesSet(policyType PolicyType) framework.Opera
}
// Update the policy
if err := b.Core.policyStore.SetPolicy(ctx, policy); err != nil {
if err := b.Core.policyStore.SetPolicyWithRequest(ctx, policy, req); err != nil {
return handleError(err)
}
@ -3645,7 +3645,7 @@ func (b *SystemBackend) handlePoliciesDelete(policyType PolicyType) framework.Op
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
name := data.Get("name").(string)
if err := b.Core.policyStore.DeletePolicy(ctx, name, policyType); err != nil {
if err := b.Core.policyStore.DeletePolicyWithRequest(ctx, name, policyType, req); err != nil {
return handleError(err)
}
return nil, nil

View File

@ -22,6 +22,13 @@ const (
// Type (i.e. batch/service) is included in 'type'.
ObservationTypeTokenCreation = "token/create"
// ObservationTypePolicyUpsert is emitted when a policy is
// inserted or updated.
ObservationTypePolicyUpsert = "policy/upsert"
// ObservationTypePolicyDelete is emitted when a policy is
// deleted.
ObservationTypePolicyDelete = "policy/delete"
// ObservationTypePolicyACLEvaluation is emitted when an ACL policy is evaluated
ObservationTypePolicyACLEvaluation = "policy/acl/evaluation"

View File

@ -19,6 +19,7 @@ import (
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault/observations"
)
const (
@ -341,14 +342,14 @@ func (ps *PolicyStore) invalidate(ctx context.Context, name string, policyType P
// case another process has re-written the policy; instead next time Get is
// called the values will be loaded back in.
if out == nil {
ps.switchedDeletePolicy(ctx, name, policyType, false, true)
ps.switchedDeletePolicy(ctx, name, policyType, false, true, nil)
}
return
}
// SetPolicy is used to create or update the given policy
func (ps *PolicyStore) SetPolicy(ctx context.Context, p *Policy) error {
// SetPolicyWithRequest is used to create or update a given policy from a request
func (ps *PolicyStore) SetPolicyWithRequest(ctx context.Context, p *Policy, req *logical.Request) error {
defer metrics.MeasureSince([]string{"policy", "set_policy"}, time.Now())
if p == nil {
return fmt.Errorf("nil policy passed in for storage")
@ -362,10 +363,15 @@ func (ps *PolicyStore) SetPolicy(ctx context.Context, p *Policy) error {
return fmt.Errorf("cannot update %q policy", p.Name)
}
return ps.setPolicyInternal(ctx, p)
return ps.setPolicyInternal(ctx, p, req)
}
func (ps *PolicyStore) setPolicyInternal(ctx context.Context, p *Policy) error {
// SetPolicy is used to create or update the given policy
func (ps *PolicyStore) SetPolicy(ctx context.Context, p *Policy) error {
return ps.SetPolicyWithRequest(ctx, p, nil)
}
func (ps *PolicyStore) setPolicyInternal(ctx context.Context, p *Policy, req *logical.Request) error {
ps.modifyLock.Lock()
defer ps.modifyLock.Unlock()
@ -454,9 +460,56 @@ func (ps *PolicyStore) setPolicyInternal(ctx context.Context, p *Policy) error {
return fmt.Errorf("unknown policy type, cannot set")
}
var clientId string
var entityId string
var requestId string
if req != nil {
clientId = req.ClientID
entityId = req.EntityID
requestId = req.ID
}
// TODO Violet 1
err = ps.core.Observations().RecordObservationToLedger(ctx, observations.ObservationTypePolicyUpsert, p.namespace, map[string]interface{}{
"client_id": clientId,
"entity_id": entityId,
"request_id": requestId,
"type": p.Type.String(),
"name": p.Name,
"raw_policy": p.Raw,
"path_rules": pathRulesToObservationPathRules(p.Paths),
})
if err != nil {
ps.logger.Error("error recording observation for policy upsert", err)
}
return nil
}
// ObservationPathRules is a minimal version of PathRules to contain only
// salient information for observations.
type ObservationPathRules struct {
Path string `json:"path"`
Capabilities []string `json:"capabilities"`
Prefix bool `json:"prefix"`
HasSegmentWildcards bool `json:"has_segment_wildcards"`
}
// pathRulesToObservationPathRules translated a list of PathRules into a list of ObservationPathRules
func pathRulesToObservationPathRules(rules []*PathRules) []*ObservationPathRules {
var observationPathRules []*ObservationPathRules
for _, rule := range rules {
if rule != nil {
observationPathRules = append(observationPathRules, &ObservationPathRules{
Path: rule.Path,
Capabilities: rule.Capabilities,
Prefix: rule.IsPrefix,
HasSegmentWildcards: rule.HasSegmentWildcards,
})
}
}
return observationPathRules
}
// GetNonEGPPolicyType returns a policy's type.
// It will return an error if the policy doesn't exist in the store or isn't
// an ACL or a Sentinel Role Governing Policy (RGP).
@ -737,9 +790,14 @@ func (ps *PolicyStore) policiesByNamespaces(ctx context.Context, policyType Poli
return keys, err
}
// DeletePolicyWithRequest is used to delete the named policy with a given request
func (ps *PolicyStore) DeletePolicyWithRequest(ctx context.Context, name string, policyType PolicyType, req *logical.Request) error {
return ps.switchedDeletePolicy(ctx, name, policyType, true, false, req)
}
// DeletePolicy is used to delete the named policy
func (ps *PolicyStore) DeletePolicy(ctx context.Context, name string, policyType PolicyType) error {
return ps.switchedDeletePolicy(ctx, name, policyType, true, false)
return ps.switchedDeletePolicy(ctx, name, policyType, true, false, nil)
}
// deletePolicyForce is used to delete the named policy and force it even if
@ -747,10 +805,10 @@ func (ps *PolicyStore) DeletePolicy(ctx context.Context, name string, policyType
// where we internally need to actually remove a policy that the user normally
// isn't allowed to remove.
func (ps *PolicyStore) deletePolicyForce(ctx context.Context, name string, policyType PolicyType) error {
return ps.switchedDeletePolicy(ctx, name, policyType, true, true)
return ps.switchedDeletePolicy(ctx, name, policyType, true, true, nil)
}
func (ps *PolicyStore) switchedDeletePolicy(ctx context.Context, name string, policyType PolicyType, physicalDeletion, force bool) error {
func (ps *PolicyStore) switchedDeletePolicy(ctx context.Context, name string, policyType PolicyType, physicalDeletion, force bool, req *logical.Request) error {
defer metrics.MeasureSince([]string{"policy", "delete_policy"}, time.Now())
ns, err := namespace.FromContext(ctx)
@ -833,6 +891,26 @@ func (ps *PolicyStore) switchedDeletePolicy(ctx context.Context, name string, po
ps.invalidateEGPTreePath(index)
}
var clientId string
var entityId string
var requestId string
if req != nil {
clientId = req.ClientID
entityId = req.EntityID
requestId = req.ID
}
err = ps.core.Observations().RecordObservationToLedger(ctx, observations.ObservationTypePolicyDelete, ns, map[string]interface{}{
"client_id": clientId,
"entity_id": entityId,
"request_id": requestId,
"forced_delete": force,
"type": policyType.String(),
"name": name,
})
if err != nil {
ps.logger.Error("error recording observation for policy delete", err)
}
return nil
}
@ -936,7 +1014,7 @@ func (ps *PolicyStore) loadACLPolicyInternal(ctx context.Context, policyName, po
policy.Name = policyName
policy.Type = PolicyTypeACL
return ps.setPolicyInternal(ctx, policy)
return ps.setPolicyInternal(ctx, policy, nil)
}
func (ps *PolicyStore) sanitizeName(name string) string {

View File

@ -1071,6 +1071,41 @@ func (ts *TokenStore) create(ctx context.Context, entry *logical.TokenEntry) err
}
}
recordObservationFunc := func() {
clientId, nonEntityToken := entry.CreateClientID()
var mountAccessor string
var mountPath string
var mountType string
mountEntry := ts.core.router.MatchingMountEntry(ctx, entry.Path)
if mountEntry != nil {
mountAccessor = mountEntry.Accessor
mountPath = mountEntry.Path
mountType = mountEntry.Type
}
// Note: this should not be modified to include the token's ID (the token's actual value),
// due to sensitivity.
ts.core.Observations().RecordObservationToLedger(ctx, observations.ObservationTypeTokenCreation, tokenNS, map[string]interface{}{
"policies": entry.Policies,
"path": entry.Path,
"display_name": entry.DisplayName,
"num_uses": entry.NumUses,
"token_type": entry.Type.String(),
"ttl": entry.TTL.String(),
"role": entry.Role,
"token_client_id": clientId,
"token_entity_id": entry.EntityID,
"token_client_id_is_entity_id": !nonEntityToken,
"mount_accessor": mountAccessor,
"mount_path": mountPath,
"mount_type": mountType,
})
if err != nil {
ts.logger.Error("error recording observation for token creation", err)
}
}
switch entry.Type {
case logical.TokenTypeDefault, logical.TokenTypeService:
// In case it was default, force to service
@ -1148,6 +1183,9 @@ func (ts *TokenStore) create(ctx context.Context, entry *logical.TokenEntry) err
if !userSelectedID && !ts.core.DisableSSCTokens() {
entry.ExternalID = ts.GenerateSSCTokenID(entry.ID, logical.IndexStateFromContext(ctx), entry)
}
recordObservationFunc()
return nil
case logical.TokenTypeBatch:
@ -1217,6 +1255,8 @@ func (ts *TokenStore) create(ctx context.Context, entry *logical.TokenEntry) err
entry.ID = fmt.Sprintf("%s.%s", entry.ID, tokenNS.ID)
}
recordObservationFunc()
return nil
default:
@ -3254,27 +3294,6 @@ func (ts *TokenStore) handleCreateCommon(ctx context.Context, req *logical.Reque
}
}
clientId, nonEntityToken := te.CreateClientID()
// Note: this should not be modified to include the token's ID (the token's actual value),
// due to sensitivity.
ts.core.Observations().RecordObservationToLedger(ctx, observations.ObservationTypeTokenCreation, ns, map[string]interface{}{
"policies": te.Policies,
"path": te.Path,
"display_name": te.DisplayName,
"num_uses": te.NumUses,
"token_type": te.Type.String(),
"ttl": te.TTL.String(),
"role": te.Role,
"token_client_id": clientId,
"token_entity_id": te.EntityID,
"token_client_id_is_entity_id": !nonEntityToken,
"request_client_id": req.ClientID,
"request_entity_id": req.EntityID,
})
if err != nil {
ts.logger.Error("error recording observation for token creation", err)
}
return resp, nil
}