consul/agent/consul/acl_token_exp_test.go

224 lines
5.5 KiB
Go

package consul
import (
"os"
"testing"
"time"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/testrpc"
"github.com/stretchr/testify/require"
)
func TestACLTokenReap_Primary(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
t.Run("global", func(t *testing.T) {
t.Parallel()
testACLTokenReap_Primary(t, false, true)
})
t.Run("local", func(t *testing.T) {
t.Parallel()
testACLTokenReap_Primary(t, true, false)
})
}
func testACLTokenReap_Primary(t *testing.T, local, global bool) {
// -------------------------------------------
// A word of caution when testing reapExpiredACLTokens():
//
// The underlying memdb index used for reaping has a minimum granularity of
// 1 second as it delegates to `time.Unix()`. This test will have to be
// deliberately slow to allow for necessary sleeps. If you try to make it
// operate faster (using expiration ttls of milliseconds) it will be flaky.
// -------------------------------------------
t.Helper()
require.NotEqual(t, local, global)
dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.ACLDatacenter = "dc1"
c.ACLsEnabled = true
c.ACLMasterToken = "root"
c.ACLTokenMinExpirationTTL = 10 * time.Millisecond
c.ACLTokenMaxExpirationTTL = 8 * time.Second
})
defer os.RemoveAll(dir1)
defer s1.Shutdown()
testrpc.WaitForLeader(t, s1.RPC, "dc1")
codec := rpcClient(t, s1)
defer codec.Close()
acl := ACL{srv: s1}
masterTokenAccessorID, err := retrieveTestTokenAccessorForSecret(codec, "root", "dc1", "root")
require.NoError(t, err)
listTokens := func() (localTokens, globalTokens []string, err error) {
req := structs.ACLTokenListRequest{
Datacenter: "dc1",
QueryOptions: structs.QueryOptions{Token: "root"},
}
var res structs.ACLTokenListResponse
err = acl.TokenList(&req, &res)
if err != nil {
return nil, nil, err
}
for _, tok := range res.Tokens {
if tok.Local {
localTokens = append(localTokens, tok.AccessorID)
} else {
globalTokens = append(globalTokens, tok.AccessorID)
}
}
return localTokens, globalTokens, nil
}
requireTokenMatch := func(t *testing.T, expect []string) {
t.Helper()
var expectLocal, expectGlobal []string
// The master token and the anonymous token are always going to be
// present and global.
expectGlobal = append(expectGlobal, masterTokenAccessorID)
expectGlobal = append(expectGlobal, structs.ACLTokenAnonymousID)
if local {
expectLocal = append(expectLocal, expect...)
} else {
expectGlobal = append(expectGlobal, expect...)
}
localTokens, globalTokens, err := listTokens()
require.NoError(t, err)
require.ElementsMatch(t, expectLocal, localTokens)
require.ElementsMatch(t, expectGlobal, globalTokens)
}
// initial sanity check
requireTokenMatch(t, []string{})
t.Run("no tokens", func(t *testing.T) {
n, err := s1.reapExpiredACLTokens(local, global)
require.NoError(t, err)
require.Equal(t, 0, n)
requireTokenMatch(t, []string{})
})
// 2 normal
token1, err := upsertTestToken(codec, "root", "dc1", func(token *structs.ACLToken) {
token.Local = local
})
require.NoError(t, err)
token2, err := upsertTestToken(codec, "root", "dc1", func(token *structs.ACLToken) {
token.Local = local
})
require.NoError(t, err)
requireTokenMatch(t, []string{
token1.AccessorID,
token2.AccessorID,
})
t.Run("only normal tokens", func(t *testing.T) {
n, err := s1.reapExpiredACLTokens(local, global)
require.NoError(t, err)
require.Equal(t, 0, n)
requireTokenMatch(t, []string{
token1.AccessorID,
token2.AccessorID,
})
})
// 2 expiring
token3, err := upsertTestToken(codec, "root", "dc1", func(token *structs.ACLToken) {
token.ExpirationTTL = 1 * time.Second
token.Local = local
})
require.NoError(t, err)
token4, err := upsertTestToken(codec, "root", "dc1", func(token *structs.ACLToken) {
token.ExpirationTTL = 5 * time.Second
token.Local = local
})
require.NoError(t, err)
// 2 more normal
token5, err := upsertTestToken(codec, "root", "dc1", func(token *structs.ACLToken) {
token.Local = local
})
require.NoError(t, err)
token6, err := upsertTestToken(codec, "root", "dc1", func(token *structs.ACLToken) {
token.Local = local
})
require.NoError(t, err)
requireTokenMatch(t, []string{
token1.AccessorID,
token2.AccessorID,
token3.AccessorID,
token4.AccessorID,
token5.AccessorID,
token6.AccessorID,
})
t.Run("mixed but nothing expired yet", func(t *testing.T) {
n, err := s1.reapExpiredACLTokens(local, global)
require.NoError(t, err)
require.Equal(t, 0, n)
requireTokenMatch(t, []string{
token1.AccessorID,
token2.AccessorID,
token3.AccessorID,
token4.AccessorID,
token5.AccessorID,
token6.AccessorID,
})
})
time.Sleep(token3.ExpirationTime.Sub(time.Now()) + 10*time.Millisecond)
t.Run("one should be reaped", func(t *testing.T) {
n, err := s1.reapExpiredACLTokens(local, global)
require.NoError(t, err)
require.Equal(t, 1, n)
requireTokenMatch(t, []string{
token1.AccessorID,
token2.AccessorID,
// token3.AccessorID,
token4.AccessorID,
token5.AccessorID,
token6.AccessorID,
})
})
time.Sleep(token4.ExpirationTime.Sub(time.Now()) + 10*time.Millisecond)
t.Run("two should be reaped", func(t *testing.T) {
n, err := s1.reapExpiredACLTokens(local, global)
require.NoError(t, err)
require.Equal(t, 1, n)
requireTokenMatch(t, []string{
token1.AccessorID,
token2.AccessorID,
// token3.AccessorID,
// token4.AccessorID,
token5.AccessorID,
token6.AccessorID,
})
})
}