consul/agent/structs/acl_test.go

704 lines
18 KiB
Go

package structs
import (
"fmt"
"strings"
"testing"
"github.com/hashicorp/consul/acl"
"github.com/stretchr/testify/require"
)
func TestStructs_ACLToken_PolicyIDs(t *testing.T) {
t.Parallel()
t.Run("Basic", func(t *testing.T) {
t.Parallel()
token := &ACLToken{
Policies: []ACLTokenPolicyLink{
ACLTokenPolicyLink{
ID: "one",
},
ACLTokenPolicyLink{
ID: "two",
},
ACLTokenPolicyLink{
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("Legacy Management", func(t *testing.T) {
t.Parallel()
a := &ACL{
ID: "root",
Type: ACLTokenTypeManagement,
Name: "management",
}
token := a.Convert()
policyIDs := token.PolicyIDs()
require.Len(t, policyIDs, 0)
embedded := token.EmbeddedPolicy()
require.NotNil(t, embedded)
require.Equal(t, ACLPolicyGlobalManagement, embedded.Rules)
})
t.Run("Legacy Management With Rules", func(t *testing.T) {
t.Parallel()
a := &ACL{
ID: "root",
Type: ACLTokenTypeManagement,
Name: "management",
Rules: "operator = \"write\"",
}
token := a.Convert()
policyIDs := token.PolicyIDs()
require.Len(t, policyIDs, 0)
embedded := token.EmbeddedPolicy()
require.NotNil(t, embedded)
require.Equal(t, ACLPolicyGlobalManagement, embedded.Rules)
})
t.Run("No Policies", func(t *testing.T) {
t.Parallel()
token := &ACLToken{}
policyIDs := token.PolicyIDs()
require.Len(t, policyIDs, 0)
})
}
func TestStructs_ACLToken_EmbeddedPolicy(t *testing.T) {
t.Parallel()
t.Run("No Rules", func(t *testing.T) {
t.Parallel()
token := &ACLToken{}
require.Nil(t, token.EmbeddedPolicy())
})
t.Run("Legacy Client", func(t *testing.T) {
t.Parallel()
// None of the other fields should be considered
token := &ACLToken{
Type: ACLTokenTypeClient,
Rules: `acl = "read"`,
}
policy := token.EmbeddedPolicy()
require.NotNil(t, policy)
require.NotEqual(t, "", policy.ID)
require.True(t, strings.HasPrefix(policy.Name, "legacy-policy-"))
require.Equal(t, token.Rules, policy.Rules)
require.Equal(t, policy.Syntax, acl.SyntaxLegacy)
require.NotNil(t, policy.Hash)
require.NotEqual(t, []byte{}, policy.Hash)
})
t.Run("Same Policy for Tokens with same Rules", func(t *testing.T) {
t.Parallel()
token1 := &ACLToken{
AccessorID: "f55b260c-5e05-418e-ab19-d421d1ab4b52",
SecretID: "b2165bac-7006-459b-8a72-7f549f0f06d6",
Description: "token 1",
Type: ACLTokenTypeClient,
Rules: `acl = "read"`,
}
token2 := &ACLToken{
AccessorID: "09d1c059-961a-46bd-a2e4-76adebe35fa5",
SecretID: "65e98e67-9b29-470c-8ffa-7c5a23cc67c8",
Description: "token 2",
Type: ACLTokenTypeClient,
Rules: `acl = "read"`,
}
policy1 := token1.EmbeddedPolicy()
policy2 := token2.EmbeddedPolicy()
require.Equal(t, policy1, policy2)
})
}
func TestStructs_ACLServiceIdentity_SyntheticPolicy(t *testing.T) {
t.Parallel()
for _, test := range []struct {
serviceName string
datacenters []string
expectRules string
}{
{"web", nil, `
service "web" {
policy = "write"
}
service "web-sidecar-proxy" {
policy = "write"
}
service_prefix "" {
policy = "read"
}
node_prefix "" {
policy = "read"
}`},
{"companion-cube-99", []string{"dc1", "dc2"}, `
service "companion-cube-99" {
policy = "write"
}
service "companion-cube-99-sidecar-proxy" {
policy = "write"
}
service_prefix "" {
policy = "read"
}
node_prefix "" {
policy = "read"
}`},
} {
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{
Syntax: acl.SyntaxCurrent,
Datacenters: test.datacenters,
Description: "synthetic policy",
Rules: test.expectRules,
}
got := svcid.SyntheticPolicy()
require.NotEmpty(t, got.ID)
require.True(t, strings.HasPrefix(got.Name, "synthetic-policy-"))
// strip irrelevant fields before equality
got.ID = ""
got.Name = ""
got.Hash = nil
require.Equal(t, expect, got)
})
}
}
func TestStructs_ACLToken_SetHash(t *testing.T) {
t.Parallel()
token := ACLToken{
AccessorID: "09d1c059-961a-46bd-a2e4-76adebe35fa5",
SecretID: "65e98e67-9b29-470c-8ffa-7c5a23cc67c8",
Description: "test",
Policies: []ACLTokenPolicyLink{
ACLTokenPolicyLink{
ID: "one",
},
ACLTokenPolicyLink{
ID: "two",
},
ACLTokenPolicyLink{
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) {
t.Parallel()
// estimated size here should
token := ACLToken{
AccessorID: "09d1c059-961a-46bd-a2e4-76adebe35fa5",
SecretID: "65e98e67-9b29-470c-8ffa-7c5a23cc67c8",
Description: "test",
Policies: []ACLTokenPolicyLink{
ACLTokenPolicyLink{
ID: "one",
},
ACLTokenPolicyLink{
ID: "two",
},
ACLTokenPolicyLink{
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.Parallel()
t.Run("Basic", func(t *testing.T) {
t.Parallel()
token := ACLToken{
AccessorID: "09d1c059-961a-46bd-a2e4-76adebe35fa5",
SecretID: "65e98e67-9b29-470c-8ffa-7c5a23cc67c8",
Description: "test",
Policies: []ACLTokenPolicyLink{
ACLTokenPolicyLink{
ID: "one",
},
ACLTokenPolicyLink{
ID: "two",
},
ACLTokenPolicyLink{
ID: "three",
},
},
}
stub := token.Stub()
require.Equal(t, token.AccessorID, stub.AccessorID)
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)
require.False(t, stub.Legacy)
})
t.Run("Legacy", func(t *testing.T) {
t.Parallel()
token := ACLToken{
AccessorID: "09d1c059-961a-46bd-a2e4-76adebe35fa5",
SecretID: "65e98e67-9b29-470c-8ffa-7c5a23cc67c8",
Description: "test",
Type: ACLTokenTypeClient,
Rules: `key "" { policy = "read" }`,
}
stub := token.Stub()
require.Equal(t, token.AccessorID, stub.AccessorID)
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)
require.True(t, stub.Legacy)
})
}
func TestStructs_ACLTokens_Sort(t *testing.T) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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" }`,
Syntax: acl.SyntaxCurrent,
RaftIndex: RaftIndex{
CreateIndex: 1,
ModifyIndex: 2,
},
},
&ACLPolicy{
ID: "b35541f0-a88a-48da-bc66-43553c60b628",
Name: "policy2",
Description: "policy2",
Rules: `agent_prefix "" { policy = "read" }`,
Syntax: acl.SyntaxCurrent,
RaftIndex: RaftIndex{
CreateIndex: 3,
ModifyIndex: 4,
},
},
&ACLPolicy{
ID: "383abb79-94ca-46c6-89b7-8ecb69046de9",
Name: "policy3",
Description: "policy3",
Rules: `key_prefix "" { policy = "read" }`,
Syntax: acl.SyntaxCurrent,
RaftIndex: RaftIndex{
CreateIndex: 5,
ModifyIndex: 6,
},
},
&ACLPolicy{
ID: "8bf38965-95e5-4e86-9be7-f6070cc0708b",
Name: "policy4",
Description: "policy4",
Rules: `service_prefix "" { policy = "read" }`,
Syntax: acl.SyntaxCurrent,
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)
for i := range testPolicies {
require.Equal(t, testPolicies[i].ID, policies[i].ID)
require.Equal(t, testPolicies[i].ModifyIndex, policies[i].Revision)
}
})
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)
require.Equal(t, testPolicies[i].ID, entry.Policy.ID)
require.Equal(t, testPolicies[i].ModifyIndex, entry.Policy.Revision)
// set this to detect using from the cache next time
entry.Policy.Revision = 9999
}
})
t.Run("Cache Hits", func(t *testing.T) {
policies, err := testPolicies.resolveWithCache(cache, nil)
require.NoError(t, err)
require.Len(t, policies, 4)
for i := range testPolicies {
require.Equal(t, testPolicies[i].ID, policies[i].ID)
require.Equal(t, uint64(9999), policies[i].Revision)
}
})
}
func TestStructs_ACLPolicies_Compile(t *testing.T) {
t.Parallel()
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" }`,
Syntax: acl.SyntaxCurrent,
RaftIndex: RaftIndex{
CreateIndex: 1,
ModifyIndex: 2,
},
},
&ACLPolicy{
ID: "b35541f0-a88a-48da-bc66-43553c60b628",
Name: "policy2",
Description: "policy2",
Rules: `agent_prefix "" { policy = "read" }`,
Syntax: acl.SyntaxCurrent,
RaftIndex: RaftIndex{
CreateIndex: 3,
ModifyIndex: 4,
},
},
&ACLPolicy{
ID: "383abb79-94ca-46c6-89b7-8ecb69046de9",
Name: "policy3",
Description: "policy3",
Rules: `key_prefix "" { policy = "read" }`,
Syntax: acl.SyntaxCurrent,
RaftIndex: RaftIndex{
CreateIndex: 5,
ModifyIndex: 6,
},
},
&ACLPolicy{
ID: "8bf38965-95e5-4e86-9be7-f6070cc0708b",
Name: "policy4",
Description: "policy4",
Rules: `service_prefix "" { policy = "read" }`,
Syntax: acl.SyntaxCurrent,
RaftIndex: RaftIndex{
CreateIndex: 7,
ModifyIndex: 8,
},
},
}
t.Run("Cache Miss", func(t *testing.T) {
authz, err := testPolicies.Compile(acl.DenyAll(), 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.Deny, 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.Deny, 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(acl.DenyAll(), cache, nil)
require.NoError(t, err)
require.NotNil(t, authz)
// we reset the Authorizer in the cache so now everything should be denied
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))
})
}