open-nomad/nomad/structs/acl_test.go
James Rasell 8295d0e516
acl: add validation to binding rule selector on upsert. (#16210)
* acl: add validation to binding rule selector on upsert.

* docs: add more information on binding rule selector escaping.
2023-02-17 15:38:55 +01:00

1535 lines
40 KiB
Go

package structs
import (
"errors"
"fmt"
"testing"
"time"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/helper/pointer"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/shoenig/test/must"
"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 TestACLToken_HasRoles(t *testing.T) {
testCases := []struct {
name string
inputToken *ACLToken
inputRoleIDs []string
expectedOutput bool
}{
{
name: "client token request all subset",
inputToken: &ACLToken{
Type: ACLClientToken,
Roles: []*ACLTokenRoleLink{
{ID: "foo"},
{ID: "bar"},
{ID: "baz"},
},
},
inputRoleIDs: []string{"foo", "bar", "baz"},
expectedOutput: true,
},
{
name: "client token request partial subset",
inputToken: &ACLToken{
Type: ACLClientToken,
Roles: []*ACLTokenRoleLink{
{ID: "foo"},
{ID: "bar"},
{ID: "baz"},
},
},
inputRoleIDs: []string{"foo", "baz"},
expectedOutput: true,
},
{
name: "client token request one subset",
inputToken: &ACLToken{
Type: ACLClientToken,
Roles: []*ACLTokenRoleLink{
{ID: "foo"},
{ID: "bar"},
{ID: "baz"},
},
},
inputRoleIDs: []string{"baz"},
expectedOutput: true,
},
{
name: "client token request no subset",
inputToken: &ACLToken{
Type: ACLClientToken,
Roles: []*ACLTokenRoleLink{
{ID: "foo"},
{ID: "bar"},
{ID: "baz"},
},
},
inputRoleIDs: []string{"new"},
expectedOutput: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actualOutput := tc.inputToken.HasRoles(tc.inputRoleIDs)
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.Equal(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: "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())
}
func Test_ACLAuthMethodListRequest(t *testing.T) {
req := ACLAuthMethodListRequest{}
must.True(t, req.IsRead())
}
func Test_ACLAuthMethodGetRequest(t *testing.T) {
req := ACLAuthMethodGetRequest{}
must.True(t, req.IsRead())
}
func TestACLAuthMethodSetHash(t *testing.T) {
ci.Parallel(t)
am := &ACLAuthMethod{
Name: "foo",
Type: "bad type",
}
out1 := am.SetHash()
must.NotNil(t, out1)
must.NotNil(t, am.Hash)
must.Eq(t, out1, am.Hash)
am.Type = "good type"
out2 := am.SetHash()
must.NotNil(t, out2)
must.NotNil(t, am.Hash)
must.Eq(t, out2, am.Hash)
must.NotEq(t, out1, out2)
}
func TestACLAuthMethod_Stub(t *testing.T) {
ci.Parallel(t)
maxTokenTTL, _ := time.ParseDuration("3600s")
am := ACLAuthMethod{
Name: fmt.Sprintf("acl-auth-method-%s", uuid.Short()),
Type: "acl-auth-mock-type",
TokenLocality: "locality",
MaxTokenTTL: maxTokenTTL,
Default: true,
Config: &ACLAuthMethodConfig{
OIDCDiscoveryURL: "http://example.com",
OIDCClientID: "mock",
OIDCClientSecret: "very secret secret",
BoundAudiences: []string{"audience1", "audience2"},
AllowedRedirectURIs: []string{"foo", "bar"},
DiscoveryCaPem: []string{"foo"},
SigningAlgs: []string{"bar"},
ClaimMappings: map[string]string{"foo": "bar"},
ListClaimMappings: map[string]string{"foo": "bar"},
},
CreateTime: time.Now().UTC(),
CreateIndex: 10,
ModifyIndex: 10,
}
am.SetHash()
must.Eq(t, am.Stub(), &ACLAuthMethodStub{
Name: am.Name,
Type: am.Type,
Default: am.Default,
Hash: am.Hash,
CreateIndex: am.CreateIndex,
ModifyIndex: am.ModifyIndex,
})
nilAuthMethod := &ACLAuthMethod{}
must.Eq(t, nilAuthMethod.Stub(), &ACLAuthMethodStub{})
}
func TestACLAuthMethod_Equal(t *testing.T) {
ci.Parallel(t)
maxTokenTTL, _ := time.ParseDuration("3600s")
am1 := &ACLAuthMethod{
Name: fmt.Sprintf("acl-auth-method-%s", uuid.Short()),
Type: "acl-auth-mock-type",
TokenLocality: "locality",
MaxTokenTTL: maxTokenTTL,
Default: true,
Config: &ACLAuthMethodConfig{
OIDCDiscoveryURL: "http://example.com",
OIDCClientID: "mock",
OIDCClientSecret: "very secret secret",
BoundAudiences: []string{"audience1", "audience2"},
AllowedRedirectURIs: []string{"foo", "bar"},
DiscoveryCaPem: []string{"foo"},
SigningAlgs: []string{"bar"},
ClaimMappings: map[string]string{"foo": "bar"},
ListClaimMappings: map[string]string{"foo": "bar"},
},
CreateTime: time.Now().UTC(),
CreateIndex: 10,
ModifyIndex: 10,
}
am1.SetHash()
// am2 differs from am1 by 1 nested conf field
am2 := am1.Copy()
am2.Config.OIDCClientID = "mock2"
am2.SetHash()
tests := []struct {
name string
method1 *ACLAuthMethod
method2 *ACLAuthMethod
want bool
}{
{"one nil", am1, &ACLAuthMethod{}, false},
{"both nil", &ACLAuthMethod{}, &ACLAuthMethod{}, true},
{"one is different than the other", am1, am2, false},
{"equal", am1, am1.Copy(), true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.method1.Equal(tt.method2)
must.Eq(t, got, tt.want, must.Sprintf(
"ACLAuthMethod.Equal() got %v, want %v, test case: %s", got, tt.want, tt.name))
})
}
}
func TestACLAuthMethod_Copy(t *testing.T) {
ci.Parallel(t)
maxTokenTTL, _ := time.ParseDuration("3600s")
am1 := &ACLAuthMethod{
Name: fmt.Sprintf("acl-auth-method-%s", uuid.Short()),
Type: "acl-auth-mock-type",
TokenLocality: "locality",
MaxTokenTTL: maxTokenTTL,
Default: true,
Config: &ACLAuthMethodConfig{
OIDCDiscoveryURL: "http://example.com",
OIDCClientID: "mock",
OIDCClientSecret: "very secret secret",
BoundAudiences: []string{"audience1", "audience2"},
AllowedRedirectURIs: []string{"foo", "bar"},
DiscoveryCaPem: []string{"foo"},
SigningAlgs: []string{"bar"},
ClaimMappings: map[string]string{"foo": "bar"},
ListClaimMappings: map[string]string{"foo": "bar"},
},
CreateTime: time.Now().UTC(),
CreateIndex: 10,
ModifyIndex: 10,
}
am1.SetHash()
am2 := am1.Copy()
am2.SetHash()
must.Eq(t, am1, am2)
am3 := am1.Copy()
am3.Config.AllowedRedirectURIs = []string{"new", "urls"}
am3.SetHash()
must.NotEq(t, am1, am3)
}
func TestACLAuthMethod_Validate(t *testing.T) {
ci.Parallel(t)
goodTTL, _ := time.ParseDuration("3600s")
badTTL, _ := time.ParseDuration("3600h")
tests := []struct {
name string
method *ACLAuthMethod
wantErr bool
errContains string
}{
{
"valid method",
&ACLAuthMethod{
Name: "mock-auth-method",
Type: "OIDC",
TokenLocality: "local",
MaxTokenTTL: goodTTL,
},
false,
"",
},
{"invalid name", &ACLAuthMethod{Name: "is this name invalid?"}, true, "invalid name"},
{"invalid token locality", &ACLAuthMethod{TokenLocality: "regional"}, true, "invalid token locality"},
{"invalid type", &ACLAuthMethod{Type: "groovy"}, true, "invalid token type"},
{"invalid max ttl", &ACLAuthMethod{MaxTokenTTL: badTTL}, true, "invalid token type"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
minTTL, _ := time.ParseDuration("10s")
maxTTL, _ := time.ParseDuration("10h")
got := tt.method.Validate(minTTL, maxTTL)
if tt.wantErr {
must.Error(t, got, must.Sprintf(
"ACLAuthMethod.Validate() got error, didn't expect it; test case: %s", tt.name))
must.StrContains(t, got.Error(), tt.errContains, must.Sprintf(
"ACLAuthMethod.Validate() got %v error message, expected %v; test case: %s",
got, tt.errContains, tt.name))
} else {
must.NoError(t, got, must.Sprintf(
"ACLAuthMethod.Validate() expected an error but didn't get one; test case: %s", tt.name))
}
})
}
}
func TestACLAuthMethod_Merge(t *testing.T) {
ci.Parallel(t)
name := fmt.Sprintf("acl-auth-method-%s", uuid.Short())
maxTokenTTL, _ := time.ParseDuration("3600s")
am1 := &ACLAuthMethod{
Name: name,
TokenLocality: "global",
}
am2 := &ACLAuthMethod{
Name: name,
Type: "OIDC",
TokenLocality: "locality",
MaxTokenTTL: maxTokenTTL,
Default: true,
Config: &ACLAuthMethodConfig{
OIDCDiscoveryURL: "http://example.com",
OIDCClientID: "mock",
OIDCClientSecret: "very secret secret",
BoundAudiences: []string{"audience1", "audience2"},
AllowedRedirectURIs: []string{"foo", "bar"},
DiscoveryCaPem: []string{"foo"},
SigningAlgs: []string{"bar"},
ClaimMappings: map[string]string{"foo": "bar"},
ListClaimMappings: map[string]string{"foo": "bar"},
},
CreateTime: time.Now().UTC(),
CreateIndex: 10,
ModifyIndex: 10,
}
am1.Merge(am2)
must.Eq(t, am1.TokenLocality, "global")
minTTL, _ := time.ParseDuration("10s")
maxTTL, _ := time.ParseDuration("10h")
must.NoError(t, am1.Validate(minTTL, maxTTL))
}
func TestACLAuthMethodConfig_Copy(t *testing.T) {
ci.Parallel(t)
amc1 := &ACLAuthMethodConfig{
OIDCDiscoveryURL: "http://example.com",
OIDCClientID: "mock",
OIDCClientSecret: "very secret secret",
OIDCScopes: []string{"groups"},
BoundAudiences: []string{"audience1", "audience2"},
AllowedRedirectURIs: []string{"foo", "bar"},
DiscoveryCaPem: []string{"foo"},
SigningAlgs: []string{"bar"},
ClaimMappings: map[string]string{"foo": "bar"},
ListClaimMappings: map[string]string{"foo": "bar"},
}
amc2 := amc1.Copy()
must.Eq(t, amc1, amc2)
amc3 := amc1.Copy()
amc3.AllowedRedirectURIs = []string{"new", "urls"}
must.NotEq(t, amc1, amc3)
}
func TestACLAuthMethod_Canonicalize(t *testing.T) {
now := time.Now().UTC()
tests := []struct {
name string
inputMethod *ACLAuthMethod
}{
{
"no create time or modify time set",
&ACLAuthMethod{},
},
{
"create time set to now, modify time not set",
&ACLAuthMethod{CreateTime: now},
},
{
"both create time and modify time set",
&ACLAuthMethod{CreateTime: now, ModifyTime: now.Add(time.Hour)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
existing := tt.inputMethod.Copy()
tt.inputMethod.Canonicalize()
if existing.CreateTime.IsZero() {
must.NotEq(t, time.Time{}, tt.inputMethod.CreateTime)
} else {
must.Eq(t, existing.CreateTime, tt.inputMethod.CreateTime)
}
if existing.ModifyTime.IsZero() {
must.NotEq(t, time.Time{}, tt.inputMethod.ModifyTime)
}
})
}
}
func TestACLAuthMethod_TokenLocalityIsGlobal(t *testing.T) {
ci.Parallel(t)
globalAuthMethod := &ACLAuthMethod{TokenLocality: "global"}
must.True(t, globalAuthMethod.TokenLocalityIsGlobal())
localAuthMethod := &ACLAuthMethod{TokenLocality: "local"}
must.False(t, localAuthMethod.TokenLocalityIsGlobal())
}
func TestACLBindingRule_Canonicalize(t *testing.T) {
ci.Parallel(t)
testCases := []struct {
name string
inputACLBindingRule *ACLBindingRule
}{
{
name: "new binding rule",
inputACLBindingRule: &ACLBindingRule{},
},
{
name: "existing binding rule",
inputACLBindingRule: &ACLBindingRule{
ID: "some-random-uuid",
CreateTime: time.Now().UTC(),
ModifyTime: time.Now().UTC(),
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Make a copy, so we can compare the modified object to it.
copiedBindingRule := tc.inputACLBindingRule.Copy()
tc.inputACLBindingRule.Canonicalize()
if copiedBindingRule.ID == "" {
must.NotEq(t, "", tc.inputACLBindingRule.ID)
must.NotEq(t, copiedBindingRule.CreateTime, tc.inputACLBindingRule.CreateTime)
must.NotEq(t, copiedBindingRule.ModifyTime, tc.inputACLBindingRule.ModifyTime)
} else {
must.Eq(t, copiedBindingRule.ID, tc.inputACLBindingRule.ID)
must.Eq(t, copiedBindingRule.CreateTime, tc.inputACLBindingRule.CreateTime)
must.NotEq(t, copiedBindingRule.ModifyTime, tc.inputACLBindingRule.ModifyTime)
}
})
}
}
func TestACLBindingRule_Validate(t *testing.T) {
ci.Parallel(t)
testCases := []struct {
name string
inputACLBindingRule *ACLBindingRule
expectedError error
}{
{
name: "valid policy type rule",
inputACLBindingRule: &ACLBindingRule{
Description: "some short description",
AuthMethod: "auth0",
Selector: "group_name in list.groups",
BindType: ACLBindingRuleBindTypePolicy,
BindName: "some-policy-name",
},
expectedError: nil,
},
{
name: "invalid policy type rule",
inputACLBindingRule: &ACLBindingRule{
Description: "some short description",
AuthMethod: "auth0",
Selector: "group_name in list.groups",
BindType: ACLBindingRuleBindTypePolicy,
BindName: "",
},
expectedError: &multierror.Error{
Errors: []error{
errors.New("bind name is missing"),
},
},
},
{
name: "valid role type rule",
inputACLBindingRule: &ACLBindingRule{
Description: "some short description",
AuthMethod: "auth0",
Selector: "group_name in list.groups",
BindType: ACLBindingRuleBindTypeRole,
BindName: "some-role-name",
},
expectedError: nil,
},
{
name: "invalid role type rule",
inputACLBindingRule: &ACLBindingRule{
Description: "some short description",
AuthMethod: "auth0",
Selector: "group_name in list.groups",
BindType: ACLBindingRuleBindTypeRole,
BindName: "",
},
expectedError: &multierror.Error{
Errors: []error{
errors.New("bind name is missing"),
},
},
},
{
name: "valid management type rule",
inputACLBindingRule: &ACLBindingRule{
Description: "some short description",
AuthMethod: "auth0",
Selector: "group_name in list.groups",
BindType: ACLBindingRuleBindTypeManagement,
BindName: "",
},
expectedError: nil,
},
{
name: "invalid management type rule",
inputACLBindingRule: &ACLBindingRule{
Description: "some short description",
AuthMethod: "auth0",
Selector: "group_name in list.groups",
BindType: ACLBindingRuleBindTypeManagement,
BindName: "some-name",
},
expectedError: &multierror.Error{
Errors: []error{
errors.New("bind name should be empty"),
},
},
},
{
name: "invalid selector",
inputACLBindingRule: &ACLBindingRule{
Description: "some short description",
AuthMethod: "auth0",
Selector: "group-name in list.groups",
BindType: ACLBindingRuleBindTypePolicy,
BindName: "some-policy-name",
},
expectedError: &multierror.Error{
Errors: []error{
errors.New("selector is invalid: 1:6 (5): no match found, expected: \"!=\", \".\", \"==\", \"[\", [ \\t\\r\\n] or [a-zA-Z0-9_/]"),
},
},
},
{
name: "invalid all",
inputACLBindingRule: &ACLBindingRule{
Description: uuid.Generate() + uuid.Generate() + uuid.Generate() +
uuid.Generate() + uuid.Generate() + uuid.Generate() +
uuid.Generate() + uuid.Generate(),
AuthMethod: "",
Selector: "group-name in list.groups",
BindType: "",
BindName: "",
},
expectedError: &multierror.Error{
Errors: []error{
errors.New("auth method is missing"),
errors.New("description longer than 256"),
errors.New("bind type is missing"),
errors.New("selector is invalid: 1:6 (5): no match found, expected: \"!=\", \".\", \"==\", \"[\", [ \\t\\r\\n] or [a-zA-Z0-9_/]"),
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
must.Eq(t, tc.expectedError, tc.inputACLBindingRule.Validate())
})
}
}
func TestACLBindingRule_Merge(t *testing.T) {
ci.Parallel(t)
id := uuid.Short()
br := &ACLBindingRule{
ID: id,
Description: "old description",
AuthMethod: "example-acl-auth-method",
BindType: "rule",
BindName: "bind name",
CreateTime: time.Now().UTC(),
CreateIndex: 10,
ModifyIndex: 10,
}
// make a description update
br_description_update := &ACLBindingRule{
ID: id,
Description: "new description",
}
br_description_update.Merge(br)
must.Eq(t, br_description_update.Description, "new description")
must.Eq(t, br_description_update.BindType, "rule")
}
func TestACLBindingRule_SetHash(t *testing.T) {
ci.Parallel(t)
bindingRule := &ACLBindingRule{
ID: uuid.Generate(),
AuthMethod: "okta",
}
out1 := bindingRule.SetHash()
must.NotNil(t, out1)
must.NotNil(t, bindingRule.Hash)
must.Eq(t, out1, bindingRule.Hash)
bindingRule.Description = "my lovely rule"
out2 := bindingRule.SetHash()
must.NotNil(t, out2)
must.NotNil(t, bindingRule.Hash)
must.Eq(t, out2, bindingRule.Hash)
must.NotEq(t, out1, out2)
}
func TestACLBindingRule_Equal(t *testing.T) {
ci.Parallel(t)
aclBindingRule1 := &ACLBindingRule{
ID: uuid.Short(),
Description: "mocked-acl-binding-rule",
AuthMethod: "auth0",
Selector: "engineering in list.roles",
BindType: "role-id",
BindName: "eng-ro",
CreateTime: time.Now().UTC(),
ModifyTime: time.Now().UTC(),
CreateIndex: 10,
ModifyIndex: 10,
}
aclBindingRule1.SetHash()
// Create a second binding rule, and modify this from the first.
aclBindingRule2 := aclBindingRule1.Copy()
aclBindingRule2.Description = ""
aclBindingRule2.SetHash()
testCases := []struct {
name string
method1 *ACLBindingRule
method2 *ACLBindingRule
want bool
}{
{"one nil", aclBindingRule1, &ACLBindingRule{}, false},
{"both nil", &ACLBindingRule{}, &ACLBindingRule{}, true},
{"not equal", aclBindingRule1, aclBindingRule2, false},
{"equal", aclBindingRule2, aclBindingRule2.Copy(), true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := tc.method1.Equal(tc.method2)
must.Eq(t, got, tc.want)
})
}
}
func TestACLBindingRule_Copy(t *testing.T) {
ci.Parallel(t)
aclBindingRule1 := &ACLBindingRule{
ID: uuid.Short(),
Description: "mocked-acl-binding-rule",
AuthMethod: "auth0",
Selector: "engineering in list.roles",
BindType: "role-id",
BindName: "eng-ro",
CreateTime: time.Now().UTC(),
ModifyTime: time.Now().UTC(),
CreateIndex: 10,
ModifyIndex: 10,
}
aclBindingRule1.SetHash()
aclBindingRule2 := aclBindingRule1.Copy()
must.Eq(t, aclBindingRule1, aclBindingRule2)
aclBindingRule3 := aclBindingRule1.Copy()
aclBindingRule3.Description = ""
aclBindingRule3.SetHash()
must.NotEq(t, aclBindingRule1, aclBindingRule3)
}
func TestACLBindingRule_Stub(t *testing.T) {
ci.Parallel(t)
aclBindingRule := ACLBindingRule{
ID: "some-uuid",
Description: "my-binding-rule",
AuthMethod: "auth0",
Selector: "some selector.pattern",
BindType: "role",
BindName: "some-role-id-or-name",
CreateTime: time.Now().UTC(),
ModifyTime: time.Now().UTC(),
CreateIndex: 1309,
ModifyIndex: 9031,
}
aclBindingRule.SetHash()
must.Eq(t, &ACLBindingRuleListStub{
ID: "some-uuid",
Description: "my-binding-rule",
AuthMethod: "auth0",
Hash: aclBindingRule.Hash,
CreateIndex: 1309,
ModifyIndex: 9031,
}, aclBindingRule.Stub())
}
func Test_ACLBindingRulesUpsertRequest(t *testing.T) {
ci.Parallel(t)
req := ACLBindingRulesUpsertRequest{}
require.False(t, req.IsRead())
}
func Test_ACLBindingRulesDeleteRequest(t *testing.T) {
ci.Parallel(t)
req := ACLBindingRulesDeleteRequest{}
require.False(t, req.IsRead())
}
func Test_ACLBindingRulesListRequest(t *testing.T) {
ci.Parallel(t)
req := ACLBindingRulesListRequest{}
require.True(t, req.IsRead())
}
func Test_ACLBindingRulesRequest(t *testing.T) {
ci.Parallel(t)
req := ACLBindingRulesRequest{}
require.True(t, req.IsRead())
}
func Test_ACLBindingRuleRequest(t *testing.T) {
ci.Parallel(t)
req := ACLBindingRuleRequest{}
require.True(t, req.IsRead())
}
func TestACLOIDCAuthURLRequest(t *testing.T) {
ci.Parallel(t)
req := &ACLOIDCAuthURLRequest{}
must.False(t, req.IsRead())
}
func TestACLOIDCAuthURLRequest_Validate(t *testing.T) {
ci.Parallel(t)
testRequest := &ACLOIDCAuthURLRequest{}
err := testRequest.Validate()
must.Error(t, err)
must.StrContains(t, err.Error(), "missing auth method name")
must.StrContains(t, err.Error(), "missing client nonce")
must.StrContains(t, err.Error(), "missing redirect URI")
}
func TestACLOIDCCompleteAuthRequest(t *testing.T) {
ci.Parallel(t)
req := &ACLOIDCCompleteAuthRequest{}
must.False(t, req.IsRead())
}
func TestACLOIDCCompleteAuthRequest_Validate(t *testing.T) {
ci.Parallel(t)
testRequest := &ACLOIDCCompleteAuthRequest{}
err := testRequest.Validate()
must.Error(t, err)
must.StrContains(t, err.Error(), "missing auth method name")
must.StrContains(t, err.Error(), "missing client nonce")
must.StrContains(t, err.Error(), "missing state")
must.StrContains(t, err.Error(), "missing code")
must.StrContains(t, err.Error(), "missing redirect URI")
}