Sync enterprise changes to oss (#10994)

This commit updates OSS with files for enterprise-specific admin partitions feature work
This commit is contained in:
Chris S. Kim 2021-09-08 11:59:30 -04:00 committed by GitHub
parent 06f3ccebce
commit 9bbfa048a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 228 additions and 73 deletions

View File

@ -109,7 +109,7 @@ func (s *HTTPHandlers) configDelete(resp http.ResponseWriter, req *http.Request)
return reply, nil
}
// ConfigCreate applies the given config entry update.
// ConfigApply applies the given config entry update.
func (s *HTTPHandlers) ConfigApply(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
args := structs.ConfigEntryRequest{
Op: structs.ConfigEntryUpsert,

View File

@ -59,6 +59,18 @@ func (s *Server) validateEnterpriseRequest(entMeta *structs.EnterpriseMeta, writ
return nil
}
func (s *Server) validateEnterpriseIntentionPartition(partition string) error {
if partition == "" {
return nil
} else if strings.ToLower(partition) == "default" {
return nil
}
// No special handling for wildcard partitions as they are pointless in OSS.
return errors.New("Partitions is a Consul Enterprise feature")
}
func (s *Server) validateEnterpriseIntentionNamespace(ns string, _ bool) error {
if ns == "" {
return nil

View File

@ -173,7 +173,7 @@ func (s *Intention) computeApplyChangesLegacyCreate(
) (*structs.IntentionMutation, error) {
// This variant is just for legacy UUID-based intentions.
args.Intention.DefaultNamespaces(entMeta)
args.Intention.FillPartitionAndNamespace(entMeta, true)
if !args.Intention.CanWrite(authz) {
sn := args.Intention.SourceServiceName()
@ -257,12 +257,12 @@ func (s *Intention) computeApplyChangesLegacyUpdate(
return nil, acl.ErrPermissionDenied
}
args.Intention.DefaultNamespaces(entMeta)
args.Intention.FillPartitionAndNamespace(entMeta, true)
// Prior to v1.9.0 renames of the destination side of an intention were
// allowed, but that behavior doesn't work anymore.
if ixn.DestinationServiceName() != args.Intention.DestinationServiceName() {
return nil, fmt.Errorf("Cannot modify DestinationNS or DestinationName for an intention once it exists.")
return nil, fmt.Errorf("Cannot modify Destination partition/namespace/name for an intention once it exists.")
}
// Default source type
@ -308,7 +308,7 @@ func (s *Intention) computeApplyChangesUpsert(
return nil, fmt.Errorf("ID must not be specified")
}
args.Intention.DefaultNamespaces(entMeta)
args.Intention.FillPartitionAndNamespace(entMeta, true)
if !args.Intention.CanWrite(authz) {
sn := args.Intention.SourceServiceName()
@ -389,7 +389,7 @@ func (s *Intention) computeApplyChangesDelete(
entMeta *structs.EnterpriseMeta,
args *structs.IntentionRequest,
) (*structs.IntentionMutation, error) {
args.Intention.DefaultNamespaces(entMeta)
args.Intention.FillPartitionAndNamespace(entMeta, true)
if !args.Intention.CanWrite(authz) {
sn := args.Intention.SourceServiceName()
@ -753,9 +753,15 @@ func (s *Intention) aclAccessorID(secretID string) string {
}
func (s *Intention) validateEnterpriseIntention(ixn *structs.Intention) error {
if err := s.srv.validateEnterpriseIntentionPartition(ixn.SourcePartition); err != nil {
return fmt.Errorf("Invalid source partition %q: %v", ixn.SourcePartition, err)
}
if err := s.srv.validateEnterpriseIntentionNamespace(ixn.SourceNS, true); err != nil {
return fmt.Errorf("Invalid source namespace %q: %v", ixn.SourceNS, err)
}
if err := s.srv.validateEnterpriseIntentionPartition(ixn.DestinationPartition); err != nil {
return fmt.Errorf("Invalid destination partition %q: %v", ixn.DestinationPartition, err)
}
if err := s.srv.validateEnterpriseIntentionNamespace(ixn.DestinationNS, true); err != nil {
return fmt.Errorf("Invalid destination namespace %q: %v", ixn.DestinationNS, err)
}

View File

@ -74,6 +74,8 @@ func TestIntentionApply_new(t *testing.T) {
actual.Hash = ixn.Intention.Hash
//nolint:staticcheck
ixn.Intention.UpdatePrecedence()
// Partition fields will be normalized on Intention.Get
ixn.Intention.NormalizePartitionFields()
require.Equal(t, ixn.Intention, actual)
}
@ -97,7 +99,7 @@ func TestIntentionApply_new(t *testing.T) {
var reply string
err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn2, &reply)
testutil.RequireErrorContains(t, err, "Cannot modify DestinationNS or DestinationName for an intention once it exists.")
testutil.RequireErrorContains(t, err, "Cannot modify Destination partition/namespace/name for an intention once it exists.")
})
}
@ -265,6 +267,8 @@ func TestIntentionApply_updateGood(t *testing.T) {
actual.Hash = ixn.Intention.Hash
//nolint:staticcheck
ixn.Intention.UpdatePrecedence()
// Partition fields will be normalized on Intention.Get
ixn.Intention.NormalizePartitionFields()
require.Equal(t, ixn.Intention, actual)
}
}

View File

@ -38,6 +38,9 @@ func (s *Server) startIntentionConfigEntryMigration(ctx context.Context) error {
// datacenter is composed entirely of compatible servers and there are
// no more legacy intentions.
if s.DatacenterSupportsIntentionsAsConfigEntries() {
// NOTE: we only have to migrate legacy intentions from the default
// partition because partitions didn't exist when legacy intentions
// were canonical
_, ixns, err := s.fsm.State().LegacyIntentions(nil, structs.WildcardEnterpriseMetaInDefaultPartition())
if err != nil {
return err
@ -88,6 +91,9 @@ func (s *Server) legacyIntentionMigration(ctx context.Context) error {
}
state := s.fsm.State()
// NOTE: we only have to migrate legacy intentions from the default
// partition because partitions didn't exist when legacy intentions
// were canonical
_, ixns, err := state.LegacyIntentions(nil, structs.WildcardEnterpriseMetaInDefaultPartition())
if err != nil {
return err

View File

@ -128,7 +128,7 @@ func configIntentionsListTxn(tx ReadTxn, ws memdb.WatchSet, entMeta *structs.Ent
idx := maxIndexTxn(tx, tableConfigEntries)
iter, err := getConfigEntryKindsWithTxn(tx, structs.ServiceIntentions, structs.WildcardEnterpriseMetaInDefaultPartition())
iter, err := getConfigEntryKindsWithTxn(tx, structs.ServiceIntentions, entMeta.WildcardEnterpriseMetaForPartition())
if err != nil {
return 0, nil, false, fmt.Errorf("failed config entry lookup: %s", err)
}

View File

@ -3,12 +3,12 @@ package state
import (
"errors"
"fmt"
"github.com/hashicorp/consul/agent/connect"
"sort"
"github.com/hashicorp/go-memdb"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/structs"
)
@ -1008,7 +1008,7 @@ func (s *Store) intentionTopologyTxn(tx ReadTxn, ws memdb.WatchSet,
return true
}
return false
}, structs.WildcardEnterpriseMetaInDefaultPartition())
}, target.WildcardEnterpriseMetaForPartition())
if err != nil {
return index, nil, fmt.Errorf("failed to fetch catalog service list: %v", err)
}

View File

@ -155,6 +155,8 @@ func TestStore_IntentionSetGet_basic(t *testing.T) {
expected.UpdatePrecedence()
//nolint:staticcheck
expected.SetHash()
expected.NormalizePartitionFields()
}
require.True(t, watchFired(ws), "watch fired")
@ -1083,7 +1085,7 @@ func TestStore_IntentionsList(t *testing.T) {
require.Equal(t, lastIndex, idx)
testIntention := func(src, dst string) *structs.Intention {
return &structs.Intention{
ret := &structs.Intention{
ID: testUUID(),
SourceNS: "default",
SourceName: src,
@ -1095,6 +1097,10 @@ func TestStore_IntentionsList(t *testing.T) {
CreatedAt: testTimeA,
UpdatedAt: testTimeA,
}
if !legacy {
ret.NormalizePartitionFields()
}
return ret
}
testConfigEntry := func(dst string, srcs ...string) *structs.ServiceIntentionsConfigEntry {

View File

@ -21,6 +21,18 @@ func (s *HTTPHandlers) parseEntMeta(req *http.Request, entMeta *structs.Enterpri
return s.parseEntMetaPartition(req, entMeta)
}
func (s *HTTPHandlers) validateEnterpriseIntentionPartition(logName, partition string) error {
if partition == "" {
return nil
} else if strings.ToLower(partition) == "default" {
return nil
}
// No special handling for wildcard namespaces as they are pointless in OSS.
return BadRequestError{Reason: "Invalid " + logName + "(" + partition + ")" + ": Partitions is a Consul Enterprise feature"}
}
func (s *HTTPHandlers) validateEnterpriseIntentionNamespace(logName, ns string, _ bool) error {
if ns == "" {
return nil

View File

@ -54,6 +54,9 @@ func (s *HTTPHandlers) IntentionCreate(resp http.ResponseWriter, req *http.Reque
if err := s.parseEntMetaNoWildcard(req, &entMeta); err != nil {
return nil, err
}
if entMeta.PartitionOrDefault() != structs.PartitionOrDefault("") {
return nil, BadRequestError{Reason: "Cannot use a partition with this endpoint"}
}
args := structs.IntentionRequest{
Op: structs.IntentionOpCreate,
@ -64,7 +67,8 @@ func (s *HTTPHandlers) IntentionCreate(resp http.ResponseWriter, req *http.Reque
return nil, fmt.Errorf("Failed to decode request body: %s", err)
}
args.Intention.FillNonDefaultNamespaces(&entMeta)
// TODO(partitions): reject non-empty/non-default partitions from the decoded body
args.Intention.FillPartitionAndNamespace(&entMeta, false)
if err := s.validateEnterpriseIntention(args.Intention); err != nil {
return nil, err
@ -79,12 +83,19 @@ func (s *HTTPHandlers) IntentionCreate(resp http.ResponseWriter, req *http.Reque
}
func (s *HTTPHandlers) validateEnterpriseIntention(ixn *structs.Intention) error {
if err := s.validateEnterpriseIntentionPartition("SourcePartition", ixn.SourcePartition); err != nil {
return err
}
if err := s.validateEnterpriseIntentionPartition("DestinationPartition", ixn.DestinationPartition); err != nil {
return err
}
if err := s.validateEnterpriseIntentionNamespace("SourceNS", ixn.SourceNS, true); err != nil {
return err
}
if err := s.validateEnterpriseIntentionNamespace("DestinationNS", ixn.DestinationNS, true); err != nil {
return err
}
return nil
}
@ -131,6 +142,7 @@ func (s *HTTPHandlers) IntentionMatch(resp http.ResponseWriter, req *http.Reques
}
args.Match.Entries[i] = structs.IntentionMatchEntry{
Partition: entMeta.PartitionOrEmpty(),
Namespace: ns,
Name: name,
}
@ -214,10 +226,12 @@ func (s *HTTPHandlers) IntentionCheck(resp http.ResponseWriter, req *http.Reques
// We parse them the same way as matches to extract namespace/name
args.Check.SourceName = source[0]
if args.Check.SourceType == structs.IntentionSourceConsul {
// TODO(partitions): this func should return partition
ns, name, err := parseIntentionStringComponent(source[0], &entMeta)
if err != nil {
return nil, fmt.Errorf("source %q is invalid: %s", source[0], err)
}
args.Check.SourcePartition = entMeta.PartitionOrEmpty()
args.Check.SourceNS = ns
args.Check.SourceName = name
}
@ -227,6 +241,7 @@ func (s *HTTPHandlers) IntentionCheck(resp http.ResponseWriter, req *http.Reques
if err != nil {
return nil, fmt.Errorf("destination %q is invalid: %s", destination[0], err)
}
args.Check.DestinationPartition = entMeta.PartitionOrEmpty()
args.Check.DestinationNS = ns
args.Check.DestinationName = name
@ -269,6 +284,7 @@ func (s *HTTPHandlers) IntentionGetExact(resp http.ResponseWriter, req *http.Req
if err != nil {
return nil, fmt.Errorf("source %q is invalid: %s", source[0], err)
}
args.Exact.SourcePartition = entMeta.PartitionOrEmpty()
args.Exact.SourceNS = ns
args.Exact.SourceName = name
}
@ -278,6 +294,7 @@ func (s *HTTPHandlers) IntentionGetExact(resp http.ResponseWriter, req *http.Req
if err != nil {
return nil, fmt.Errorf("destination %q is invalid: %s", destination[0], err)
}
args.Exact.DestinationPartition = entMeta.PartitionOrEmpty()
args.Exact.DestinationNS = ns
args.Exact.DestinationName = name
}
@ -394,6 +411,9 @@ func (s *HTTPHandlers) IntentionSpecificUpdate(id string, resp http.ResponseWrit
if err := s.parseEntMetaNoWildcard(req, &entMeta); err != nil {
return nil, err
}
if entMeta.PartitionOrDefault() != structs.PartitionOrDefault("") {
return nil, BadRequestError{Reason: "Cannot use a partition with this endpoint"}
}
args := structs.IntentionRequest{
Op: structs.IntentionOpUpdate,
@ -404,7 +424,7 @@ func (s *HTTPHandlers) IntentionSpecificUpdate(id string, resp http.ResponseWrit
return nil, BadRequestError{Reason: fmt.Sprintf("Request decode failed: %v", err)}
}
args.Intention.FillNonDefaultNamespaces(&entMeta)
args.Intention.FillPartitionAndNamespace(&entMeta, false)
// Use the ID from the URL
args.Intention.ID = id
@ -444,12 +464,14 @@ func (s *HTTPHandlers) IntentionPutExact(resp http.ResponseWriter, req *http.Req
args.Intention.ID = ""
// Use the intention identity from the URL.
args.Intention.SourcePartition = exact.SourcePartition
args.Intention.SourceNS = exact.SourceNS
args.Intention.SourceName = exact.SourceName
args.Intention.DestinationPartition = exact.DestinationPartition
args.Intention.DestinationNS = exact.DestinationNS
args.Intention.DestinationName = exact.DestinationName
args.Intention.FillNonDefaultNamespaces(&entMeta)
args.Intention.FillPartitionAndNamespace(&entMeta, false)
var ignored string
if err := s.agent.RPC("Intention.Apply", &args, &ignored); err != nil {
@ -494,10 +516,12 @@ func (s *HTTPHandlers) IntentionDeleteExact(resp http.ResponseWriter, req *http.
Op: structs.IntentionOpDelete,
Intention: &structs.Intention{
// NOTE: ID is explicitly empty here
SourceNS: exact.SourceNS,
SourceName: exact.SourceName,
DestinationNS: exact.DestinationNS,
DestinationName: exact.DestinationName,
SourcePartition: exact.SourcePartition,
SourceNS: exact.SourceNS,
SourceName: exact.SourceName,
DestinationPartition: exact.DestinationPartition,
DestinationNS: exact.DestinationNS,
DestinationName: exact.DestinationName,
},
}
s.parseDC(req, &args.Datacenter)
@ -533,6 +557,7 @@ func parseIntentionQueryExact(req *http.Request, entMeta *structs.EnterpriseMeta
if err != nil {
return nil, fmt.Errorf("source %q is invalid: %s", source[0], err)
}
exact.SourcePartition = entMeta.PartitionOrEmpty()
exact.SourceNS = ns
exact.SourceName = name
}
@ -542,6 +567,7 @@ func parseIntentionQueryExact(req *http.Request, entMeta *structs.EnterpriseMeta
if err != nil {
return nil, fmt.Errorf("destination %q is invalid: %s", destination[0], err)
}
exact.DestinationPartition = entMeta.PartitionOrEmpty()
exact.DestinationNS = ns
exact.DestinationName = name
}
@ -549,6 +575,7 @@ func parseIntentionQueryExact(req *http.Request, entMeta *structs.EnterpriseMeta
return &exact, nil
}
// TODO(partitions): update to handle partitions
func parseIntentionStringComponent(input string, entMeta *structs.EnterpriseMeta) (string, string, error) {
// Get the index to the '/'. If it doesn't exist, we have just a name
// so just set that and return.

View File

@ -6,11 +6,12 @@ import (
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/sdk/testutil"
"github.com/hashicorp/consul/testrpc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIntentionList(t *testing.T) {

View File

@ -169,10 +169,6 @@ func (e *ServiceConfigEntry) Validate() error {
if err != nil {
validationErr = multierror.Append(validationErr, fmt.Errorf("error in upstream override for %s: %v", override.ServiceName(), err))
}
if err := validateInnerEnterpriseMeta(&override.EnterpriseMeta, &e.EnterpriseMeta); err != nil {
validationErr = multierror.Append(validationErr, fmt.Errorf("error in upstream override for %s: %v", override.ServiceName(), err))
}
}
if e.UpstreamConfig.Defaults != nil {

View File

@ -119,19 +119,22 @@ func (e *ServiceIntentionsConfigEntry) ToIntention(src *SourceIntention) *Intent
}
ixn := &Intention{
ID: src.LegacyID,
Description: src.Description,
SourceNS: src.NamespaceOrDefault(),
SourceName: src.Name,
SourceType: src.Type,
Action: src.Action,
Permissions: src.Permissions,
Meta: meta,
Precedence: src.Precedence,
DestinationNS: e.NamespaceOrDefault(),
DestinationName: e.Name,
RaftIndex: e.RaftIndex,
ID: src.LegacyID,
Description: src.Description,
SourcePartition: src.PartitionOrEmpty(),
SourceNS: src.NamespaceOrDefault(),
SourceName: src.Name,
SourceType: src.Type,
Action: src.Action,
Permissions: src.Permissions,
Meta: meta,
Precedence: src.Precedence,
DestinationPartition: e.PartitionOrEmpty(),
DestinationNS: e.NamespaceOrDefault(),
DestinationName: e.Name,
RaftIndex: e.RaftIndex,
}
if src.LegacyCreateTime != nil {
ixn.CreatedAt = *src.LegacyCreateTime
}
@ -764,6 +767,9 @@ func validateIntentionWildcards(name string, entMeta *EnterpriseMeta) error {
return fmt.Errorf("Name: exact value cannot follow wildcard namespace")
}
}
if strings.Contains(entMeta.PartitionOrDefault(), WildcardSpecifier) {
return fmt.Errorf("Partition: cannot use wildcard '*' in partition")
}
return nil
}
@ -815,5 +821,23 @@ func MigrateIntentions(ixns Intentions) []*ServiceIntentionsConfigEntry {
for _, entry := range collated {
out = append(out, entry)
}
sort.Slice(out, func(i, j int) bool {
a := out[i]
b := out[j]
if a.PartitionOrDefault() < b.PartitionOrDefault() {
return true
} else if a.PartitionOrDefault() > b.PartitionOrDefault() {
return false
}
if a.NamespaceOrDefault() < b.NamespaceOrDefault() {
return true
} else if a.NamespaceOrDefault() > b.NamespaceOrDefault() {
return false
}
return a.Name < b.Name
})
return out
}

View File

@ -1296,7 +1296,7 @@ func TestMigrateIntentions(t *testing.T) {
}
anyTime := time.Now().UTC()
entMeta := NodeEnterpriseMetaInDefaultPartition()
entMeta := DefaultEnterpriseMetaInDefaultPartition()
cases := map[string]testcase{
"nil": {},
@ -1487,7 +1487,7 @@ func TestMigrateIntentions(t *testing.T) {
tc := tc
t.Run(name, func(t *testing.T) {
got := MigrateIntentions(tc.in)
require.ElementsMatch(t, tc.expect, got)
require.Equal(t, tc.expect, got)
})
}
}

View File

@ -2418,8 +2418,6 @@ func testConfigEntryNormalizeAndValidate(t *testing.T, cases map[string]configEn
err = tc.entry.Validate()
if tc.validateErr != "" {
// require.Error(t, err)
// require.Contains(t, err.Error(), tc.validateErr)
testutil.RequireErrorContains(t, err, tc.validateErr)
return
}

View File

@ -52,6 +52,11 @@ type Intention struct {
SourceNS, SourceName string
DestinationNS, DestinationName string
// SourcePartition and DestinationPartition cannot be wildcards "*" and
// are not compatible with legacy intentions.
SourcePartition string `json:",omitempty"`
DestinationPartition string `json:",omitempty"`
// SourceType is the type of the value for the source.
SourceType IntentionSourceType
@ -111,10 +116,12 @@ func (t *Intention) Clone() *Intention {
func (t *Intention) ToExact() *IntentionQueryExact {
return &IntentionQueryExact{
SourceNS: t.SourceNS,
SourceName: t.SourceName,
DestinationNS: t.DestinationNS,
DestinationName: t.DestinationName,
SourcePartition: t.SourcePartition,
SourceNS: t.SourceNS,
SourceName: t.SourceName,
DestinationPartition: t.DestinationPartition,
DestinationNS: t.DestinationNS,
DestinationName: t.DestinationName,
}
}
@ -379,6 +386,16 @@ func (x *Intention) String() string {
idPart = "ID: " + x.ID + ", "
}
var srcPartitionPart string
if x.SourcePartition != "" {
srcPartitionPart = x.SourcePartition + "/"
}
var dstPartitionPart string
if x.DestinationPartition != "" {
dstPartitionPart = x.DestinationPartition + "/"
}
var detailPart string
if len(x.Permissions) > 0 {
detailPart = fmt.Sprintf("Permissions: %d", len(x.Permissions))
@ -386,9 +403,9 @@ func (x *Intention) String() string {
detailPart = "Action: " + strings.ToUpper(string(x.Action))
}
return fmt.Sprintf("%s/%s => %s/%s (%sPrecedence: %d, %s)",
x.SourceNS, x.SourceName,
x.DestinationNS, x.DestinationName,
return fmt.Sprintf("%s%s/%s => %s%s/%s (%sPrecedence: %d, %s)",
srcPartitionPart, x.SourceNS, x.SourceName,
dstPartitionPart, x.DestinationNS, x.DestinationName,
idPart,
x.Precedence,
detailPart,
@ -625,6 +642,7 @@ type IntentionQueryMatch struct {
// IntentionMatchEntry is a single entry for matching an intention.
type IntentionMatchEntry struct {
Partition string `json:",omitempty"`
Namespace string
Name string
}
@ -637,6 +655,10 @@ type IntentionQueryCheck struct {
SourceNS, SourceName string
DestinationNS, DestinationName string
// TODO(partitions): check query works with partitions
SourcePartition string `json:",omitempty"`
DestinationPartition string `json:",omitempty"`
// SourceType is the type of the value for the source.
SourceType IntentionSourceType
}
@ -673,9 +695,13 @@ type IntentionDecisionSummary struct {
type IntentionQueryExact struct {
SourceNS, SourceName string
DestinationNS, DestinationName string
// TODO(partitions): check query works with partitions
SourcePartition string `json:",omitempty"`
DestinationPartition string `json:",omitempty"`
}
// Validate is used to ensure all 4 parameters are specified.
// Validate is used to ensure all 4 required parameters are specified.
func (q *IntentionQueryExact) Validate() error {
var err error
if q.SourceNS == "" {
@ -721,18 +747,24 @@ func (s IntentionPrecedenceSorter) Less(i, j int) bool {
return a.Precedence > b.Precedence
}
// Tie break on lexicographic order of the 4-tuple in canonical form (SrcNS,
// Src, DstNS, Dst). This is arbitrary but it keeps sorting deterministic
// which is a nice 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.
// Tie break on lexicographic order of the tuple in canonical form (SrcPxn,
// SrcNS, Src, DstPxn, DstNS, Dst). This is arbitrary but it keeps sorting
// deterministic which is a nice 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.SourcePartition != b.SourcePartition {
return a.SourcePartition < b.SourcePartition
}
if a.SourceNS != b.SourceNS {
return a.SourceNS < b.SourceNS
}
if a.SourceName != b.SourceName {
return a.SourceName < b.SourceName
}
if a.DestinationPartition != b.DestinationPartition {
return a.DestinationPartition < b.DestinationPartition
}
if a.DestinationNS != b.DestinationNS {
return a.DestinationNS < b.DestinationNS
}

View File

@ -47,21 +47,35 @@ func (_ *IntentionQueryCheck) FillAuthzContext(_ *acl.AuthorizerContext) {
// do nothing
}
// DefaultNamespaces will populate both the SourceNS and DestinationNS fields
// if they are empty with the proper defaults.
func (ixn *Intention) DefaultNamespaces(_ *EnterpriseMeta) {
// Until we support namespaces, we force all namespaces to be default
// FillPartitionAndNamespace will fill in empty source and destination partition/namespaces.
// If fillDefault is true, all fields are defaulted when the given enterprise meta does not
// specify them.
//
// fillDefault MUST be true on servers to ensure that all fields are populated on writes.
// fillDefault MUST be false on clients so that servers can correctly fill in the
// namespace/partition of the ACL token.
func (ixn *Intention) FillPartitionAndNamespace(entMeta *EnterpriseMeta, fillDefault bool) {
if ixn == nil {
return
}
var ns = entMeta.NamespaceOrEmpty()
if fillDefault {
if ns == "" {
ns = IntentionDefaultNamespace
}
}
if ixn.SourceNS == "" {
ixn.SourceNS = IntentionDefaultNamespace
ixn.SourceNS = ns
}
if ixn.DestinationNS == "" {
ixn.DestinationNS = IntentionDefaultNamespace
ixn.DestinationNS = ns
}
ixn.SourcePartition = ""
ixn.DestinationPartition = ""
}
// FillNonDefaultNamespaces will populate the SourceNS and DestinationNS fields
// if they are empty with the proper defaults, but only if the proper defaults
// are themselves not "default".
func (ixn *Intention) FillNonDefaultNamespaces(_ *EnterpriseMeta) {
// do nothing
func (ixn *Intention) NormalizePartitionFields() {
ixn.SourcePartition = ""
ixn.DestinationPartition = ""
}

View File

@ -369,6 +369,11 @@ func TestIntention_String(t *testing.T) {
testID := generateUUID()
partitionPrefix := DefaultEnterpriseMetaInDefaultPartition().PartitionOrEmpty()
if partitionPrefix != "" {
partitionPrefix += "/"
}
cases := map[string]testcase{
"legacy allow": {
&Intention{
@ -377,7 +382,7 @@ func TestIntention_String(t *testing.T) {
DestinationName: "bar",
Action: IntentionActionAllow,
},
`default/foo => default/bar (ID: ` + testID + `, Precedence: 9, Action: ALLOW)`,
partitionPrefix + `default/foo => ` + partitionPrefix + `default/bar (ID: ` + testID + `, Precedence: 9, Action: ALLOW)`,
},
"legacy deny": {
&Intention{
@ -386,7 +391,7 @@ func TestIntention_String(t *testing.T) {
DestinationName: "bar",
Action: IntentionActionDeny,
},
`default/foo => default/bar (ID: ` + testID + `, Precedence: 9, Action: DENY)`,
partitionPrefix + `default/foo => ` + partitionPrefix + `default/bar (ID: ` + testID + `, Precedence: 9, Action: DENY)`,
},
"L4 allow": {
&Intention{
@ -394,7 +399,7 @@ func TestIntention_String(t *testing.T) {
DestinationName: "bar",
Action: IntentionActionAllow,
},
`default/foo => default/bar (Precedence: 9, Action: ALLOW)`,
partitionPrefix + `default/foo => ` + partitionPrefix + `default/bar (Precedence: 9, Action: ALLOW)`,
},
"L4 deny": {
&Intention{
@ -402,7 +407,7 @@ func TestIntention_String(t *testing.T) {
DestinationName: "bar",
Action: IntentionActionDeny,
},
`default/foo => default/bar (Precedence: 9, Action: DENY)`,
partitionPrefix + `default/foo => ` + partitionPrefix + `default/bar (Precedence: 9, Action: DENY)`,
},
"L7 one perm": {
&Intention{
@ -417,7 +422,7 @@ func TestIntention_String(t *testing.T) {
},
},
},
`default/foo => default/bar (Precedence: 9, Permissions: 1)`,
partitionPrefix + `default/foo => ` + partitionPrefix + `default/bar (Precedence: 9, Permissions: 1)`,
},
"L7 two perms": {
&Intention{
@ -438,14 +443,14 @@ func TestIntention_String(t *testing.T) {
},
},
},
`default/foo => default/bar (Precedence: 9, Permissions: 2)`,
partitionPrefix + `default/foo => ` + partitionPrefix + `default/bar (Precedence: 9, Permissions: 2)`,
},
}
for name, tc := range cases {
tc := tc
// Add a bunch of required fields.
tc.ixn.DefaultNamespaces(DefaultEnterpriseMetaInDefaultPartition())
tc.ixn.FillPartitionAndNamespace(DefaultEnterpriseMetaInDefaultPartition(), true)
tc.ixn.UpdatePrecedence()
t.Run(name, func(t *testing.T) {

View File

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

View File

@ -6,7 +6,7 @@ import (
// TestIntention returns a valid, uninserted (no ID set) intention.
func TestIntention(t testing.T) *Intention {
return &Intention{
ixn := &Intention{
SourceNS: IntentionDefaultNamespace,
SourceName: "api",
DestinationNS: IntentionDefaultNamespace,
@ -15,4 +15,6 @@ func TestIntention(t testing.T) *Intention {
SourceType: IntentionSourceConsul,
Meta: map[string]string{},
}
ixn.NormalizePartitionFields()
return ixn
}