mirror of https://github.com/status-im/consul.git
Sync enterprise changes to oss (#10994)
This commit updates OSS with files for enterprise-specific admin partitions feature work
This commit is contained in:
parent
06f3ccebce
commit
9bbfa048a2
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 = ""
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue