mirror of
https://github.com/status-im/consul.git
synced 2025-01-09 21:35:52 +00:00
5fb9df1640
* Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
536 lines
15 KiB
Go
536 lines
15 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package structs
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/hashicorp/consul/acl"
|
|
)
|
|
|
|
func TestIntention_ACLs(t *testing.T) {
|
|
type testCase struct {
|
|
intention Intention
|
|
rules string
|
|
read bool
|
|
write bool
|
|
}
|
|
|
|
cases := map[string]testCase{
|
|
"all-denied": {
|
|
intention: Intention{
|
|
SourceNS: "default",
|
|
SourceName: "web",
|
|
DestinationNS: "default",
|
|
DestinationName: "api",
|
|
},
|
|
read: false,
|
|
write: false,
|
|
},
|
|
"deny-write-read-dest": {
|
|
rules: `service "api" { policy = "deny" intentions = "read" }`,
|
|
intention: Intention{
|
|
SourceNS: "default",
|
|
SourceName: "web",
|
|
DestinationNS: "default",
|
|
DestinationName: "api",
|
|
},
|
|
read: true,
|
|
write: false,
|
|
},
|
|
"deny-write-read-source": {
|
|
rules: `service "web" { policy = "deny" intentions = "read" }`,
|
|
intention: Intention{
|
|
SourceNS: "default",
|
|
SourceName: "web",
|
|
DestinationNS: "default",
|
|
DestinationName: "api",
|
|
},
|
|
read: true,
|
|
write: false,
|
|
},
|
|
"allow-write-with-dest-write": {
|
|
rules: `service "api" { policy = "deny" intentions = "write" }`,
|
|
intention: Intention{
|
|
SourceNS: "default",
|
|
SourceName: "web",
|
|
DestinationNS: "default",
|
|
DestinationName: "api",
|
|
},
|
|
read: true,
|
|
write: true,
|
|
},
|
|
"deny-write-with-source-write": {
|
|
rules: `service "web" { policy = "deny" intentions = "write" }`,
|
|
intention: Intention{
|
|
SourceNS: "default",
|
|
SourceName: "web",
|
|
DestinationNS: "default",
|
|
DestinationName: "api",
|
|
},
|
|
read: true,
|
|
write: false,
|
|
},
|
|
"deny-wildcard-write-allow-read": {
|
|
rules: `service "*" { policy = "deny" intentions = "write" }`,
|
|
intention: Intention{
|
|
SourceNS: "default",
|
|
SourceName: "*",
|
|
DestinationNS: "default",
|
|
DestinationName: "*",
|
|
},
|
|
// technically having been granted read/write on any intention will allow
|
|
// read access for this rule
|
|
read: true,
|
|
write: false,
|
|
},
|
|
"allow-wildcard-write": {
|
|
rules: `service_prefix "" { policy = "deny" intentions = "write" }`,
|
|
intention: Intention{
|
|
SourceNS: "default",
|
|
SourceName: "*",
|
|
DestinationNS: "default",
|
|
DestinationName: "*",
|
|
},
|
|
read: true,
|
|
write: true,
|
|
},
|
|
"allow-wildcard-read": {
|
|
rules: `service "foo" { policy = "deny" intentions = "read" }`,
|
|
intention: Intention{
|
|
SourceNS: "default",
|
|
SourceName: "*",
|
|
DestinationNS: "default",
|
|
DestinationName: "*",
|
|
},
|
|
read: true,
|
|
write: false,
|
|
},
|
|
}
|
|
|
|
config := acl.Config{
|
|
WildcardName: WildcardSpecifier,
|
|
}
|
|
|
|
for name, tcase := range cases {
|
|
t.Run(name, func(t *testing.T) {
|
|
authz, err := acl.NewAuthorizerFromRules(tcase.rules, &config, nil)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, tcase.read, tcase.intention.CanRead(authz))
|
|
require.Equal(t, tcase.write, tcase.intention.CanWrite(authz))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIntentionValidate(t *testing.T) {
|
|
cases := []struct {
|
|
Name string
|
|
Modify func(*Intention)
|
|
Err string
|
|
}{
|
|
{
|
|
"long description",
|
|
func(x *Intention) {
|
|
x.Description = strings.Repeat("x", metaValueMaxLength+1)
|
|
},
|
|
"description exceeds",
|
|
},
|
|
|
|
{
|
|
"no action set",
|
|
func(x *Intention) { x.Action = "" },
|
|
"action must be set",
|
|
},
|
|
|
|
{
|
|
"no SourceNS",
|
|
func(x *Intention) { x.SourceNS = "" },
|
|
"SourceNS must be set",
|
|
},
|
|
|
|
{
|
|
"no SourceName",
|
|
func(x *Intention) { x.SourceName = "" },
|
|
"SourceName must be set",
|
|
},
|
|
|
|
{
|
|
"no DestinationNS",
|
|
func(x *Intention) { x.DestinationNS = "" },
|
|
"DestinationNS must be set",
|
|
},
|
|
|
|
{
|
|
"no DestinationName",
|
|
func(x *Intention) { x.DestinationName = "" },
|
|
"DestinationName must be set",
|
|
},
|
|
|
|
{
|
|
"SourceNS partial wildcard",
|
|
func(x *Intention) { x.SourceNS = "foo*" },
|
|
"partial value",
|
|
},
|
|
|
|
{
|
|
"SourceName partial wildcard",
|
|
func(x *Intention) { x.SourceName = "foo*" },
|
|
"partial value",
|
|
},
|
|
|
|
{
|
|
"SourceName exact following wildcard",
|
|
func(x *Intention) {
|
|
x.SourceNS = "*"
|
|
x.SourceName = "foo"
|
|
},
|
|
"follow wildcard",
|
|
},
|
|
|
|
{
|
|
"DestinationNS partial wildcard",
|
|
func(x *Intention) { x.DestinationNS = "foo*" },
|
|
"partial value",
|
|
},
|
|
|
|
{
|
|
"DestinationName partial wildcard",
|
|
func(x *Intention) { x.DestinationName = "foo*" },
|
|
"partial value",
|
|
},
|
|
|
|
{
|
|
"DestinationName exact following wildcard",
|
|
func(x *Intention) {
|
|
x.DestinationNS = "*"
|
|
x.DestinationName = "foo"
|
|
},
|
|
"follow wildcard",
|
|
},
|
|
|
|
{
|
|
"SourceType is not set",
|
|
func(x *Intention) { x.SourceType = "" },
|
|
"SourceType must",
|
|
},
|
|
|
|
{
|
|
"SourceType is other",
|
|
func(x *Intention) { x.SourceType = IntentionSourceType("other") },
|
|
"SourceType must",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
ixn := TestIntention(t)
|
|
tc.Modify(ixn)
|
|
|
|
err := ixn.Validate()
|
|
assert.Equal(t, err != nil, tc.Err != "", err)
|
|
if err == nil {
|
|
return
|
|
}
|
|
|
|
assert.Contains(t, strings.ToLower(err.Error()), strings.ToLower(tc.Err))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIntentionPrecedenceSorter(t *testing.T) {
|
|
type fields struct {
|
|
SrcSamenessGroup string
|
|
SrcPeer string
|
|
SrcNS string
|
|
SrcN string
|
|
DstNS string
|
|
DstN string
|
|
}
|
|
cases := []struct {
|
|
Name string
|
|
Input []fields
|
|
Expected []fields
|
|
}{
|
|
{
|
|
"exhaustive list",
|
|
[]fields{
|
|
// Sameness fields
|
|
{SrcSamenessGroup: "group", SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "*"},
|
|
{SrcSamenessGroup: "group", SrcNS: "*", SrcN: "*", DstNS: "*", DstN: "*"},
|
|
{SrcSamenessGroup: "group", SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "exact"},
|
|
{SrcSamenessGroup: "group", SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "exact"},
|
|
{SrcSamenessGroup: "group", SrcNS: "exact", SrcN: "exact", DstNS: "*", DstN: "*"},
|
|
{SrcSamenessGroup: "group", SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "exact"},
|
|
{SrcSamenessGroup: "group", SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "*"},
|
|
{SrcSamenessGroup: "group", SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "*"},
|
|
{SrcSamenessGroup: "group", SrcNS: "exact", SrcN: "*", DstNS: "*", DstN: "*"},
|
|
// Peer fields
|
|
{SrcPeer: "peer", SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "*"},
|
|
{SrcPeer: "peer", SrcNS: "*", SrcN: "*", DstNS: "*", DstN: "*"},
|
|
{SrcPeer: "peer", SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "exact"},
|
|
{SrcPeer: "peer", SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "exact"},
|
|
{SrcPeer: "peer", SrcNS: "exact", SrcN: "exact", DstNS: "*", DstN: "*"},
|
|
{SrcPeer: "peer", SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "exact"},
|
|
{SrcPeer: "peer", SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "*"},
|
|
{SrcPeer: "peer", SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "*"},
|
|
{SrcPeer: "peer", SrcNS: "exact", SrcN: "*", DstNS: "*", DstN: "*"},
|
|
|
|
{SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "*"},
|
|
{SrcNS: "*", SrcN: "*", DstNS: "*", DstN: "*"},
|
|
{SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "exact"},
|
|
{SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "exact"},
|
|
{SrcNS: "exact", SrcN: "exact", DstNS: "*", DstN: "*"},
|
|
{SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "exact"},
|
|
{SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "*"},
|
|
{SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "*"},
|
|
{SrcNS: "exact", SrcN: "*", DstNS: "*", DstN: "*"},
|
|
},
|
|
[]fields{
|
|
{SrcPeer: "", SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "exact"},
|
|
{SrcPeer: "peer", SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "exact"},
|
|
{SrcSamenessGroup: "group", SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "exact"},
|
|
{SrcPeer: "", SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "exact"},
|
|
{SrcPeer: "peer", SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "exact"},
|
|
{SrcSamenessGroup: "group", SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "exact"},
|
|
{SrcPeer: "", SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "exact"},
|
|
{SrcPeer: "peer", SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "exact"},
|
|
{SrcSamenessGroup: "group", SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "exact"},
|
|
{SrcPeer: "", SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "*"},
|
|
{SrcPeer: "peer", SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "*"},
|
|
{SrcSamenessGroup: "group", SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "*"},
|
|
{SrcPeer: "", SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "*"},
|
|
{SrcPeer: "peer", SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "*"},
|
|
{SrcSamenessGroup: "group", SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "*"},
|
|
{SrcPeer: "", SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "*"},
|
|
{SrcPeer: "peer", SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "*"},
|
|
{SrcSamenessGroup: "group", SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "*"},
|
|
{SrcPeer: "", SrcNS: "exact", SrcN: "exact", DstNS: "*", DstN: "*"},
|
|
{SrcPeer: "peer", SrcNS: "exact", SrcN: "exact", DstNS: "*", DstN: "*"},
|
|
{SrcSamenessGroup: "group", SrcNS: "exact", SrcN: "exact", DstNS: "*", DstN: "*"},
|
|
{SrcPeer: "", SrcNS: "exact", SrcN: "*", DstNS: "*", DstN: "*"},
|
|
{SrcPeer: "peer", SrcNS: "exact", SrcN: "*", DstNS: "*", DstN: "*"},
|
|
{SrcSamenessGroup: "group", SrcNS: "exact", SrcN: "*", DstNS: "*", DstN: "*"},
|
|
{SrcPeer: "", SrcNS: "*", SrcN: "*", DstNS: "*", DstN: "*"},
|
|
{SrcPeer: "peer", SrcNS: "*", SrcN: "*", DstNS: "*", DstN: "*"},
|
|
{SrcSamenessGroup: "group", SrcNS: "*", SrcN: "*", DstNS: "*", DstN: "*"},
|
|
},
|
|
},
|
|
{
|
|
"tiebreak deterministically",
|
|
[]fields{
|
|
{SrcNS: "a", SrcN: "*", DstNS: "a", DstN: "b"},
|
|
{SrcNS: "a", SrcN: "*", DstNS: "a", DstN: "a"},
|
|
{SrcNS: "b", SrcN: "a", DstNS: "a", DstN: "a"},
|
|
{SrcNS: "a", SrcN: "b", DstNS: "a", DstN: "a"},
|
|
{SrcNS: "a", SrcN: "a", DstNS: "b", DstN: "a"},
|
|
{SrcNS: "a", SrcN: "a", DstNS: "a", DstN: "b"},
|
|
{SrcNS: "a", SrcN: "a", DstNS: "a", DstN: "a"},
|
|
},
|
|
[]fields{
|
|
// Exact matches first in lexicographical order (arbitrary but
|
|
// deterministic)
|
|
{SrcNS: "a", SrcN: "a", DstNS: "a", DstN: "a"},
|
|
{SrcNS: "a", SrcN: "a", DstNS: "a", DstN: "b"},
|
|
{SrcNS: "a", SrcN: "a", DstNS: "b", DstN: "a"},
|
|
{SrcNS: "a", SrcN: "b", DstNS: "a", DstN: "a"},
|
|
{SrcNS: "b", SrcN: "a", DstNS: "a", DstN: "a"},
|
|
// Wildcards next, lexicographical
|
|
{SrcNS: "a", SrcN: "*", DstNS: "a", DstN: "a"},
|
|
{SrcNS: "a", SrcN: "*", DstNS: "a", DstN: "b"},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
|
|
var input Intentions
|
|
for _, v := range tc.Input {
|
|
input = append(input, &Intention{
|
|
SourceSamenessGroup: v.SrcSamenessGroup,
|
|
SourcePeer: v.SrcPeer,
|
|
SourceNS: v.SrcNS,
|
|
SourceName: v.SrcN,
|
|
DestinationNS: v.DstNS,
|
|
DestinationName: v.DstN,
|
|
})
|
|
}
|
|
|
|
// Set all the precedence values
|
|
for _, ixn := range input {
|
|
ixn.UpdatePrecedence()
|
|
}
|
|
|
|
// Sort
|
|
sort.Sort(IntentionPrecedenceSorter(input))
|
|
|
|
// Get back into a comparable form
|
|
var actual []fields
|
|
for _, v := range input {
|
|
actual = append(actual, fields{
|
|
SrcSamenessGroup: v.SourceSamenessGroup,
|
|
SrcPeer: v.SourcePeer,
|
|
SrcNS: v.SourceNS,
|
|
SrcN: v.SourceName,
|
|
DstNS: v.DestinationNS,
|
|
DstN: v.DestinationName,
|
|
})
|
|
}
|
|
assert.Equal(t, tc.Expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIntention_SetHash(t *testing.T) {
|
|
i := Intention{
|
|
ID: "the-id",
|
|
Description: "the-description",
|
|
SourceNS: "source-ns",
|
|
SourceName: "source-name",
|
|
DestinationNS: "dest-ns",
|
|
DestinationName: "dest-name",
|
|
SourceType: "source-type",
|
|
Action: "action",
|
|
Precedence: 123,
|
|
Meta: map[string]string{
|
|
"meta1": "one",
|
|
"meta2": "two",
|
|
},
|
|
}
|
|
i.SetHash()
|
|
expected := []byte{
|
|
0x20, 0x89, 0x55, 0xdb, 0x69, 0x34, 0xce, 0x89, 0xd8, 0xb9, 0x2e, 0x3a,
|
|
0x85, 0xb6, 0xea, 0x43, 0xb2, 0x23, 0x16, 0x93, 0x94, 0x13, 0x2a, 0xe4,
|
|
0x81, 0xfe, 0xe, 0x34, 0x91, 0x99, 0xe9, 0x8d,
|
|
}
|
|
require.Equal(t, expected, i.Hash)
|
|
}
|
|
|
|
func TestIntention_String(t *testing.T) {
|
|
type testcase struct {
|
|
ixn *Intention
|
|
expect string
|
|
}
|
|
|
|
testID := generateUUID()
|
|
|
|
partitionPrefix := DefaultEnterpriseMetaInDefaultPartition().PartitionOrEmpty()
|
|
if partitionPrefix != "" {
|
|
partitionPrefix += "/"
|
|
}
|
|
|
|
cases := map[string]testcase{
|
|
"legacy allow": {
|
|
&Intention{
|
|
ID: testID,
|
|
SourceName: "foo",
|
|
DestinationName: "bar",
|
|
Action: IntentionActionAllow,
|
|
},
|
|
partitionPrefix + `default/foo => ` + partitionPrefix + `default/bar (ID: ` + testID + `, Precedence: 9, Action: ALLOW)`,
|
|
},
|
|
"legacy deny": {
|
|
&Intention{
|
|
ID: testID,
|
|
SourceName: "foo",
|
|
DestinationName: "bar",
|
|
Action: IntentionActionDeny,
|
|
},
|
|
partitionPrefix + `default/foo => ` + partitionPrefix + `default/bar (ID: ` + testID + `, Precedence: 9, Action: DENY)`,
|
|
},
|
|
"L4 allow": {
|
|
&Intention{
|
|
SourceName: "foo",
|
|
DestinationName: "bar",
|
|
Action: IntentionActionAllow,
|
|
},
|
|
partitionPrefix + `default/foo => ` + partitionPrefix + `default/bar (Precedence: 9, Action: ALLOW)`,
|
|
},
|
|
"L4 deny": {
|
|
&Intention{
|
|
SourceName: "foo",
|
|
DestinationName: "bar",
|
|
Action: IntentionActionDeny,
|
|
},
|
|
partitionPrefix + `default/foo => ` + partitionPrefix + `default/bar (Precedence: 9, Action: DENY)`,
|
|
},
|
|
"L7 one perm": {
|
|
&Intention{
|
|
SourceName: "foo",
|
|
DestinationName: "bar",
|
|
Permissions: []*IntentionPermission{
|
|
{
|
|
Action: IntentionActionAllow,
|
|
HTTP: &IntentionHTTPPermission{
|
|
PathPrefix: "/foo",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
partitionPrefix + `default/foo => ` + partitionPrefix + `default/bar (Precedence: 9, Permissions: 1)`,
|
|
},
|
|
"L7 two perms": {
|
|
&Intention{
|
|
SourceName: "foo",
|
|
DestinationName: "bar",
|
|
Permissions: []*IntentionPermission{
|
|
{
|
|
Action: IntentionActionDeny,
|
|
HTTP: &IntentionHTTPPermission{
|
|
PathExact: "/foo/admin",
|
|
},
|
|
},
|
|
{
|
|
Action: IntentionActionAllow,
|
|
HTTP: &IntentionHTTPPermission{
|
|
PathPrefix: "/foo",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
partitionPrefix + `default/foo => ` + partitionPrefix + `default/bar (Precedence: 9, Permissions: 2)`,
|
|
},
|
|
"L4 allow with source peer": {
|
|
&Intention{
|
|
SourceName: "foo",
|
|
SourcePeer: "billing",
|
|
DestinationName: "bar",
|
|
Action: IntentionActionAllow,
|
|
},
|
|
`peer(billing)/default/foo => ` + partitionPrefix + `default/bar (Precedence: 9, Action: ALLOW)`,
|
|
},
|
|
"L4 allow with source sameness group": {
|
|
&Intention{
|
|
SourceName: "foo",
|
|
SourceSamenessGroup: "group-1",
|
|
DestinationName: "bar",
|
|
Action: IntentionActionAllow,
|
|
},
|
|
`sameness-group(group-1)/default/foo => ` + partitionPrefix + `default/bar (Precedence: 9, Action: ALLOW)`,
|
|
},
|
|
}
|
|
|
|
for name, tc := range cases {
|
|
tc := tc
|
|
// Add a bunch of required fields.
|
|
tc.ixn.FillPartitionAndNamespace(DefaultEnterpriseMetaInDefaultPartition(), true)
|
|
tc.ixn.UpdatePrecedence()
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
got := tc.ixn.String()
|
|
require.Equal(t, tc.expect, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIntentionQueryRequest_CacheInfoKey(t *testing.T) {
|
|
assertCacheInfoKeyIsComplete(t, &IntentionQueryRequest{})
|
|
}
|