Skip to content

Commit b771142

Browse files
author
Mahmood Ali
authored
Merge pull request #7959 from hashicorp/b-deleted-vault-accessors
vault: ensure that token revocation is idempotent
2 parents c514a55 + ff3cf8f commit b771142

File tree

2 files changed

+132
-2
lines changed

2 files changed

+132
-2
lines changed

nomad/vault.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"math/rand"
88
"strconv"
9+
"strings"
910
"sync"
1011
"sync/atomic"
1112
"time"
@@ -1135,7 +1136,9 @@ func (v *vaultClient) storeForRevocation(accessors []*structs.VaultAccessor) {
11351136

11361137
now := time.Now()
11371138
for _, a := range accessors {
1138-
v.revoking[a] = now.Add(time.Duration(a.CreationTTL) * time.Second)
1139+
if _, ok := v.revoking[a]; !ok {
1140+
v.revoking[a] = now.Add(time.Duration(a.CreationTTL) * time.Second)
1141+
}
11391142
}
11401143
v.revLock.Unlock()
11411144
}
@@ -1176,7 +1179,8 @@ func (v *vaultClient) parallelRevoke(ctx context.Context, accessors []*structs.V
11761179
return nil
11771180
}
11781181

1179-
if err := v.auth.RevokeAccessor(va.Accessor); err != nil {
1182+
err := v.auth.RevokeAccessor(va.Accessor)
1183+
if err != nil && !strings.Contains(err.Error(), "invalid accessor") {
11801184
return fmt.Errorf("failed to revoke token (alloc: %q, node: %q, task: %q): %v", va.AllocID, va.NodeID, va.Task, err)
11811185
}
11821186
case <-pCtx.Done():

nomad/vault_test.go

+126
Original file line numberDiff line numberDiff line change
@@ -1409,6 +1409,52 @@ func TestVaultClient_RevokeTokens_PreEstablishs(t *testing.T) {
14091409
}
14101410
}
14111411

1412+
// TestVaultClient_RevokeTokens_failures_TTL asserts that
1413+
// the registered TTL doesn't get extended on retries
1414+
func TestVaultClient_RevokeTokens_Failures_TTL(t *testing.T) {
1415+
t.Parallel()
1416+
vconfig := &config.VaultConfig{
1417+
Enabled: helper.BoolToPtr(true),
1418+
Token: uuid.Generate(),
1419+
Addr: "http://127.0.0.1:0",
1420+
}
1421+
logger := testlog.HCLogger(t)
1422+
client, err := NewVaultClient(vconfig, logger, nil)
1423+
if err != nil {
1424+
t.Fatalf("failed to build vault client: %v", err)
1425+
}
1426+
client.SetActive(true)
1427+
defer client.Stop()
1428+
1429+
// Create some VaultAccessors
1430+
vas := []*structs.VaultAccessor{
1431+
mock.VaultAccessor(),
1432+
mock.VaultAccessor(),
1433+
}
1434+
1435+
err = client.RevokeTokens(context.Background(), vas, true)
1436+
require.NoError(t, err)
1437+
1438+
// Was committed
1439+
require.Len(t, client.revoking, 2)
1440+
1441+
// set TTL
1442+
ttl := time.Now().Add(50 * time.Second)
1443+
client.revoking[vas[0]] = ttl
1444+
client.revoking[vas[1]] = ttl
1445+
1446+
// revoke again and ensure that TTL isn't extended
1447+
err = client.RevokeTokens(context.Background(), vas, true)
1448+
require.NoError(t, err)
1449+
1450+
require.Len(t, client.revoking, 2)
1451+
expected := map[*structs.VaultAccessor]time.Time{
1452+
vas[0]: ttl,
1453+
vas[1]: ttl,
1454+
}
1455+
require.Equal(t, expected, client.revoking)
1456+
}
1457+
14121458
func TestVaultClient_RevokeTokens_Root(t *testing.T) {
14131459
t.Parallel()
14141460
v := testutil.NewTestVault(t)
@@ -1541,6 +1587,86 @@ func TestVaultClient_RevokeTokens_Role(t *testing.T) {
15411587
}
15421588
}
15431589

1590+
// TestVaultClient_RevokeTokens_Idempotent asserts that token revocation
1591+
// is idempotent, and can cope with cases if token was deleted out of band.
1592+
func TestVaultClient_RevokeTokens_Idempotent(t *testing.T) {
1593+
t.Parallel()
1594+
v := testutil.NewTestVault(t)
1595+
defer v.Stop()
1596+
1597+
// Set the configs token in a new test role
1598+
v.Config.Token = defaultTestVaultWhitelistRoleAndToken(v, t, 5)
1599+
1600+
purged := map[string]struct{}{}
1601+
purge := func(accessors []*structs.VaultAccessor) error {
1602+
for _, accessor := range accessors {
1603+
purged[accessor.Accessor] = struct{}{}
1604+
}
1605+
return nil
1606+
}
1607+
1608+
logger := testlog.HCLogger(t)
1609+
client, err := NewVaultClient(v.Config, logger, purge)
1610+
if err != nil {
1611+
t.Fatalf("failed to build vault client: %v", err)
1612+
}
1613+
client.SetActive(true)
1614+
defer client.Stop()
1615+
1616+
waitForConnection(client, t)
1617+
1618+
// Create some vault tokens
1619+
auth := v.Client.Auth().Token()
1620+
req := vapi.TokenCreateRequest{
1621+
Policies: []string{"default"},
1622+
}
1623+
t1, err := auth.Create(&req)
1624+
require.NoError(t, err)
1625+
require.NotNil(t, t1)
1626+
require.NotNil(t, t1.Auth)
1627+
1628+
t2, err := auth.Create(&req)
1629+
require.NoError(t, err)
1630+
require.NotNil(t, t2)
1631+
require.NotNil(t, t2.Auth)
1632+
1633+
t3, err := auth.Create(&req)
1634+
require.NoError(t, err)
1635+
require.NotNil(t, t3)
1636+
require.NotNil(t, t3.Auth)
1637+
1638+
// revoke t3 out of band
1639+
err = auth.RevokeAccessor(t3.Auth.Accessor)
1640+
require.NoError(t, err)
1641+
1642+
// Create two VaultAccessors
1643+
vas := []*structs.VaultAccessor{
1644+
{Accessor: t1.Auth.Accessor},
1645+
{Accessor: t2.Auth.Accessor},
1646+
{Accessor: t3.Auth.Accessor},
1647+
}
1648+
1649+
// Issue a token revocation
1650+
err = client.RevokeTokens(context.Background(), vas, true)
1651+
require.NoError(t, err)
1652+
require.Empty(t, client.revoking)
1653+
1654+
// revoke token again
1655+
err = client.RevokeTokens(context.Background(), vas, true)
1656+
require.NoError(t, err)
1657+
require.Empty(t, client.revoking)
1658+
1659+
// Lookup the token and make sure we get an error
1660+
require.Len(t, purged, 3)
1661+
require.Contains(t, purged, t1.Auth.Accessor)
1662+
require.Contains(t, purged, t2.Auth.Accessor)
1663+
require.Contains(t, purged, t3.Auth.Accessor)
1664+
s, err := auth.Lookup(t1.Auth.ClientToken)
1665+
require.Errorf(t, err, "failed to purge token: %v", s)
1666+
s, err = auth.Lookup(t2.Auth.ClientToken)
1667+
require.Errorf(t, err, "failed to purge token: %v", s)
1668+
}
1669+
15441670
func waitForConnection(v *vaultClient, t *testing.T) {
15451671
testutil.WaitForResult(func() (bool, error) {
15461672
return v.ConnectionEstablished()

0 commit comments

Comments
 (0)