a8a8b1f84f
This commit adds basic expiry checking when performing ACL token resolution. This expiry checking is local to each server and does not at this time take into account potential time skew on server hosts. A new error message has been created so clients whose token has expired get a clear message, rather than a generic token not found. The ACL resolution tests have been refactored into table driven tests, so additions are easier in the future.
248 lines
7.2 KiB
Go
248 lines
7.2 KiB
Go
package nomad
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/nomad/acl"
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/hashicorp/nomad/helper/pointer"
|
|
"github.com/hashicorp/nomad/helper/uuid"
|
|
"github.com/hashicorp/nomad/nomad/mock"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/hashicorp/nomad/testutil"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestResolveACLToken(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
testServer, _, testServerCleanup := TestACLServer(t, nil)
|
|
defer testServerCleanup()
|
|
testutil.WaitForLeader(t, testServer.RPC)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
testFn func(testServer *Server)
|
|
}{
|
|
{
|
|
name: "leader token",
|
|
testFn: func(testServer *Server) {
|
|
|
|
// Check the leader ACL token is correctly set.
|
|
leaderACL := testServer.getLeaderAcl()
|
|
require.NotEmpty(t, leaderACL)
|
|
|
|
// Resolve the token and ensure it's a management token.
|
|
aclResp, err := testServer.ResolveToken(leaderACL)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, aclResp)
|
|
require.True(t, aclResp.IsManagement())
|
|
},
|
|
},
|
|
{
|
|
name: "anonymous token",
|
|
testFn: func(testServer *Server) {
|
|
|
|
// Call the function with an empty input secret ID which is
|
|
// classed as representing anonymous access in clusters with
|
|
// ACLs enabled.
|
|
aclResp, err := testServer.ResolveToken("")
|
|
require.NoError(t, err)
|
|
require.NotNil(t, aclResp)
|
|
require.False(t, aclResp.IsManagement())
|
|
},
|
|
},
|
|
{
|
|
name: "token not found",
|
|
testFn: func(testServer *Server) {
|
|
|
|
// Call the function with randomly generated secret ID which
|
|
// does not exist within state.
|
|
aclResp, err := testServer.ResolveToken(uuid.Generate())
|
|
require.Equal(t, structs.ErrTokenNotFound, err)
|
|
require.Nil(t, aclResp)
|
|
},
|
|
},
|
|
{
|
|
name: "token expired",
|
|
testFn: func(testServer *Server) {
|
|
|
|
// Create a mock token with an expiration time long in the
|
|
// past, and upsert.
|
|
token := mock.ACLToken()
|
|
token.ExpirationTime = pointer.Of(time.Date(
|
|
1970, time.January, 1, 0, 0, 0, 0, time.UTC))
|
|
|
|
err := testServer.State().UpsertACLTokens(
|
|
structs.MsgTypeTestSetup, 10, []*structs.ACLToken{token})
|
|
require.NoError(t, err)
|
|
|
|
// Perform the function call which should result in finding the
|
|
// token has expired.
|
|
aclResp, err := testServer.ResolveToken(uuid.Generate())
|
|
require.Equal(t, structs.ErrTokenNotFound, err)
|
|
require.Nil(t, aclResp)
|
|
},
|
|
},
|
|
{
|
|
name: "management token",
|
|
testFn: func(testServer *Server) {
|
|
|
|
// Generate a management token and upsert this.
|
|
managementToken := mock.ACLToken()
|
|
managementToken.Type = structs.ACLManagementToken
|
|
managementToken.Policies = nil
|
|
|
|
err := testServer.State().UpsertACLTokens(
|
|
structs.MsgTypeTestSetup, 10, []*structs.ACLToken{managementToken})
|
|
require.NoError(t, err)
|
|
|
|
// Resolve the token and check that we received a management
|
|
// ACL.
|
|
aclResp, err := testServer.ResolveToken(managementToken.SecretID)
|
|
require.Nil(t, err)
|
|
require.NotNil(t, aclResp)
|
|
require.True(t, aclResp.IsManagement())
|
|
require.Equal(t, acl.ManagementACL, aclResp)
|
|
},
|
|
},
|
|
{
|
|
name: "client token",
|
|
testFn: func(testServer *Server) {
|
|
|
|
// Generate a client token with associated policies and upsert
|
|
// these.
|
|
policy1 := mock.ACLPolicy()
|
|
policy2 := mock.ACLPolicy()
|
|
err := testServer.State().UpsertACLPolicies(
|
|
structs.MsgTypeTestSetup, 10, []*structs.ACLPolicy{policy1, policy2})
|
|
|
|
clientToken := mock.ACLToken()
|
|
clientToken.Policies = []string{policy1.Name, policy2.Name}
|
|
err = testServer.State().UpsertACLTokens(
|
|
structs.MsgTypeTestSetup, 20, []*structs.ACLToken{clientToken})
|
|
require.NoError(t, err)
|
|
|
|
// Resolve the token and check that we received a client
|
|
// ACL with appropriate permissions.
|
|
aclResp, err := testServer.ResolveToken(clientToken.SecretID)
|
|
require.Nil(t, err)
|
|
require.NotNil(t, aclResp)
|
|
require.False(t, aclResp.IsManagement())
|
|
|
|
allowed := aclResp.AllowNamespaceOperation("default", acl.NamespaceCapabilityListJobs)
|
|
require.True(t, allowed)
|
|
allowed = aclResp.AllowNamespaceOperation("other", acl.NamespaceCapabilityListJobs)
|
|
require.False(t, allowed)
|
|
|
|
// Resolve the same token again and ensure we get the same
|
|
// result.
|
|
aclResp2, err := testServer.ResolveToken(clientToken.SecretID)
|
|
require.Nil(t, err)
|
|
require.NotNil(t, aclResp2)
|
|
require.Equal(t, aclResp, aclResp2)
|
|
|
|
// Bust the cache by upserting the policy
|
|
err = testServer.State().UpsertACLPolicies(
|
|
structs.MsgTypeTestSetup, 30, []*structs.ACLPolicy{policy1})
|
|
require.Nil(t, err)
|
|
|
|
// Resolve the same token again, should get different value
|
|
aclResp3, err := testServer.ResolveToken(clientToken.SecretID)
|
|
require.Nil(t, err)
|
|
require.NotNil(t, aclResp3)
|
|
require.NotEqual(t, aclResp2, aclResp3)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
tc.testFn(testServer)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolveSecretToken(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
testServer, _, testServerCleanup := TestACLServer(t, nil)
|
|
defer testServerCleanup()
|
|
testutil.WaitForLeader(t, testServer.RPC)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
testFn func(testServer *Server)
|
|
}{
|
|
{
|
|
name: "valid token",
|
|
testFn: func(testServer *Server) {
|
|
|
|
// Generate and upsert a token.
|
|
token := mock.ACLToken()
|
|
err := testServer.State().UpsertACLTokens(
|
|
structs.MsgTypeTestSetup, 10, []*structs.ACLToken{token})
|
|
require.NoError(t, err)
|
|
|
|
// Attempt to look up the token and perform checks.
|
|
tokenResp, err := testServer.ResolveSecretToken(token.SecretID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, tokenResp)
|
|
require.Equal(t, token, tokenResp)
|
|
},
|
|
},
|
|
{
|
|
name: "anonymous token",
|
|
testFn: func(testServer *Server) {
|
|
|
|
// Call the function with an empty input secret ID which is
|
|
// classed as representing anonymous access in clusters with
|
|
// ACLs enabled.
|
|
tokenResp, err := testServer.ResolveSecretToken("")
|
|
require.NoError(t, err)
|
|
require.NotNil(t, tokenResp)
|
|
require.Equal(t, structs.AnonymousACLToken, tokenResp)
|
|
},
|
|
},
|
|
{
|
|
name: "token not found",
|
|
testFn: func(testServer *Server) {
|
|
|
|
// Call the function with randomly generated secret ID which
|
|
// does not exist within state.
|
|
tokenResp, err := testServer.ResolveSecretToken(uuid.Generate())
|
|
require.Equal(t, structs.ErrTokenNotFound, err)
|
|
require.Nil(t, tokenResp)
|
|
},
|
|
},
|
|
{
|
|
name: "token expired",
|
|
testFn: func(testServer *Server) {
|
|
|
|
// Create a mock token with an expiration time long in the
|
|
// past, and upsert.
|
|
token := mock.ACLToken()
|
|
token.ExpirationTime = pointer.Of(time.Date(
|
|
1970, time.January, 1, 0, 0, 0, 0, time.UTC))
|
|
|
|
err := testServer.State().UpsertACLTokens(
|
|
structs.MsgTypeTestSetup, 10, []*structs.ACLToken{token})
|
|
require.NoError(t, err)
|
|
|
|
// Perform the function call which should result in finding the
|
|
// token has expired.
|
|
tokenResp, err := testServer.ResolveSecretToken(uuid.Generate())
|
|
require.Equal(t, structs.ErrTokenNotFound, err)
|
|
require.Nil(t, tokenResp)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
tc.testFn(testServer)
|
|
})
|
|
}
|
|
}
|