Add SourcePeer fields to relevant Intentions types (#13390)

This commit is contained in:
Chris S. Kim 2022-06-08 13:24:10 -04:00 committed by GitHub
parent c1f20d17ee
commit 3e71754e7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 172 additions and 71 deletions

View File

@ -83,6 +83,8 @@ func (m *EnterpriseMeta) MergeNoWildcard(_ *EnterpriseMeta) {
} }
func (_ *EnterpriseMeta) Normalize() {} func (_ *EnterpriseMeta) Normalize() {}
func (_ *EnterpriseMeta) NormalizePartition() {}
func (_ *EnterpriseMeta) NormalizeNamespace() {}
func (m *EnterpriseMeta) Matches(_ *EnterpriseMeta) bool { func (m *EnterpriseMeta) Matches(_ *EnterpriseMeta) bool {
return true return true

View File

@ -123,6 +123,7 @@ func (e *ServiceIntentionsConfigEntry) ToIntention(src *SourceIntention) *Intent
ixn := &Intention{ ixn := &Intention{
ID: src.LegacyID, ID: src.LegacyID,
Description: src.Description, Description: src.Description,
SourcePeer: src.Peer,
SourcePartition: src.PartitionOrEmpty(), SourcePartition: src.PartitionOrEmpty(),
SourceNS: src.NamespaceOrDefault(), SourceNS: src.NamespaceOrDefault(),
SourceName: src.Name, SourceName: src.Name,
@ -259,6 +260,9 @@ type SourceIntention struct {
// formerly Intention.SourceNS // formerly Intention.SourceNS
acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"` acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
// Peer is the name of the remote peer of the source service, if applicable.
Peer string `json:",omitempty"`
} }
type IntentionPermission struct { type IntentionPermission struct {
@ -361,11 +365,11 @@ func (e *ServiceIntentionsConfigEntry) UpdateOver(rawPrev ConfigEntry) error {
} }
var ( var (
prevSourceByName = make(map[ServiceName]*SourceIntention) prevSourceByName = make(map[PeeredServiceName]*SourceIntention)
prevSourceByLegacyID = make(map[string]*SourceIntention) prevSourceByLegacyID = make(map[string]*SourceIntention)
) )
for _, src := range prev.Sources { for _, src := range prev.Sources {
prevSourceByName[src.SourceServiceName()] = src prevSourceByName[PeeredServiceName{Peer: src.Peer, ServiceName: src.SourceServiceName()}] = src
if src.LegacyID != "" { if src.LegacyID != "" {
prevSourceByLegacyID[src.LegacyID] = src prevSourceByLegacyID[src.LegacyID] = src
} }
@ -377,7 +381,7 @@ func (e *ServiceIntentionsConfigEntry) UpdateOver(rawPrev ConfigEntry) error {
} }
// Check that the LegacyID fields are handled correctly during updates. // Check that the LegacyID fields are handled correctly during updates.
if prevSrc, ok := prevSourceByName[src.SourceServiceName()]; ok { if prevSrc, ok := prevSourceByName[PeeredServiceName{Peer: src.Peer, ServiceName: src.SourceServiceName()}]; ok {
if prevSrc.LegacyID == "" { if prevSrc.LegacyID == "" {
return fmt.Errorf("Sources[%d].LegacyID: cannot set this field", i) return fmt.Errorf("Sources[%d].LegacyID: cannot set this field", i)
} else if src.LegacyID != prevSrc.LegacyID { } else if src.LegacyID != prevSrc.LegacyID {
@ -423,10 +427,17 @@ func (e *ServiceIntentionsConfigEntry) normalize(legacyWrite bool) error {
src.Type = IntentionSourceConsul src.Type = IntentionSourceConsul
} }
// If the source namespace is omitted it inherits that of the // Normalize the source's namespace and partition.
// destination. // If the source is not peered, it inherits the destination's
// EnterpriseMeta.
if src.Peer == "" {
src.EnterpriseMeta.MergeNoWildcard(&e.EnterpriseMeta) src.EnterpriseMeta.MergeNoWildcard(&e.EnterpriseMeta)
src.EnterpriseMeta.Normalize() src.EnterpriseMeta.Normalize()
} else {
// If the source is peered, normalize the namespace only,
// since peer is mutually exclusive with partition.
src.EnterpriseMeta.NormalizeNamespace()
}
// Compute the precedence only AFTER normalizing namespaces since the // Compute the precedence only AFTER normalizing namespaces since the
// namespaces are factored into the calculation. // namespaces are factored into the calculation.
@ -542,7 +553,7 @@ func (e *ServiceIntentionsConfigEntry) validate(legacyWrite bool) error {
return fmt.Errorf("Name is required") return fmt.Errorf("Name is required")
} }
if err := validateIntentionWildcards(e.Name, &e.EnterpriseMeta); err != nil { if err := validateIntentionWildcards(e.Name, &e.EnterpriseMeta, ""); err != nil {
return err return err
} }
@ -568,7 +579,7 @@ func (e *ServiceIntentionsConfigEntry) validate(legacyWrite bool) error {
return fmt.Errorf("Sources[%d].Name is required", i) return fmt.Errorf("Sources[%d].Name is required", i)
} }
if err := validateIntentionWildcards(src.Name, &src.EnterpriseMeta); err != nil { if err := validateIntentionWildcards(src.Name, &src.EnterpriseMeta, src.Peer); err != nil {
return fmt.Errorf("Sources[%d].%v", i, err) return fmt.Errorf("Sources[%d].%v", i, err)
} }
@ -576,6 +587,10 @@ func (e *ServiceIntentionsConfigEntry) validate(legacyWrite bool) error {
return fmt.Errorf("Sources[%d].%v", i, err) return fmt.Errorf("Sources[%d].%v", i, err)
} }
if src.Peer != "" && src.PartitionOrEmpty() != "" {
return fmt.Errorf("Sources[%d].Peer: cannot set Peer and Partition at the same time.", i)
}
// Length of opaque values // Length of opaque values
if len(src.Description) > metaValueMaxLength { if len(src.Description) > metaValueMaxLength {
return fmt.Errorf( return fmt.Errorf(
@ -583,6 +598,10 @@ func (e *ServiceIntentionsConfigEntry) validate(legacyWrite bool) error {
} }
if legacyWrite { if legacyWrite {
if src.Peer != "" {
return fmt.Errorf("Sources[%d].Peer cannot be set by legacy intentions", i)
}
if len(src.LegacyMeta) > metaMaxKeyPairs { if len(src.LegacyMeta) > metaMaxKeyPairs {
return fmt.Errorf( return fmt.Errorf(
"Sources[%d].Meta exceeds maximum element count %d", i, metaMaxKeyPairs) "Sources[%d].Meta exceeds maximum element count %d", i, metaMaxKeyPairs)
@ -753,7 +772,7 @@ func (e *ServiceIntentionsConfigEntry) validate(legacyWrite bool) error {
} }
// Wildcard usage verification // Wildcard usage verification
func validateIntentionWildcards(name string, entMeta *acl.EnterpriseMeta) error { func validateIntentionWildcards(name string, entMeta *acl.EnterpriseMeta, peerName string) error {
ns := entMeta.NamespaceOrDefault() ns := entMeta.NamespaceOrDefault()
if ns != WildcardSpecifier { if ns != WildcardSpecifier {
if strings.Contains(ns, WildcardSpecifier) { if strings.Contains(ns, WildcardSpecifier) {
@ -772,6 +791,9 @@ func validateIntentionWildcards(name string, entMeta *acl.EnterpriseMeta) error
if strings.Contains(entMeta.PartitionOrDefault(), WildcardSpecifier) { if strings.Contains(entMeta.PartitionOrDefault(), WildcardSpecifier) {
return fmt.Errorf("Partition: cannot use wildcard '*' in partition") return fmt.Errorf("Partition: cannot use wildcard '*' in partition")
} }
if strings.Contains(peerName, WildcardSpecifier) {
return fmt.Errorf("Peer: cannot use wildcard '*' in peer")
}
return nil return nil
} }

View File

@ -57,6 +57,11 @@ type Intention struct {
SourcePartition string `json:",omitempty"` SourcePartition string `json:",omitempty"`
DestinationPartition string `json:",omitempty"` DestinationPartition string `json:",omitempty"`
// SourcePeer cannot be a wildcard "*" and is not compatible with legacy
// intentions. Cannot be used with SourcePartition, as both represent the
// same level of tenancy (partition is local to cluster, peer is remote).
SourcePeer string `json:",omitempty"`
// SourceType is the type of the value for the source. // SourceType is the type of the value for the source.
SourceType IntentionSourceType SourceType IntentionSourceType
@ -311,7 +316,9 @@ func (ixn *Intention) CanRead(authz acl.Authorizer) bool {
// complete intention. This is so that both ends can be aware of why // complete intention. This is so that both ends can be aware of why
// something does or does not work. // something does or does not work.
if ixn.SourceName != "" { // If SourcePeer is set, tenancy is irrelevant in the context of the local cluster
// so we skip authorizing on the Source end.
if ixn.SourceName != "" && ixn.SourcePeer == "" {
ixn.FillAuthzContext(&authzContext, false) ixn.FillAuthzContext(&authzContext, false)
if authz.IntentionRead(ixn.SourceName, &authzContext) == acl.Allow { if authz.IntentionRead(ixn.SourceName, &authzContext) == acl.Allow {
return true return true
@ -394,9 +401,13 @@ func (x *Intention) String() string {
idPart = "ID: " + x.ID + ", " idPart = "ID: " + x.ID + ", "
} }
var srcPartitionPart string // Cluster may be either partition (local) or peer (remote)
var srcClusterPart string
if x.SourcePartition != "" { if x.SourcePartition != "" {
srcPartitionPart = x.SourcePartition + "/" srcClusterPart = x.SourcePartition + "/"
}
if x.SourcePeer != "" {
srcClusterPart = "peer(" + x.SourcePeer + ")/"
} }
var dstPartitionPart string var dstPartitionPart string
@ -412,7 +423,7 @@ func (x *Intention) String() string {
} }
return fmt.Sprintf("%s%s/%s => %s%s/%s (%sPrecedence: %d, %s)", return fmt.Sprintf("%s%s/%s => %s%s/%s (%sPrecedence: %d, %s)",
srcPartitionPart, x.SourceNS, x.SourceName, srcClusterPart, x.SourceNS, x.SourceName,
dstPartitionPart, x.DestinationNS, x.DestinationName, dstPartitionPart, x.DestinationNS, x.DestinationName,
idPart, idPart,
x.Precedence, x.Precedence,
@ -461,6 +472,7 @@ func (x *Intention) ToSourceIntention(legacy bool) *SourceIntention {
src := &SourceIntention{ src := &SourceIntention{
Name: x.SourceName, Name: x.SourceName,
EnterpriseMeta: *x.SourceEnterpriseMeta(), EnterpriseMeta: *x.SourceEnterpriseMeta(),
Peer: x.SourcePeer,
Action: x.Action, Action: x.Action,
Permissions: nil, // explicitly not symmetric with the old APIs Permissions: nil, // explicitly not symmetric with the old APIs
Precedence: 0, // Ignore, let it be computed. Precedence: 0, // Ignore, let it be computed.
@ -570,6 +582,7 @@ type IntentionMutation struct {
ID string ID string
Destination ServiceName Destination ServiceName
Source ServiceName Source ServiceName
// TODO(peering): check if this needs peer field
Value *SourceIntention Value *SourceIntention
} }
@ -716,6 +729,8 @@ type IntentionQueryExact struct {
// TODO(partitions): check query works with partitions // TODO(partitions): check query works with partitions
SourcePartition string `json:",omitempty"` SourcePartition string `json:",omitempty"`
DestinationPartition string `json:",omitempty"` DestinationPartition string `json:",omitempty"`
SourcePeer string `json:",omitempty"`
} }
// Validate is used to ensure all 4 required parameters are specified. // Validate is used to ensure all 4 required parameters are specified.
@ -736,6 +751,7 @@ func (q *IntentionQueryExact) Validate() error {
return err return err
} }
// TODO(peering): add support for listing peer
type IntentionListRequest struct { type IntentionListRequest struct {
Datacenter string Datacenter string
Legacy bool `json:"-"` Legacy bool `json:"-"`
@ -764,12 +780,18 @@ func (s IntentionPrecedenceSorter) Less(i, j int) bool {
return a.Precedence > b.Precedence return a.Precedence > b.Precedence
} }
// Tie break on lexicographic order of the tuple in canonical form (SrcPxn, // Tie break on lexicographic order of the tuple in canonical form:
// SrcNS, Src, DstPxn, DstNS, Dst). This is arbitrary but it keeps sorting //
// deterministic which is a nice property for consistency. It is arguably // (SrcPeer, SrcPxn, SrcNS, Src, DstPxn, DstNS, Dst)
// open to abuse if implementations rely on this however by definition the //
// order among same-precedence rules is arbitrary and doesn't affect whether // This is arbitrary but it keeps sorting deterministic which is a nice
// an allow or deny rule is acted on since all applicable rules are checked. // property for consistency. It is arguably open to abuse if implementations
// rely on this however by definition the order among same-precedence rules
// is arbitrary and doesn't affect whether an allow or deny rule is acted on
// since all applicable rules are checked.
if a.SourcePeer != b.SourcePeer {
return a.SourcePeer < b.SourcePeer
}
if a.SourcePartition != b.SourcePartition { if a.SourcePartition != b.SourcePartition {
return a.SourcePartition < b.SourcePartition return a.SourcePartition < b.SourcePartition
} }

View File

@ -242,58 +242,85 @@ func TestIntentionValidate(t *testing.T) {
} }
func TestIntentionPrecedenceSorter(t *testing.T) { func TestIntentionPrecedenceSorter(t *testing.T) {
type fields struct {
SrcPeer string
SrcNS string
SrcN string
DstNS string
DstN string
}
cases := []struct { cases := []struct {
Name string Name string
Input [][]string // SrcNS, SrcN, DstNS, DstN Input []fields
Expected [][]string // Same structure as Input Expected []fields
}{ }{
{ {
"exhaustive list", "exhaustive list",
[][]string{ []fields{
{"*", "*", "exact", "*"}, // Peer fields
{"*", "*", "*", "*"}, {SrcPeer: "peer", SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "*"},
{"exact", "*", "exact", "exact"}, {SrcPeer: "peer", SrcNS: "*", SrcN: "*", DstNS: "*", DstN: "*"},
{"*", "*", "exact", "exact"}, {SrcPeer: "peer", SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "exact"},
{"exact", "exact", "*", "*"}, {SrcPeer: "peer", SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "exact"},
{"exact", "exact", "exact", "exact"}, {SrcPeer: "peer", SrcNS: "exact", SrcN: "exact", DstNS: "*", DstN: "*"},
{"exact", "exact", "exact", "*"}, {SrcPeer: "peer", SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "exact"},
{"exact", "*", "exact", "*"}, {SrcPeer: "peer", SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "*"},
{"exact", "*", "*", "*"}, {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: "*"},
}, },
[][]string{ []fields{
{"exact", "exact", "exact", "exact"}, {SrcPeer: "", SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "exact"},
{"exact", "*", "exact", "exact"}, {SrcPeer: "peer", SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "exact"},
{"*", "*", "exact", "exact"}, {SrcPeer: "", SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "exact"},
{"exact", "exact", "exact", "*"}, {SrcPeer: "peer", SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "exact"},
{"exact", "*", "exact", "*"}, {SrcPeer: "", SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "exact"},
{"*", "*", "exact", "*"}, {SrcPeer: "peer", SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "exact"},
{"exact", "exact", "*", "*"}, {SrcPeer: "", SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "*"},
{"exact", "*", "*", "*"}, {SrcPeer: "peer", SrcNS: "exact", SrcN: "exact", DstNS: "exact", DstN: "*"},
{"*", "*", "*", "*"}, {SrcPeer: "", SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "*"},
{SrcPeer: "peer", SrcNS: "exact", SrcN: "*", DstNS: "exact", DstN: "*"},
{SrcPeer: "", SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "*"},
{SrcPeer: "peer", SrcNS: "*", SrcN: "*", DstNS: "exact", DstN: "*"},
{SrcPeer: "", SrcNS: "exact", SrcN: "exact", DstNS: "*", DstN: "*"},
{SrcPeer: "peer", SrcNS: "exact", SrcN: "exact", DstNS: "*", DstN: "*"},
{SrcPeer: "", SrcNS: "exact", SrcN: "*", DstNS: "*", DstN: "*"},
{SrcPeer: "peer", SrcNS: "exact", SrcN: "*", DstNS: "*", DstN: "*"},
{SrcPeer: "", SrcNS: "*", SrcN: "*", DstNS: "*", DstN: "*"},
{SrcPeer: "peer", SrcNS: "*", SrcN: "*", DstNS: "*", DstN: "*"},
}, },
}, },
{ {
"tiebreak deterministically", "tiebreak deterministically",
[][]string{ []fields{
{"a", "*", "a", "b"}, {SrcNS: "a", SrcN: "*", DstNS: "a", DstN: "b"},
{"a", "*", "a", "a"}, {SrcNS: "a", SrcN: "*", DstNS: "a", DstN: "a"},
{"b", "a", "a", "a"}, {SrcNS: "b", SrcN: "a", DstNS: "a", DstN: "a"},
{"a", "b", "a", "a"}, {SrcNS: "a", SrcN: "b", DstNS: "a", DstN: "a"},
{"a", "a", "b", "a"}, {SrcNS: "a", SrcN: "a", DstNS: "b", DstN: "a"},
{"a", "a", "a", "b"}, {SrcNS: "a", SrcN: "a", DstNS: "a", DstN: "b"},
{"a", "a", "a", "a"}, {SrcNS: "a", SrcN: "a", DstNS: "a", DstN: "a"},
}, },
[][]string{ []fields{
// Exact matches first in lexicographical order (arbitrary but // Exact matches first in lexicographical order (arbitrary but
// deterministic) // deterministic)
{"a", "a", "a", "a"}, {SrcNS: "a", SrcN: "a", DstNS: "a", DstN: "a"},
{"a", "a", "a", "b"}, {SrcNS: "a", SrcN: "a", DstNS: "a", DstN: "b"},
{"a", "a", "b", "a"}, {SrcNS: "a", SrcN: "a", DstNS: "b", DstN: "a"},
{"a", "b", "a", "a"}, {SrcNS: "a", SrcN: "b", DstNS: "a", DstN: "a"},
{"b", "a", "a", "a"}, {SrcNS: "b", SrcN: "a", DstNS: "a", DstN: "a"},
// Wildcards next, lexicographical // Wildcards next, lexicographical
{"a", "*", "a", "a"}, {SrcNS: "a", SrcN: "*", DstNS: "a", DstN: "a"},
{"a", "*", "a", "b"}, {SrcNS: "a", SrcN: "*", DstNS: "a", DstN: "b"},
}, },
}, },
} }
@ -304,10 +331,11 @@ func TestIntentionPrecedenceSorter(t *testing.T) {
var input Intentions var input Intentions
for _, v := range tc.Input { for _, v := range tc.Input {
input = append(input, &Intention{ input = append(input, &Intention{
SourceNS: v[0], SourcePeer: v.SrcPeer,
SourceName: v[1], SourceNS: v.SrcNS,
DestinationNS: v[2], SourceName: v.SrcN,
DestinationName: v[3], DestinationNS: v.DstNS,
DestinationName: v.DstN,
}) })
} }
@ -320,13 +348,14 @@ func TestIntentionPrecedenceSorter(t *testing.T) {
sort.Sort(IntentionPrecedenceSorter(input)) sort.Sort(IntentionPrecedenceSorter(input))
// Get back into a comparable form // Get back into a comparable form
var actual [][]string var actual []fields
for _, v := range input { for _, v := range input {
actual = append(actual, []string{ actual = append(actual, fields{
v.SourceNS, SrcPeer: v.SourcePeer,
v.SourceName, SrcNS: v.SourceNS,
v.DestinationNS, SrcN: v.SourceName,
v.DestinationName, DstNS: v.DestinationNS,
DstN: v.DestinationName,
}) })
} }
assert.Equal(t, tc.Expected, actual) assert.Equal(t, tc.Expected, actual)
@ -443,6 +472,15 @@ func TestIntention_String(t *testing.T) {
}, },
partitionPrefix + `default/foo => ` + partitionPrefix + `default/bar (Precedence: 9, Permissions: 2)`, 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)`,
},
} }
for name, tc := range cases { for name, tc := range cases {

View File

@ -2154,6 +2154,12 @@ type IndexedServices struct {
QueryMeta QueryMeta
} }
// PeeredServiceName is a basic tuple of ServiceName and peer
type PeeredServiceName struct {
ServiceName ServiceName
Peer string
}
type ServiceName struct { type ServiceName struct {
Name string Name string
acl.EnterpriseMeta acl.EnterpriseMeta

View File

@ -682,6 +682,11 @@ var expectedFieldConfigIntention bexpr.FieldConfigurations = bexpr.FieldConfigur
CoerceFn: bexpr.CoerceString, CoerceFn: bexpr.CoerceString,
SupportedOperations: []bexpr.MatchOperator{bexpr.MatchEqual, bexpr.MatchNotEqual, bexpr.MatchIn, bexpr.MatchNotIn, bexpr.MatchMatches, bexpr.MatchNotMatches}, SupportedOperations: []bexpr.MatchOperator{bexpr.MatchEqual, bexpr.MatchNotEqual, bexpr.MatchIn, bexpr.MatchNotIn, bexpr.MatchMatches, bexpr.MatchNotMatches},
}, },
"SourcePeer": &bexpr.FieldConfiguration{
StructFieldName: "SourcePeer",
CoerceFn: bexpr.CoerceString,
SupportedOperations: []bexpr.MatchOperator{bexpr.MatchEqual, bexpr.MatchNotEqual, bexpr.MatchIn, bexpr.MatchNotIn, bexpr.MatchMatches, bexpr.MatchNotMatches},
},
"SourcePartition": &bexpr.FieldConfiguration{ "SourcePartition": &bexpr.FieldConfiguration{
StructFieldName: "SourcePartition", StructFieldName: "SourcePartition",
CoerceFn: bexpr.CoerceString, CoerceFn: bexpr.CoerceString,

View File

@ -18,6 +18,7 @@ type ServiceIntentionsConfigEntry struct {
type SourceIntention struct { type SourceIntention struct {
Name string Name string
Peer string `json:",omitempty"`
Partition string `json:",omitempty"` Partition string `json:",omitempty"`
Namespace string `json:",omitempty"` Namespace string `json:",omitempty"`
Action IntentionAction `json:",omitempty"` Action IntentionAction `json:",omitempty"`

View File

@ -35,6 +35,11 @@ type Intention struct {
SourcePartition string `json:",omitempty"` SourcePartition string `json:",omitempty"`
DestinationPartition string `json:",omitempty"` DestinationPartition string `json:",omitempty"`
// SourcePeer cannot be a wildcard "*" and is not compatible with legacy
// intentions. Cannot be used with SourcePartition, as both represent the
// same level of tenancy (partition is local to cluster, peer is remote).
SourcePeer string `json:",omitempty"`
// SourceType is the type of the value for the source. // SourceType is the type of the value for the source.
SourceType IntentionSourceType SourceType IntentionSourceType