mirror of https://github.com/status-im/consul.git
Add SourcePeer fields to relevant Intentions types (#13390)
This commit is contained in:
parent
7423886136
commit
bb832e2bba
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -1181,7 +1181,7 @@ const (
|
||||||
|
|
||||||
// ServiceKindDestination is a Destination for the Connect feature.
|
// ServiceKindDestination is a Destination for the Connect feature.
|
||||||
// This service allows external traffic to exit the mesh through a terminating gateway
|
// This service allows external traffic to exit the mesh through a terminating gateway
|
||||||
//based on centralized configuration.
|
// based on centralized configuration.
|
||||||
ServiceKindDestination ServiceKind = "destination"
|
ServiceKindDestination ServiceKind = "destination"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"`
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue