Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cubbyhole cleanup #6006

Merged
merged 13 commits into from
Jan 9, 2019
71 changes: 55 additions & 16 deletions vault/token_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,8 @@ var (
return errors.New("nil token entry")
}

tokenNS, err := NamespaceByID(ctx, te.NamespaceID, ts.core)
if err != nil {
return err
}
if tokenNS == nil {
return namespace.ErrNoNamespace
}

switch tokenNS.ID {
case namespace.RootNamespaceID:
switch {
case te.NamespaceID == namespace.RootNamespaceID && !strings.HasPrefix(te.ID, "s."):
saltedID, err := ts.SaltID(ctx, te.ID)
if err != nil {
return err
Expand Down Expand Up @@ -1722,7 +1714,7 @@ func (ts *TokenStore) handleTidy(ctx context.Context, req *logical.Request, data

ns, err := namespace.FromContext(ctx)
if err != nil {
return nil, errwrap.Wrapf("failed get namespace from context: {{err}}", err)
return nil, errwrap.Wrapf("failed to get namespace from context: {{err}}", err)
}

go func() {
Expand Down Expand Up @@ -1751,6 +1743,12 @@ func (ts *TokenStore) handleTidy(ctx context.Context, req *logical.Request, data
return errwrap.Wrapf("failed to fetch secondary index entries: {{err}}", err)
}

// List all the cubbyhole storage keys
cubbyholeKeys, err := ts.cubbyholeBackend.storageView.List(quitCtx, "")
if err != nil {
return errwrap.Wrapf("failed to fetch cubbyhole storage keys: {{err}}", err)
}

var countParentEntries, deletedCountParentEntries, countParentList, deletedCountParentList int64

// Scan through the secondary index entries; if there is an entry
Expand Down Expand Up @@ -1824,9 +1822,13 @@ func (ts *TokenStore) handleTidy(ctx context.Context, req *logical.Request, data
}

var countAccessorList,
countCubbyholeKeys,
deletedCountAccessorEmptyToken,
deletedCountAccessorInvalidToken,
deletedCountInvalidTokenInAccessor int64
deletedCountInvalidTokenInAccessor,
deletedCountInvalidCubbyholeKey int64

validCubbyholeKeys := make(map[string]bool)

// For each of the accessor, see if the token ID associated with it is
// a valid one. If not, delete the leases associated with that token
Expand Down Expand Up @@ -1871,10 +1873,12 @@ func (ts *TokenStore) handleTidy(ctx context.Context, req *logical.Request, data

lock.RUnlock()

// If token entry is not found assume that the token is not valid any
// more and conclude that accessor, leases, and secondary index entries
// for this token should not exist as well.
if te == nil {
switch {
case te == nil:
// If token entry is not found assume that the token is not valid any
// more and conclude that accessor, leases, and secondary index entries
// for this token should not exist as well.

ts.logger.Info("deleting token with nil entry referenced by accessor", "salted_accessor", saltedAccessor)

// RevokeByToken expects a '*logical.TokenEntry'. For the
Expand Down Expand Up @@ -1904,6 +1908,40 @@ func (ts *TokenStore) handleTidy(ctx context.Context, req *logical.Request, data
continue
}
deletedCountAccessorInvalidToken++
default:
// Cache the cubbyhole storage key when the token is valid
switch {
case te.NamespaceID == namespace.RootNamespaceID && !strings.HasPrefix(te.ID, "s."):
tyrannosaurus-becks marked this conversation as resolved.
Show resolved Hide resolved
saltedID, err := ts.SaltID(quitCtx, te.ID)
if err != nil {
tidyErrors = multierror.Append(tidyErrors, errwrap.Wrapf("failed to create salted token id: {{err}}", err))
continue
}
validCubbyholeKeys[salt.SaltID(ts.cubbyholeBackend.saltUUID, saltedID, salt.SHA1Hash)] = true
tyrannosaurus-becks marked this conversation as resolved.
Show resolved Hide resolved
default:
if te.CubbyholeID == "" {
tidyErrors = multierror.Append(tidyErrors, fmt.Errorf("missing cubbyhole ID for a valid token"))
continue
}
validCubbyholeKeys[te.CubbyholeID] = true
}
}
}

// Revoke invalid cubbyhole storage keys
for _, key := range cubbyholeKeys {
countCubbyholeKeys++
if countCubbyholeKeys%500 == 0 {
ts.logger.Info("checking if there are invalid cubbyholes", "progress", countCubbyholeKeys)
}

key := strings.TrimSuffix(key, "/")
if !validCubbyholeKeys[key] {
err = ts.cubbyholeBackend.revoke(quitCtx, key)
if err != nil {
tidyErrors = multierror.Append(tidyErrors, errwrap.Wrapf(fmt.Sprintf("failed to revoke cubbyhole key %q: {{err}}", key), err))
}
deletedCountInvalidCubbyholeKey++
}
}

Expand All @@ -1915,6 +1953,7 @@ func (ts *TokenStore) handleTidy(ctx context.Context, req *logical.Request, data
ts.logger.Info("number of deleted accessors which had empty tokens", "count", deletedCountAccessorEmptyToken)
ts.logger.Info("number of revoked tokens which were invalid but present in accessors", "count", deletedCountInvalidTokenInAccessor)
ts.logger.Info("number of deleted accessors which had invalid tokens", "count", deletedCountAccessorInvalidToken)
ts.logger.Info("number of deleted cubbyhole keys that were invalid", "count", deletedCountInvalidCubbyholeKey)

return tidyErrors.ErrorOrNil()
}
Expand Down
77 changes: 77 additions & 0 deletions vault/token_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,83 @@ import (
"github.com/hashicorp/vault/logical"
)

func TestTokenStore_CubbyholeTidy(t *testing.T) {
c, _, root := TestCoreUnsealed(t)
ts := c.tokenStore

for i := 1; i <= 20; i++ {
// Create 20 tokens
tokenReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "create",
ClientToken: root,
}

resp := testMakeTokenViaRequest(t, ts, tokenReq)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can inject some legacy style tokens to make sure we are not accidentally removing salted cubbyhole IDs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll think if I can do this tomorrow morning.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the token IDs are supplied, we force SHA1 hashing. I've added such tokens into the sample space for both tests.

token := resp.Auth.ClientToken

// Create 4 junk cubbyhole entries
if i%5 == 0 {
invalidToken, err := uuid.GenerateUUID()
if err != nil {
t.Fatal(err)
}

resp, err := ts.cubbyholeBackend.HandleRequest(namespace.RootContext(nil), &logical.Request{
ClientToken: invalidToken,
Operation: logical.UpdateOperation,
Path: "cubbyhole/sample/data",
Data: map[string]interface{}{
"foo": "bar",
},
Storage: ts.cubbyholeBackend.storageView,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
}

// Write into cubbyholes of 10 tokens
if i%2 == 0 {
continue
}
resp, err := c.HandleRequest(namespace.RootContext(nil), &logical.Request{
ClientToken: token,
Operation: logical.UpdateOperation,
Path: "cubbyhole/sample/data",
Data: map[string]interface{}{
"foo": "bar",
},
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}
}

// Tidy cubbyhole storage
resp, err := ts.HandleRequest(namespace.RootContext(nil), &logical.Request{
Path: "tidy",
Operation: logical.UpdateOperation,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
}

// Wait for tidy operation to complete
time.Sleep(2 * time.Second)

// List all the cubbyhole storage keys
cubbyholeKeys, err := ts.cubbyholeBackend.storageView.List(namespace.RootContext(nil), "")
if err != nil {
t.Fatal(err)
}

// The junk entries must have been cleaned up
if len(cubbyholeKeys) != 10 {
t.Fatalf("bad: len(cubbyholeKeys); expected: 10, actual: %d", len(cubbyholeKeys))
}
}

func TestTokenStore_Salting(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ts := c.tokenStore
Expand Down