From fbbb559ca63431e12b39e0df424b1ddf580cb29c Mon Sep 17 00:00:00 2001 From: Vault Automation Date: Fri, 12 Dec 2025 15:19:04 -0500 Subject: [PATCH 1/4] VAULT-41153 correct emission of token create observations (#11302) (#11312) * VAULT-41153 correct emission of token create observations * make fmt * mount info Co-authored-by: Violet Hynes --- vault/token_store.go | 61 +++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/vault/token_store.go b/vault/token_store.go index 8433676c5b..c70f585003 100644 --- a/vault/token_store.go +++ b/vault/token_store.go @@ -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 } From be193ec3ab07706de04b59c441b7385d99f2427f Mon Sep 17 00:00:00 2001 From: Vault Automation Date: Fri, 12 Dec 2025 15:24:41 -0500 Subject: [PATCH 2/4] VAULT-41147 add policy observations to Vault (#11205) (#11229) Co-authored-by: Violet Hynes --- vault/logical_system.go | 4 +- vault/observations/observations_consts.go | 7 ++ vault/policy_store.go | 96 ++++++++++++++++++++--- 3 files changed, 96 insertions(+), 11 deletions(-) diff --git a/vault/logical_system.go b/vault/logical_system.go index 06499e4005..b4696098b0 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -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 diff --git a/vault/observations/observations_consts.go b/vault/observations/observations_consts.go index b29fd09bf3..6a27d46716 100644 --- a/vault/observations/observations_consts.go +++ b/vault/observations/observations_consts.go @@ -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" diff --git a/vault/policy_store.go b/vault/policy_store.go index f805bea4c8..38ddf5ddf7 100644 --- a/vault/policy_store.go +++ b/vault/policy_store.go @@ -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 { From ff1d4da453cab4143dc129b2fa1b2e14ab30bdd5 Mon Sep 17 00:00:00 2001 From: Violet Hynes Date: Fri, 12 Dec 2025 15:25:20 -0500 Subject: [PATCH 3/4] Manual CE backport VAULT-41128 ensure alias name is not logged in observations (#11296) (#11300) * VAULT-41128 ensure alias name is not logged in observations (#11296) * VAULT-41128 ensure alias name is not logged in observations * feedback * whoops --- vault/identity_store_util.go | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/vault/identity_store_util.go b/vault/identity_store_util.go index 2dee156cc0..9e181d76c8 100644 --- a/vault/identity_store_util.go +++ b/vault/identity_store_util.go @@ -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") From 0a52566cccadb3bdd4a980b9c3d3224ccc10559d Mon Sep 17 00:00:00 2001 From: Vault Automation Date: Fri, 12 Dec 2025 16:00:07 -0500 Subject: [PATCH 4/4] Correct misleading godoc re DisplayAttrs.Value. (#11189) (#11227) --- sdk/framework/path.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sdk/framework/path.go b/sdk/framework/path.go index 6cd1f5c841..acb36b6809 100644 --- a/sdk/framework/path.go +++ b/sdk/framework/path.go @@ -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.