You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
consul/agent/structs/acl_test.go

572 lines
15 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package structs
import (
"fmt"
"strings"
"testing"
"github.com/hashicorp/consul/acl"
"github.com/stretchr/testify/require"
)
func TestStructs_ACLToken_PolicyIDs(t *testing.T) {
t.Run("Basic", func(t *testing.T) {
token := &ACLToken{
Policies: []ACLTokenPolicyLink{
{
ID: "one",
},
{
ID: "two",
},
{
ID: "three",
},
},
}
policyIDs := token.PolicyIDs()
require.Len(t, policyIDs, 3)
require.Equal(t, "one", policyIDs[0])
require.Equal(t, "two", policyIDs[1])
require.Equal(t, "three", policyIDs[2])
})
t.Run("No Policies", func(t *testing.T) {
token := &ACLToken{}
policyIDs := token.PolicyIDs()
require.Len(t, policyIDs, 0)
})
}
func TestStructs_ACLServiceIdentity_SyntheticPolicy(t *testing.T) {
cases := []struct {
serviceName string
datacenters []string
expectRules string
}{
{"web", nil, aclServiceIdentityRules("web", nil)},
{"companion-cube-99", []string{"dc1", "dc2"}, aclServiceIdentityRules("companion-cube-99", nil)},
}
for _, test := range cases {
name := test.serviceName
if len(test.datacenters) > 0 {
name += " [" + strings.Join(test.datacenters, ", ") + "]"
}
t.Run(name, func(t *testing.T) {
svcid := &ACLServiceIdentity{
ServiceName: test.serviceName,
Datacenters: test.datacenters,
}
expect := &ACLPolicy{
Datacenters: test.datacenters,
Rules: test.expectRules,
}
got := svcid.SyntheticPolicy(nil)
require.NotEmpty(t, got.ID)
require.True(t, strings.HasPrefix(got.Name, "synthetic-policy-"))
// strip irrelevant fields before equality
got.ID = ""
got.Name = ""
got.Description = ""
got.Hash = nil
require.Equal(t, expect, got)
})
}
}
func TestStructs_ACLServiceIdentities_Deduplicate(t *testing.T) {
identities := ACLServiceIdentities{
{ServiceName: "web", Datacenters: []string{"dc1"}},
{ServiceName: "web", Datacenters: []string{"dc2"}},
{ServiceName: "db", Datacenters: []string{"dc3"}},
}
require.ElementsMatch(t, ACLServiceIdentities{
{ServiceName: "web", Datacenters: []string{"dc1", "dc2"}},
{ServiceName: "db", Datacenters: []string{"dc3"}},
}, identities.Deduplicate())
require.Len(t, identities, 3, "original slice shouldn't have been mutated")
}
func TestStructs_ACLNodeIdentities_Deduplicate(t *testing.T) {
identities := ACLNodeIdentities{
{NodeName: "web", Datacenter: "dc1"},
{NodeName: "web", Datacenter: "dc2"},
{NodeName: "web", Datacenter: "dc1"},
}
require.Equal(t, ACLNodeIdentities{
{NodeName: "web", Datacenter: "dc1"},
{NodeName: "web", Datacenter: "dc2"},
}, identities.Deduplicate())
require.Len(t, identities, 3, "original slice shouldn't have been mutated")
}
func TestStructs_ACLToken_SetHash(t *testing.T) {
token := ACLToken{
AccessorID: "09d1c059-961a-46bd-a2e4-76adebe35fa5",
SecretID: "65e98e67-9b29-470c-8ffa-7c5a23cc67c8",
Description: "test",
Policies: []ACLTokenPolicyLink{
{
ID: "one",
},
{
ID: "two",
},
{
ID: "three",
},
},
}
t.Run("Nil Hash - Generate", func(t *testing.T) {
require.Nil(t, token.Hash)
h := token.SetHash(false)
require.NotNil(t, h)
require.NotEqual(t, []byte{}, h)
require.Equal(t, h, token.Hash)
})
t.Run("Hash Set - Dont Generate", func(t *testing.T) {
original := token.Hash
h := token.SetHash(false)
require.Equal(t, original, h)
token.Description = "changed"
h = token.SetHash(false)
require.Equal(t, original, h)
})
t.Run("Hash Set - Generate", func(t *testing.T) {
original := token.Hash
h := token.SetHash(true)
require.NotEqual(t, original, h)
})
}
func TestStructs_ACLToken_EstimateSize(t *testing.T) {
// estimated size here should
token := ACLToken{
AccessorID: "09d1c059-961a-46bd-a2e4-76adebe35fa5",
SecretID: "65e98e67-9b29-470c-8ffa-7c5a23cc67c8",
Description: "test",
Policies: []ACLTokenPolicyLink{
{
ID: "one",
},
{
ID: "two",
},
{
ID: "three",
},
},
}
// this test is very contrived. Basically just tests that the
// math is okay and returns the value.
require.Equal(t, 128, token.EstimateSize())
}
func TestStructs_ACLToken_Stub(t *testing.T) {
t.Run("Basic", func(t *testing.T) {
token := ACLToken{
AccessorID: "09d1c059-961a-46bd-a2e4-76adebe35fa5",
SecretID: "65e98e67-9b29-470c-8ffa-7c5a23cc67c8",
Description: "test",
Policies: []ACLTokenPolicyLink{
{
ID: "one",
},
{
ID: "two",
},
{
ID: "three",
},
},
}
stub := token.Stub()
require.Equal(t, token.AccessorID, stub.AccessorID)
require.Equal(t, token.SecretID, stub.SecretID)
require.Equal(t, token.Description, stub.Description)
require.Equal(t, token.Policies, stub.Policies)
require.Equal(t, token.Local, stub.Local)
require.Equal(t, token.CreateTime, stub.CreateTime)
require.Equal(t, token.Hash, stub.Hash)
require.Equal(t, token.CreateIndex, stub.CreateIndex)
require.Equal(t, token.ModifyIndex, stub.ModifyIndex)
})
}
func TestStructs_ACLTokens_Sort(t *testing.T) {
tokens := ACLTokens{
&ACLToken{
AccessorID: "9db509a9-c809-48c1-895d-99f845b7a9d5",
},
&ACLToken{
AccessorID: "6bd01084-1695-43b8-898d-b2dd7874754d",
},
&ACLToken{
AccessorID: "614a4cef-9149-4271-b878-7edb1ad661f8",
},
&ACLToken{
AccessorID: "c9dd9980-8d54-472f-9e5e-74c02143e1f4",
},
}
tokens.Sort()
require.Equal(t, tokens[0].AccessorID, "614a4cef-9149-4271-b878-7edb1ad661f8")
require.Equal(t, tokens[1].AccessorID, "6bd01084-1695-43b8-898d-b2dd7874754d")
require.Equal(t, tokens[2].AccessorID, "9db509a9-c809-48c1-895d-99f845b7a9d5")
require.Equal(t, tokens[3].AccessorID, "c9dd9980-8d54-472f-9e5e-74c02143e1f4")
}
func TestStructs_ACLTokenListStubs_Sort(t *testing.T) {
tokens := ACLTokenListStubs{
&ACLTokenListStub{
AccessorID: "9db509a9-c809-48c1-895d-99f845b7a9d5",
},
&ACLTokenListStub{
AccessorID: "6bd01084-1695-43b8-898d-b2dd7874754d",
},
&ACLTokenListStub{
AccessorID: "614a4cef-9149-4271-b878-7edb1ad661f8",
},
&ACLTokenListStub{
AccessorID: "c9dd9980-8d54-472f-9e5e-74c02143e1f4",
},
}
tokens.Sort()
require.Equal(t, tokens[0].AccessorID, "614a4cef-9149-4271-b878-7edb1ad661f8")
require.Equal(t, tokens[1].AccessorID, "6bd01084-1695-43b8-898d-b2dd7874754d")
require.Equal(t, tokens[2].AccessorID, "9db509a9-c809-48c1-895d-99f845b7a9d5")
require.Equal(t, tokens[3].AccessorID, "c9dd9980-8d54-472f-9e5e-74c02143e1f4")
}
func TestStructs_ACLPolicy_Stub(t *testing.T) {
policy := &ACLPolicy{
ID: "09d1c059-961a-46bd-a2e4-76adebe35fa5",
Name: "test",
Description: "test",
Rules: `acl = "read"`,
}
stub := policy.Stub()
require.Equal(t, policy.ID, stub.ID)
require.Equal(t, policy.Name, stub.Name)
require.Equal(t, policy.Description, stub.Description)
require.Equal(t, policy.Datacenters, stub.Datacenters)
require.Equal(t, policy.Hash, stub.Hash)
require.Equal(t, policy.CreateIndex, stub.CreateIndex)
require.Equal(t, policy.ModifyIndex, stub.ModifyIndex)
}
func TestStructs_ACLPolicy_SetHash(t *testing.T) {
policy := &ACLPolicy{
ID: "09d1c059-961a-46bd-a2e4-76adebe35fa5",
Name: "test",
Description: "test",
Rules: `acl = "read"`,
}
t.Run("Nil Hash - Generate", func(t *testing.T) {
require.Nil(t, policy.Hash)
h := policy.SetHash(false)
require.NotNil(t, h)
require.NotEqual(t, []byte{}, h)
require.Equal(t, h, policy.Hash)
})
t.Run("Hash Set - Dont Generate", func(t *testing.T) {
original := policy.Hash
h := policy.SetHash(false)
require.Equal(t, original, h)
policy.Description = "changed"
h = policy.SetHash(false)
require.Equal(t, original, h)
})
t.Run("Hash Set - Generate", func(t *testing.T) {
original := policy.Hash
h := policy.SetHash(true)
require.NotEqual(t, original, h)
})
}
func TestStructs_ACLPolicy_EstimateSize(t *testing.T) {
policy := ACLPolicy{
ID: "09d1c059-961a-46bd-a2e4-76adebe35fa5",
Name: "test",
Description: "test",
Rules: `acl = "read"`,
}
// this test is very contrived. Basically just tests that the
// math is okay and returns the value.
require.Equal(t, 84, policy.EstimateSize())
policy.Datacenters = []string{"dc1", "dc2"}
require.Equal(t, 90, policy.EstimateSize())
}
func TestStructs_ACLPolicies_Sort(t *testing.T) {
policies := ACLPolicies{
&ACLPolicy{
ID: "9db509a9-c809-48c1-895d-99f845b7a9d5",
},
&ACLPolicy{
ID: "6bd01084-1695-43b8-898d-b2dd7874754d",
},
&ACLPolicy{
ID: "614a4cef-9149-4271-b878-7edb1ad661f8",
},
&ACLPolicy{
ID: "c9dd9980-8d54-472f-9e5e-74c02143e1f4",
},
}
policies.Sort()
require.Equal(t, policies[0].ID, "614a4cef-9149-4271-b878-7edb1ad661f8")
require.Equal(t, policies[1].ID, "6bd01084-1695-43b8-898d-b2dd7874754d")
require.Equal(t, policies[2].ID, "9db509a9-c809-48c1-895d-99f845b7a9d5")
require.Equal(t, policies[3].ID, "c9dd9980-8d54-472f-9e5e-74c02143e1f4")
}
func TestStructs_ACLPolicyListStubs_Sort(t *testing.T) {
policies := ACLPolicyListStubs{
&ACLPolicyListStub{
ID: "9db509a9-c809-48c1-895d-99f845b7a9d5",
},
&ACLPolicyListStub{
ID: "6bd01084-1695-43b8-898d-b2dd7874754d",
},
&ACLPolicyListStub{
ID: "614a4cef-9149-4271-b878-7edb1ad661f8",
},
&ACLPolicyListStub{
ID: "c9dd9980-8d54-472f-9e5e-74c02143e1f4",
},
}
policies.Sort()
require.Equal(t, policies[0].ID, "614a4cef-9149-4271-b878-7edb1ad661f8")
require.Equal(t, policies[1].ID, "6bd01084-1695-43b8-898d-b2dd7874754d")
require.Equal(t, policies[2].ID, "9db509a9-c809-48c1-895d-99f845b7a9d5")
require.Equal(t, policies[3].ID, "c9dd9980-8d54-472f-9e5e-74c02143e1f4")
}
func TestStructs_ACLPolicies_resolveWithCache(t *testing.T) {
config := ACLCachesConfig{
Identities: 0,
Policies: 0,
ParsedPolicies: 4,
Authorizers: 0,
Roles: 0,
}
cache, err := NewACLCaches(&config)
require.NoError(t, err)
testPolicies := ACLPolicies{
&ACLPolicy{
ID: "5d5653a1-2c2b-4b36-b083-fc9f1398eb7b",
Name: "policy1",
Description: "policy1",
Rules: `node_prefix "" { policy = "read", policy = "read", },`,
RaftIndex: RaftIndex{
CreateIndex: 1,
ModifyIndex: 2,
},
},
&ACLPolicy{
ID: "b35541f0-a88a-48da-bc66-43553c60b628",
Name: "policy2",
Description: "policy2",
Rules: `agent_prefix "" { policy = "read" } `,
RaftIndex: RaftIndex{
CreateIndex: 3,
ModifyIndex: 4,
},
},
&ACLPolicy{
ID: "383abb79-94ca-46c6-89b7-8ecb69046de9",
Name: "policy3",
Description: "policy3",
Rules: `key_prefix "" { policy = "read" }`,
RaftIndex: RaftIndex{
CreateIndex: 5,
ModifyIndex: 6,
},
},
&ACLPolicy{
ID: "8bf38965-95e5-4e86-9be7-f6070cc0708b",
Name: "policy4",
Description: "policy4",
//test should still pass even with the duplicate key since its resolving the cache
Rules: `service_prefix "" { policy = "read" policy = "read" }`,
RaftIndex: RaftIndex{
CreateIndex: 7,
ModifyIndex: 8,
},
},
}
t.Run("Cache Misses", func(t *testing.T) {
policies, err := testPolicies.resolveWithCache(cache, nil)
require.NoError(t, err)
require.Len(t, policies, 4)
require.Len(t, policies[0].NodePrefixes, 1)
require.Len(t, policies[1].AgentPrefixes, 1)
require.Len(t, policies[2].KeyPrefixes, 1)
require.Len(t, policies[3].ServicePrefixes, 1)
})
t.Run("Check Cache", func(t *testing.T) {
for i := range testPolicies {
entry := cache.GetParsedPolicy(fmt.Sprintf("%x", testPolicies[i].Hash))
require.NotNil(t, entry)
// set this to detect using from the cache next time
testPolicies[i].Rules = "invalid"
}
})
t.Run("Cache Hits", func(t *testing.T) {
policies, err := testPolicies.resolveWithCache(cache, nil)
require.NoError(t, err)
require.Len(t, policies, 4)
require.Len(t, policies[0].NodePrefixes, 1)
require.Len(t, policies[1].AgentPrefixes, 1)
require.Len(t, policies[2].KeyPrefixes, 1)
require.Len(t, policies[3].ServicePrefixes, 1)
})
}
func TestStructs_ACLPolicies_Compile(t *testing.T) {
config := ACLCachesConfig{
Identities: 0,
Policies: 0,
ParsedPolicies: 4,
Authorizers: 2,
Roles: 0,
}
cache, err := NewACLCaches(&config)
require.NoError(t, err)
testPolicies := ACLPolicies{
&ACLPolicy{
ID: "5d5653a1-2c2b-4b36-b083-fc9f1398eb7b",
Name: "policy1",
Description: "policy1",
Rules: `node_prefix "" { policy = "read" }`,
RaftIndex: RaftIndex{
CreateIndex: 1,
ModifyIndex: 2,
},
},
&ACLPolicy{
ID: "b35541f0-a88a-48da-bc66-43553c60b628",
Name: "policy2",
Description: "policy2",
Rules: `agent_prefix "" { policy = "read" }`,
RaftIndex: RaftIndex{
CreateIndex: 3,
ModifyIndex: 4,
},
},
&ACLPolicy{
ID: "383abb79-94ca-46c6-89b7-8ecb69046de9",
Name: "policy3",
Description: "policy3",
Rules: `key_prefix "" { policy = "read" }`,
RaftIndex: RaftIndex{
CreateIndex: 5,
ModifyIndex: 6,
},
},
&ACLPolicy{
ID: "8bf38965-95e5-4e86-9be7-f6070cc0708b",
Name: "policy4",
Description: "policy4",
Rules: `service_prefix "" { policy = "read" }`,
RaftIndex: RaftIndex{
CreateIndex: 7,
ModifyIndex: 8,
},
},
}
t.Run("Cache Miss", func(t *testing.T) {
authz, err := testPolicies.Compile(cache, nil)
require.NoError(t, err)
require.NotNil(t, authz)
require.Equal(t, acl.Allow, authz.NodeRead("foo", nil))
require.Equal(t, acl.Allow, authz.AgentRead("foo", nil))
require.Equal(t, acl.Allow, authz.KeyRead("foo", nil))
require.Equal(t, acl.Allow, authz.ServiceRead("foo", nil))
require.Equal(t, acl.Default, authz.ACLRead(nil))
})
t.Run("Check Cache", func(t *testing.T) {
entry := cache.GetAuthorizer(testPolicies.HashKey())
require.NotNil(t, entry)
authz := entry.Authorizer
require.NotNil(t, authz)
require.Equal(t, acl.Allow, authz.NodeRead("foo", nil))
require.Equal(t, acl.Allow, authz.AgentRead("foo", nil))
require.Equal(t, acl.Allow, authz.KeyRead("foo", nil))
require.Equal(t, acl.Allow, authz.ServiceRead("foo", nil))
require.Equal(t, acl.Default, authz.ACLRead(nil))
// setup the cache for the next test
cache.PutAuthorizer(testPolicies.HashKey(), acl.DenyAll())
})
t.Run("Cache Hit", func(t *testing.T) {
authz, err := testPolicies.Compile(cache, nil)
require.NoError(t, err)
require.NotNil(t, authz)
// we reset the Authorizer in the cache so now everything should be defaulted
require.Equal(t, acl.Deny, authz.NodeRead("foo", nil))
require.Equal(t, acl.Deny, authz.AgentRead("foo", nil))
require.Equal(t, acl.Deny, authz.KeyRead("foo", nil))
require.Equal(t, acl.Deny, authz.ServiceRead("foo", nil))
require.Equal(t, acl.Deny, authz.ACLRead(nil))
})
}