open-nomad/nomad/structs/acl_test.go
James Rasell 2736cf0dfa
acl: make listing RPC and HTTP API a stub return object. (#14211)
Making the ACL Role listing return object a stub future-proofs the
endpoint. In the event the role object grows, we are not bound by
having to return all fields within the list endpoint or change the
signature of the endpoint to reduce the list return size.
2022-08-22 17:20:23 +02:00

765 lines
20 KiB
Go

package structs
import (
"fmt"
"testing"
"time"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/helper/pointer"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/stretchr/testify/require"
)
func TestACLToken_Canonicalize(t *testing.T) {
testCases := []struct {
name string
testFn func()
}{
{
name: "token with accessor",
testFn: func() {
mockToken := &ACLToken{
AccessorID: uuid.Generate(),
SecretID: uuid.Generate(),
Name: "my cool token " + uuid.Generate(),
Type: "client",
Policies: []string{"foo", "bar"},
Roles: []*ACLTokenRoleLink{},
Global: false,
CreateTime: time.Now().UTC(),
CreateIndex: 10,
ModifyIndex: 20,
}
mockToken.SetHash()
copiedMockToken := mockToken.Copy()
mockToken.Canonicalize()
require.Equal(t, copiedMockToken, mockToken)
},
},
{
name: "token without accessor",
testFn: func() {
mockToken := &ACLToken{
Name: "my cool token " + uuid.Generate(),
Type: "client",
Policies: []string{"foo", "bar"},
Global: false,
}
mockToken.Canonicalize()
require.NotEmpty(t, mockToken.AccessorID)
require.NotEmpty(t, mockToken.SecretID)
require.NotEmpty(t, mockToken.CreateTime)
},
},
{
name: "token with ttl without accessor",
testFn: func() {
mockToken := &ACLToken{
Name: "my cool token " + uuid.Generate(),
Type: "client",
Policies: []string{"foo", "bar"},
Global: false,
ExpirationTTL: 10 * time.Hour,
}
mockToken.Canonicalize()
require.NotEmpty(t, mockToken.AccessorID)
require.NotEmpty(t, mockToken.SecretID)
require.NotEmpty(t, mockToken.CreateTime)
require.NotEmpty(t, mockToken.ExpirationTime)
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tc.testFn()
})
}
}
func TestACLTokenValidate(t *testing.T) {
ci.Parallel(t)
testCases := []struct {
name string
inputACLToken *ACLToken
inputExistingACLToken *ACLToken
expectedErrorContains string
}{
{
name: "missing type",
inputACLToken: &ACLToken{},
inputExistingACLToken: nil,
expectedErrorContains: "client or management",
},
{
name: "missing policies or roles",
inputACLToken: &ACLToken{
Type: ACLClientToken,
},
inputExistingACLToken: nil,
expectedErrorContains: "missing policies or roles",
},
{
name: "invalid policies",
inputACLToken: &ACLToken{
Type: ACLManagementToken,
Policies: []string{"foo"},
},
inputExistingACLToken: nil,
expectedErrorContains: "associated with policies or roles",
},
{
name: "invalid roles",
inputACLToken: &ACLToken{
Type: ACLManagementToken,
Roles: []*ACLTokenRoleLink{{Name: "foo"}},
},
inputExistingACLToken: nil,
expectedErrorContains: "associated with policies or roles",
},
{
name: "name too long",
inputACLToken: &ACLToken{
Type: ACLManagementToken,
Name: uuid.Generate() + uuid.Generate() + uuid.Generate() + uuid.Generate() +
uuid.Generate() + uuid.Generate() + uuid.Generate() + uuid.Generate(),
},
inputExistingACLToken: nil,
expectedErrorContains: "name too long",
},
{
name: "negative TTL",
inputACLToken: &ACLToken{
Type: ACLManagementToken,
Name: "foo",
ExpirationTTL: -1 * time.Hour,
},
inputExistingACLToken: nil,
expectedErrorContains: "should not be negative",
},
{
name: "TTL too small",
inputACLToken: &ACLToken{
Type: ACLManagementToken,
Name: "foo",
CreateTime: time.Date(2022, time.July, 11, 16, 23, 0, 0, time.UTC),
ExpirationTime: pointer.Of(time.Date(2022, time.July, 11, 16, 23, 10, 0, time.UTC)),
},
inputExistingACLToken: nil,
expectedErrorContains: "expiration time cannot be less than",
},
{
name: "TTL too large",
inputACLToken: &ACLToken{
Type: ACLManagementToken,
Name: "foo",
CreateTime: time.Date(2022, time.July, 11, 16, 23, 0, 0, time.UTC),
ExpirationTime: pointer.Of(time.Date(2042, time.July, 11, 16, 23, 0, 0, time.UTC)),
},
inputExistingACLToken: nil,
expectedErrorContains: "expiration time cannot be more than",
},
{
name: "valid management",
inputACLToken: &ACLToken{
Type: ACLManagementToken,
Name: "foo",
},
inputExistingACLToken: nil,
expectedErrorContains: "",
},
{
name: "valid client",
inputACLToken: &ACLToken{
Type: ACLClientToken,
Name: "foo",
Policies: []string{"foo"},
},
inputExistingACLToken: nil,
expectedErrorContains: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actualOutputError := tc.inputACLToken.Validate(1*time.Minute, 24*time.Hour, tc.inputExistingACLToken)
if tc.expectedErrorContains != "" {
require.ErrorContains(t, actualOutputError, tc.expectedErrorContains)
} else {
require.NoError(t, actualOutputError)
}
})
}
}
func TestACLToken_HasExpirationTime(t *testing.T) {
testCases := []struct {
name string
inputACLToken *ACLToken
expectedOutput bool ``
}{
{
name: "nil acl token",
inputACLToken: nil,
expectedOutput: false,
},
{
name: "default empty value",
inputACLToken: &ACLToken{},
expectedOutput: false,
},
{
name: "expiration set to now",
inputACLToken: &ACLToken{
ExpirationTime: pointer.Of(time.Now().UTC()),
},
expectedOutput: true,
},
{
name: "expiration set to past",
inputACLToken: &ACLToken{
ExpirationTime: pointer.Of(time.Date(2022, time.February, 21, 19, 35, 0, 0, time.UTC)),
},
expectedOutput: true,
},
{
name: "expiration set to future",
inputACLToken: &ACLToken{
ExpirationTime: pointer.Of(time.Date(2087, time.April, 25, 12, 0, 0, 0, time.UTC)),
},
expectedOutput: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actualOutput := tc.inputACLToken.HasExpirationTime()
require.Equal(t, tc.expectedOutput, actualOutput)
})
}
}
func TestACLToken_IsExpired(t *testing.T) {
testCases := []struct {
name string
inputACLToken *ACLToken
inputTime time.Time
expectedOutput bool
}{
{
name: "token without expiry",
inputACLToken: &ACLToken{},
inputTime: time.Now().UTC(),
expectedOutput: false,
},
{
name: "empty input time",
inputACLToken: &ACLToken{},
inputTime: time.Time{},
expectedOutput: false,
},
{
name: "token not expired",
inputACLToken: &ACLToken{
ExpirationTime: pointer.Of(time.Date(2022, time.May, 9, 10, 27, 0, 0, time.UTC)),
},
inputTime: time.Date(2022, time.May, 9, 10, 26, 0, 0, time.UTC),
expectedOutput: false,
},
{
name: "token expired",
inputACLToken: &ACLToken{
ExpirationTime: pointer.Of(time.Date(2022, time.May, 9, 10, 27, 0, 0, time.UTC)),
},
inputTime: time.Date(2022, time.May, 9, 10, 28, 0, 0, time.UTC),
expectedOutput: true,
},
{
name: "empty input time",
inputACLToken: &ACLToken{
ExpirationTime: pointer.Of(time.Date(2022, time.May, 9, 10, 27, 0, 0, time.UTC)),
},
inputTime: time.Time{},
expectedOutput: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actualOutput := tc.inputACLToken.IsExpired(tc.inputTime)
require.Equal(t, tc.expectedOutput, actualOutput)
})
}
}
func TestACLRole_SetHash(t *testing.T) {
testCases := []struct {
name string
inputACLRole *ACLRole
expectedOutput []byte
}{
{
name: "no hash set",
inputACLRole: &ACLRole{
Name: "acl-role",
Description: "mocked-test-acl-role",
Policies: []*ACLRolePolicyLink{
{Name: "mocked-test-policy-1"},
{Name: "mocked-test-policy-2"},
},
CreateIndex: 10,
ModifyIndex: 10,
Hash: []byte{},
},
expectedOutput: []byte{
122, 193, 189, 171, 197, 13, 37, 81, 141, 213, 188, 212, 179, 223, 148, 160,
171, 141, 155, 136, 21, 128, 252, 100, 149, 195, 236, 148, 94, 70, 173, 102,
},
},
{
name: "hash set with change",
inputACLRole: &ACLRole{
Name: "acl-role",
Description: "mocked-test-acl-role",
Policies: []*ACLRolePolicyLink{
{Name: "mocked-test-policy-1"},
{Name: "mocked-test-policy-2"},
},
CreateIndex: 10,
ModifyIndex: 10,
Hash: []byte{
137, 147, 2, 29, 53, 94, 78, 13, 45, 51, 127, 193, 21, 248, 230, 126, 34,
106, 216, 73, 248, 219, 209, 146, 204, 107, 185, 2, 89, 255, 198, 5,
},
},
expectedOutput: []byte{
122, 193, 189, 171, 197, 13, 37, 81, 141, 213, 188, 212, 179, 223, 148, 160,
171, 141, 155, 136, 21, 128, 252, 100, 149, 195, 236, 148, 94, 70, 173, 102,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actualOutput := tc.inputACLRole.SetHash()
require.Equal(t, tc.expectedOutput, actualOutput)
require.Equal(t, tc.inputACLRole.Hash, actualOutput)
})
}
}
func TestACLRole_Validate(t *testing.T) {
testCases := []struct {
name string
inputACLRole *ACLRole
expectedError bool
expectedErrorContains string
}{
{
name: "role name too long",
inputACLRole: &ACLRole{
Name: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
expectedError: true,
expectedErrorContains: "invalid name",
},
{
name: "role name too short",
inputACLRole: &ACLRole{
Name: "",
},
expectedError: true,
expectedErrorContains: "invalid name",
},
{
name: "role name with invalid characters",
inputACLRole: &ACLRole{
Name: "--#$%$^%_%%_?>",
},
expectedError: true,
expectedErrorContains: "invalid name",
},
{
name: "description too long",
inputACLRole: &ACLRole{
Name: "acl-role",
Description: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
expectedError: true,
expectedErrorContains: "description longer than",
},
{
name: "no policies",
inputACLRole: &ACLRole{
Name: "acl-role",
Description: "",
},
expectedError: true,
expectedErrorContains: "at least one policy should be specified",
},
{
name: "valid",
inputACLRole: &ACLRole{
Name: "acl-role",
Description: "",
Policies: []*ACLRolePolicyLink{
{Name: "policy-1"},
},
},
expectedError: false,
expectedErrorContains: "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actualOutput := tc.inputACLRole.Validate()
if tc.expectedError {
require.ErrorContains(t, actualOutput, tc.expectedErrorContains)
} else {
require.NoError(t, actualOutput)
}
})
}
}
func TestACLRole_Canonicalize(t *testing.T) {
testCases := []struct {
name string
inputACLRole *ACLRole
}{
{
name: "no ID set",
inputACLRole: &ACLRole{},
},
{
name: "id set",
inputACLRole: &ACLRole{ID: "some-random-uuid"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
existing := tc.inputACLRole.Copy()
tc.inputACLRole.Canonicalize()
if existing.ID == "" {
require.NotEmpty(t, tc.inputACLRole.ID)
} else {
require.Equal(t, existing.ID, tc.inputACLRole.ID)
}
})
}
}
func TestACLRole_Equals(t *testing.T) {
testCases := []struct {
name string
composedACLRole *ACLRole
inputACLRole *ACLRole
expectedOutput bool
}{
{
name: "equal with hash set",
composedACLRole: &ACLRole{
Name: "acl-role-",
Description: "mocked-test-acl-role",
Policies: []*ACLRolePolicyLink{
{Name: "mocked-test-policy-1"},
{Name: "mocked-test-policy-2"},
},
CreateIndex: 10,
ModifyIndex: 10,
Hash: []byte{
122, 193, 189, 171, 197, 13, 37, 81, 141, 213, 188, 212, 179, 223, 148, 160,
171, 141, 155, 136, 21, 128, 252, 100, 149, 195, 236, 148, 94, 70, 173, 102,
},
},
inputACLRole: &ACLRole{
Name: "acl-role",
Description: "mocked-test-acl-role",
Policies: []*ACLRolePolicyLink{
{Name: "mocked-test-policy-1"},
{Name: "mocked-test-policy-2"},
},
CreateIndex: 10,
ModifyIndex: 10,
Hash: []byte{
122, 193, 189, 171, 197, 13, 37, 81, 141, 213, 188, 212, 179, 223, 148, 160,
171, 141, 155, 136, 21, 128, 252, 100, 149, 195, 236, 148, 94, 70, 173, 102,
},
},
expectedOutput: true,
},
{
name: "equal without hash set",
composedACLRole: &ACLRole{
Name: "acl-role",
Description: "mocked-test-acl-role",
Policies: []*ACLRolePolicyLink{
{Name: "mocked-test-policy-1"},
{Name: "mocked-test-policy-2"},
},
CreateIndex: 10,
ModifyIndex: 10,
Hash: []byte{},
},
inputACLRole: &ACLRole{
Name: "acl-role",
Description: "mocked-test-acl-role",
Policies: []*ACLRolePolicyLink{
{Name: "mocked-test-policy-1"},
{Name: "mocked-test-policy-2"},
},
CreateIndex: 10,
ModifyIndex: 10,
Hash: []byte{},
},
expectedOutput: true,
},
{
name: "both nil",
composedACLRole: nil,
inputACLRole: nil,
expectedOutput: true,
},
{
name: "not equal composed nil",
composedACLRole: nil,
inputACLRole: &ACLRole{
Name: "acl-role",
Description: "mocked-test-acl-role",
Policies: []*ACLRolePolicyLink{
{Name: "mocked-test-policy-1"},
{Name: "mocked-test-policy-2"},
},
CreateIndex: 10,
ModifyIndex: 10,
Hash: []byte{
122, 193, 189, 171, 197, 13, 37, 81, 141, 213, 188, 212, 179, 223, 148, 160,
171, 141, 155, 136, 21, 128, 252, 100, 149, 195, 236, 148, 94, 70, 173, 102,
},
},
expectedOutput: false,
},
{
name: "not equal input nil",
composedACLRole: &ACLRole{
Name: "acl-role",
Description: "mocked-test-acl-role",
Policies: []*ACLRolePolicyLink{
{Name: "mocked-test-policy-1"},
{Name: "mocked-test-policy-2"},
},
CreateIndex: 10,
ModifyIndex: 10,
Hash: []byte{
122, 193, 189, 171, 197, 13, 37, 81, 141, 213, 188, 212, 179, 223, 148, 160,
171, 141, 155, 136, 21, 128, 252, 100, 149, 195, 236, 148, 94, 70, 173, 102,
},
},
inputACLRole: nil,
expectedOutput: false,
},
{
name: "not equal with hash set",
composedACLRole: &ACLRole{
Name: "acl-role",
Description: "mocked-test-acl-role",
Policies: []*ACLRolePolicyLink{
{Name: "mocked-test-policy-1"},
{Name: "mocked-test-policy-2"},
},
CreateIndex: 10,
ModifyIndex: 10,
Hash: []byte{
122, 193, 189, 171, 197, 13, 37, 81, 141, 213, 188, 212, 179, 223, 148, 160,
171, 141, 155, 136, 21, 128, 252, 100, 149, 195, 236, 148, 94, 70, 173, 102,
},
},
inputACLRole: &ACLRole{
Name: "acl-role",
Description: "mocked-test-acl-role",
Policies: []*ACLRolePolicyLink{
{Name: "mocked-test-policy-1"},
},
CreateIndex: 10,
ModifyIndex: 10,
Hash: []byte{
137, 147, 2, 29, 53, 94, 78, 13, 45, 51, 127, 193, 21, 248, 230, 126, 34,
106, 216, 73, 248, 219, 209, 146, 204, 107, 185, 2, 89, 255, 198, 5,
},
},
expectedOutput: false,
},
{
name: "not equal without hash set",
composedACLRole: &ACLRole{
Name: "acl-role",
Description: "mocked-test-acl-role",
Policies: []*ACLRolePolicyLink{
{Name: "mocked-test-policy-1"},
{Name: "mocked-test-policy-2"},
},
CreateIndex: 10,
ModifyIndex: 10,
Hash: []byte{},
},
inputACLRole: &ACLRole{
Name: "acl-role",
Description: "mocked-test-acl-role",
Policies: []*ACLRolePolicyLink{
{Name: "mocked-test-policy-1"},
},
CreateIndex: 10,
ModifyIndex: 10,
Hash: []byte{},
},
expectedOutput: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actualOutput := tc.composedACLRole.Equals(tc.inputACLRole)
require.Equal(t, tc.expectedOutput, actualOutput)
})
}
}
func TestACLRole_Copy(t *testing.T) {
testCases := []struct {
name string
inputACLRole *ACLRole
}{
{
name: "nil input",
inputACLRole: nil,
},
{
name: "general 1",
inputACLRole: &ACLRole{
Name: fmt.Sprintf("acl-role"),
Description: "mocked-test-acl-role",
Policies: []*ACLRolePolicyLink{
{Name: "mocked-test-policy-1"},
{Name: "mocked-test-policy-2"},
},
CreateIndex: 10,
ModifyIndex: 10,
Hash: []byte{
122, 193, 189, 171, 197, 13, 37, 81, 141, 213, 188, 212, 179, 223, 148, 160,
171, 141, 155, 136, 21, 128, 252, 100, 149, 195, 236, 148, 94, 70, 173, 102,
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actualOutput := tc.inputACLRole.Copy()
require.Equal(t, tc.inputACLRole, actualOutput)
})
}
}
func TestACLRole_Stub(t *testing.T) {
testCases := []struct {
name string
inputACLRole *ACLRole
expectedOutput *ACLRoleListStub
}{
{
name: "partially hydrated",
inputACLRole: &ACLRole{
ID: "1d6332c8-02d7-325e-f675-a9bb4aff0c51",
Name: "my-lovely-role",
Description: "",
Policies: []*ACLRolePolicyLink{
{Name: "my-lovely-policy"},
},
Hash: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
CreateIndex: 24,
ModifyIndex: 24,
},
expectedOutput: &ACLRoleListStub{
ID: "1d6332c8-02d7-325e-f675-a9bb4aff0c51",
Name: "my-lovely-role",
Description: "",
Policies: []*ACLRolePolicyLink{
{Name: "my-lovely-policy"},
},
Hash: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
CreateIndex: 24,
ModifyIndex: 24,
},
},
{
name: "hully hydrated",
inputACLRole: &ACLRole{
ID: "1d6332c8-02d7-325e-f675-a9bb4aff0c51",
Name: "my-lovely-role",
Description: "this-is-my-lovely-role",
Policies: []*ACLRolePolicyLink{
{Name: "my-lovely-policy"},
},
Hash: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
CreateIndex: 24,
ModifyIndex: 24,
},
expectedOutput: &ACLRoleListStub{
ID: "1d6332c8-02d7-325e-f675-a9bb4aff0c51",
Name: "my-lovely-role",
Description: "this-is-my-lovely-role",
Policies: []*ACLRolePolicyLink{
{Name: "my-lovely-policy"},
},
Hash: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
CreateIndex: 24,
ModifyIndex: 24,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actualOutput := tc.inputACLRole.Stub()
require.Equal(t, tc.expectedOutput, actualOutput)
})
}
}
func Test_ACLRolesUpsertRequest(t *testing.T) {
req := ACLRolesUpsertRequest{}
require.False(t, req.IsRead())
}
func Test_ACLRolesDeleteByIDRequest(t *testing.T) {
req := ACLRolesDeleteByIDRequest{}
require.False(t, req.IsRead())
}
func Test_ACLRolesListRequest(t *testing.T) {
req := ACLRolesListRequest{}
require.True(t, req.IsRead())
}
func Test_ACLRolesByIDRequest(t *testing.T) {
req := ACLRolesByIDRequest{}
require.True(t, req.IsRead())
}
func Test_ACLRoleByIDRequest(t *testing.T) {
req := ACLRoleByIDRequest{}
require.True(t, req.IsRead())
}
func Test_ACLRoleByNameRequest(t *testing.T) {
req := ACLRoleByNameRequest{}
require.True(t, req.IsRead())
}