mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 09:42:25 +00:00
Fix misattribution of activity log entries to incorrect auth methods (#18809)
* Fix misattribution of activity log entries to incorrect auth methods In a production Vault Enterprise instance, I noticed incorrect information in the sys/internal/counters/activity endpoints. Eventually, I was able to spot a pattern of entities being misattributed to auth methods of the same name in child namespaces, which led me to this bug in the code. When attempting to map from a token's path to an auth method, we need to do so with respect to the namespace of the token, which may be different from the namespace of the request, as tokens from parent namespaces can make requests that reach into child namespaces. * Changelog * Use a real namespace ID in tests where it now matters * gofumpt --------- Co-authored-by: Violet Hynes <violet.hynes@hashicorp.com>
This commit is contained in:
3
changelog/18809.txt
Normal file
3
changelog/18809.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:bug
|
||||
activity (enterprise): Fix misattribution of entities to no or child namespace auth methods
|
||||
```
|
||||
@@ -96,11 +96,6 @@ type segmentInfo struct {
|
||||
tokenCount *activity.TokenCount
|
||||
}
|
||||
|
||||
type clients struct {
|
||||
distinctEntities uint64
|
||||
distinctNonEntities uint64
|
||||
}
|
||||
|
||||
// ActivityLog tracks unique entity counts and non-entity token counts.
|
||||
// It handles assembling log fragments (and sending them to the active
|
||||
// node), writing log segments, and precomputing queries.
|
||||
@@ -1915,39 +1910,50 @@ func (a *ActivityLog) loadConfigOrDefault(ctx context.Context) (activityConfig,
|
||||
|
||||
// HandleTokenUsage adds the TokenEntry to the current fragment of the activity log
|
||||
// This currently occurs on token usage only.
|
||||
func (a *ActivityLog) HandleTokenUsage(ctx context.Context, entry *logical.TokenEntry, clientID string, isTWE bool) {
|
||||
func (a *ActivityLog) HandleTokenUsage(ctx context.Context, entry *logical.TokenEntry, clientID string, isTWE bool) error {
|
||||
// First, check if a is enabled, so as to avoid the cost of creating an ID for
|
||||
// tokens without entities in the case where it not.
|
||||
a.fragmentLock.RLock()
|
||||
if !a.enabled {
|
||||
a.fragmentLock.RUnlock()
|
||||
return
|
||||
return nil
|
||||
}
|
||||
a.fragmentLock.RUnlock()
|
||||
|
||||
// Do not count wrapping tokens in client count
|
||||
if IsWrappingToken(entry) {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do not count root tokens in client count.
|
||||
if entry.IsRoot() {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tokens created for the purpose of Link should bypass counting for billing purposes
|
||||
if entry.InternalMeta != nil && entry.InternalMeta[IgnoreForBilling] == "true" {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// Look up the mount accessor of the auth method that issued the token, taking care to resolve the token path
|
||||
// against the token namespace, which may not be the same as the request namespace!
|
||||
tokenNS, err := NamespaceByID(ctx, entry.NamespaceID, a.core)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tokenNS == nil {
|
||||
return namespace.ErrNoNamespace
|
||||
}
|
||||
tokenCtx := namespace.ContextWithNamespace(ctx, tokenNS)
|
||||
mountAccessor := ""
|
||||
mountEntry := a.core.router.MatchingMountEntry(ctx, entry.Path)
|
||||
mountEntry := a.core.router.MatchingMountEntry(tokenCtx, entry.Path)
|
||||
if mountEntry != nil {
|
||||
mountAccessor = mountEntry.Accessor
|
||||
}
|
||||
|
||||
// Parse an entry's client ID and add it to the activity log
|
||||
a.AddClientToFragment(clientID, entry.NamespaceID, entry.CreationTime, isTWE, mountAccessor)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ActivityLog) namespaceToLabel(ctx context.Context, nsID string) string {
|
||||
|
||||
@@ -136,7 +136,10 @@ func TestActivityLog_Creation_WrappingTokens(t *testing.T) {
|
||||
}
|
||||
|
||||
id, isTWE := te.CreateClientID()
|
||||
a.HandleTokenUsage(context.Background(), te, id, isTWE)
|
||||
err := a.HandleTokenUsage(context.Background(), te, id, isTWE)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
a.fragmentLock.Lock()
|
||||
if a.fragment != nil {
|
||||
@@ -153,7 +156,10 @@ func TestActivityLog_Creation_WrappingTokens(t *testing.T) {
|
||||
}
|
||||
|
||||
id, isTWE = teNew.CreateClientID()
|
||||
a.HandleTokenUsage(context.Background(), teNew, id, isTWE)
|
||||
err = a.HandleTokenUsage(context.Background(), teNew, id, isTWE)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
a.fragmentLock.Lock()
|
||||
if a.fragment != nil {
|
||||
@@ -380,18 +386,24 @@ func TestActivityLog_SaveTokensToStorageDoesNotUpdateTokenCount(t *testing.T) {
|
||||
tokenPath := fmt.Sprintf("%sdirecttokens/%d/0", ActivityLogPrefix, a.GetStartTimestamp())
|
||||
clientPath := fmt.Sprintf("sys/counters/activity/log/entity/%d/0", a.GetStartTimestamp())
|
||||
// Create some entries without entityIDs
|
||||
tokenEntryOne := logical.TokenEntry{NamespaceID: "ns1_id", Policies: []string{"hi"}}
|
||||
entityEntry := logical.TokenEntry{EntityID: "foo", NamespaceID: "ns1_id", Policies: []string{"hi"}}
|
||||
tokenEntryOne := logical.TokenEntry{NamespaceID: namespace.RootNamespaceID, Policies: []string{"hi"}}
|
||||
entityEntry := logical.TokenEntry{EntityID: "foo", NamespaceID: namespace.RootNamespaceID, Policies: []string{"hi"}}
|
||||
|
||||
idNonEntity, isTWE := tokenEntryOne.CreateClientID()
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
a.HandleTokenUsage(ctx, &tokenEntryOne, idNonEntity, isTWE)
|
||||
err := a.HandleTokenUsage(ctx, &tokenEntryOne, idNonEntity, isTWE)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
idEntity, isTWE := entityEntry.CreateClientID()
|
||||
for i := 0; i < 2; i++ {
|
||||
a.HandleTokenUsage(ctx, &entityEntry, idEntity, isTWE)
|
||||
err := a.HandleTokenUsage(ctx, &entityEntry, idEntity, isTWE)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
err := a.saveCurrentSegmentToStorage(ctx, false)
|
||||
if err != nil {
|
||||
|
||||
@@ -271,12 +271,12 @@ func (c *Core) CheckToken(ctx context.Context, req *logical.Request, unauth bool
|
||||
var te *logical.TokenEntry
|
||||
var entity *identity.Entity
|
||||
var identityPolicies map[string][]string
|
||||
var err error
|
||||
|
||||
// Even if unauth, if a token is provided, there's little reason not to
|
||||
// gather as much info as possible for the audit log and to e.g. control
|
||||
// trace mode for EGPs.
|
||||
if !unauth || (unauth && req.ClientToken != "") {
|
||||
var err error
|
||||
acl, te, entity, identityPolicies, err = c.fetchACLTokenEntryAndEntity(ctx, req)
|
||||
// In the unauth case we don't want to fail the command, since it's
|
||||
// unauth, we just have no information to attach to the request, so
|
||||
@@ -438,9 +438,12 @@ func (c *Core) CheckToken(ctx context.Context, req *logical.Request, unauth bool
|
||||
c.activityLogLock.RLock()
|
||||
activityLog := c.activityLog
|
||||
c.activityLogLock.RUnlock()
|
||||
// If it is an authenticated ( i.e with vault token ) request, increment client count
|
||||
// If it is an authenticated ( i.e. with vault token ) request, increment client count
|
||||
if !unauth && activityLog != nil {
|
||||
activityLog.HandleTokenUsage(ctx, te, clientID, isTWE)
|
||||
err := c.activityLog.HandleTokenUsage(ctx, te, clientID, isTWE)
|
||||
if err != nil {
|
||||
return auth, te, err
|
||||
}
|
||||
}
|
||||
return auth, te, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user