Skip to content

Commit

Permalink
Fix misattribution of activity log entries to incorrect auth methods (#…
Browse files Browse the repository at this point in the history
…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 <[email protected]>
  • Loading branch information
maxb and VioletHynes authored Aug 21, 2023
1 parent abd6324 commit 35a5fbf
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 20 deletions.
3 changes: 3 additions & 0 deletions changelog/18809.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
activity (enterprise): Fix misattribution of entities to no or child namespace auth methods
```
28 changes: 17 additions & 11 deletions vault/activity_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
24 changes: 18 additions & 6 deletions vault/activity_log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 6 additions & 3 deletions vault/request_handling.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit 35a5fbf

Please sign in to comment.