server: remove config entry CAS in legacy intention API bridge code (#9151)
Change so line-item intention edits via the API are handled via the state store instead of via CAS operations. Fixes #9143
This commit is contained in:
parent
6300abed18
commit
e323014faf
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
server: remove config entry CAS in legacy intention API bridge code
|
||||||
|
```
|
|
@ -19,10 +19,6 @@ type ConfigEntry struct {
|
||||||
|
|
||||||
// Apply does an upsert of the given config entry.
|
// Apply does an upsert of the given config entry.
|
||||||
func (c *ConfigEntry) Apply(args *structs.ConfigEntryRequest, reply *bool) error {
|
func (c *ConfigEntry) Apply(args *structs.ConfigEntryRequest, reply *bool) error {
|
||||||
return c.applyInternal(args, reply, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConfigEntry) applyInternal(args *structs.ConfigEntryRequest, reply *bool, normalizeAndValidateFn func(structs.ConfigEntry) error) error {
|
|
||||||
if err := c.srv.validateEnterpriseRequest(args.Entry.GetEnterpriseMeta(), true); err != nil {
|
if err := c.srv.validateEnterpriseRequest(args.Entry.GetEnterpriseMeta(), true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -47,18 +43,12 @@ func (c *ConfigEntry) applyInternal(args *structs.ConfigEntryRequest, reply *boo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize and validate the incoming config entry as if it came from a user.
|
// Normalize and validate the incoming config entry as if it came from a user.
|
||||||
if normalizeAndValidateFn == nil {
|
|
||||||
if err := args.Entry.Normalize(); err != nil {
|
if err := args.Entry.Normalize(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := args.Entry.Validate(); err != nil {
|
if err := args.Entry.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if err := normalizeAndValidateFn(args.Entry); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if authz != nil && !args.Entry.CanWrite(authz) {
|
if authz != nil && !args.Entry.CanWrite(authz) {
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
|
|
|
@ -291,6 +291,11 @@ func (c *FSM) applyIntentionOperation(buf []byte, index uint64) interface{} {
|
||||||
[]metrics.Label{{Name: "op", Value: string(req.Op)}})
|
[]metrics.Label{{Name: "op", Value: string(req.Op)}})
|
||||||
defer metrics.MeasureSinceWithLabels([]string{"fsm", "intention"}, time.Now(),
|
defer metrics.MeasureSinceWithLabels([]string{"fsm", "intention"}, time.Now(),
|
||||||
[]metrics.Label{{Name: "op", Value: string(req.Op)}})
|
[]metrics.Label{{Name: "op", Value: string(req.Op)}})
|
||||||
|
|
||||||
|
if req.Mutation != nil {
|
||||||
|
return c.state.IntentionMutation(index, req.Op, req.Mutation)
|
||||||
|
}
|
||||||
|
|
||||||
switch req.Op {
|
switch req.Op {
|
||||||
case structs.IntentionOpCreate, structs.IntentionOpUpdate:
|
case structs.IntentionOpCreate, structs.IntentionOpUpdate:
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
|
|
|
@ -21,22 +21,11 @@ var (
|
||||||
ErrIntentionNotFound = errors.New("Intention not found")
|
ErrIntentionNotFound = errors.New("Intention not found")
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewIntentionEndpoint returns a new Intention endpoint.
|
|
||||||
func NewIntentionEndpoint(srv *Server, logger hclog.Logger) *Intention {
|
|
||||||
return &Intention{
|
|
||||||
srv: srv,
|
|
||||||
logger: logger,
|
|
||||||
configEntryEndpoint: &ConfigEntry{srv},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intention manages the Connect intentions.
|
// Intention manages the Connect intentions.
|
||||||
type Intention struct {
|
type Intention struct {
|
||||||
// srv is a pointer back to the server.
|
// srv is a pointer back to the server.
|
||||||
srv *Server
|
srv *Server
|
||||||
logger hclog.Logger
|
logger hclog.Logger
|
||||||
|
|
||||||
configEntryEndpoint *ConfigEntry
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Intention) checkIntentionID(id string) (bool, error) {
|
func (s *Intention) checkIntentionID(id string) (bool, error) {
|
||||||
|
@ -69,10 +58,7 @@ func (s *Intention) legacyUpgradeCheck() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply creates or updates an intention in the data store.
|
// Apply creates or updates an intention in the data store.
|
||||||
func (s *Intention) Apply(
|
func (s *Intention) Apply(args *structs.IntentionRequest, reply *string) error {
|
||||||
args *structs.IntentionRequest,
|
|
||||||
reply *string) error {
|
|
||||||
|
|
||||||
// Ensure that all service-intentions config entry writes go to the primary
|
// Ensure that all service-intentions config entry writes go to the primary
|
||||||
// datacenter. These will then be replicated to all the other datacenters.
|
// datacenter. These will then be replicated to all the other datacenters.
|
||||||
args.Datacenter = s.srv.config.PrimaryDatacenter
|
args.Datacenter = s.srv.config.PrimaryDatacenter
|
||||||
|
@ -87,6 +73,10 @@ func (s *Intention) Apply(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if args.Mutation != nil {
|
||||||
|
return fmt.Errorf("Mutation field is internal only and must not be set via RPC")
|
||||||
|
}
|
||||||
|
|
||||||
// Always set a non-nil intention to avoid nil-access below
|
// Always set a non-nil intention to avoid nil-access below
|
||||||
if args.Intention == nil {
|
if args.Intention == nil {
|
||||||
args.Intention = &structs.Intention{}
|
args.Intention = &structs.Intention{}
|
||||||
|
@ -105,27 +95,26 @@ func (s *Intention) Apply(
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
configOp structs.ConfigEntryOp
|
mut *structs.IntentionMutation
|
||||||
configEntry *structs.ServiceIntentionsConfigEntry
|
|
||||||
legacyWrite bool
|
legacyWrite bool
|
||||||
)
|
)
|
||||||
switch args.Op {
|
switch args.Op {
|
||||||
case structs.IntentionOpCreate:
|
case structs.IntentionOpCreate:
|
||||||
legacyWrite = true
|
legacyWrite = true
|
||||||
configOp, configEntry, err = s.computeApplyChangesLegacyCreate(accessorID, authz, &entMeta, args)
|
mut, err = s.computeApplyChangesLegacyCreate(accessorID, authz, &entMeta, args)
|
||||||
case structs.IntentionOpUpdate:
|
case structs.IntentionOpUpdate:
|
||||||
legacyWrite = true
|
legacyWrite = true
|
||||||
configOp, configEntry, err = s.computeApplyChangesLegacyUpdate(accessorID, authz, &entMeta, args)
|
mut, err = s.computeApplyChangesLegacyUpdate(accessorID, authz, &entMeta, args)
|
||||||
case structs.IntentionOpUpsert:
|
case structs.IntentionOpUpsert:
|
||||||
legacyWrite = false
|
legacyWrite = false
|
||||||
configOp, configEntry, err = s.computeApplyChangesUpsert(&entMeta, args)
|
mut, err = s.computeApplyChangesUpsert(accessorID, authz, &entMeta, args)
|
||||||
case structs.IntentionOpDelete:
|
case structs.IntentionOpDelete:
|
||||||
if args.Intention.ID == "" {
|
if args.Intention.ID == "" {
|
||||||
legacyWrite = false
|
legacyWrite = false
|
||||||
configOp, configEntry, err = s.computeApplyChangesDelete(&entMeta, args)
|
mut, err = s.computeApplyChangesDelete(accessorID, authz, &entMeta, args)
|
||||||
} else {
|
} else {
|
||||||
legacyWrite = true
|
legacyWrite = true
|
||||||
configOp, configEntry, err = s.computeApplyChangesLegacyDelete(accessorID, authz, &entMeta, args)
|
mut, err = s.computeApplyChangesLegacyDelete(accessorID, authz, &entMeta, args)
|
||||||
}
|
}
|
||||||
case structs.IntentionOpDeleteAll:
|
case structs.IntentionOpDeleteAll:
|
||||||
// This is an internal operation initiated by the leader and is not
|
// This is an internal operation initiated by the leader and is not
|
||||||
|
@ -145,54 +134,16 @@ func (s *Intention) Apply(
|
||||||
*reply = ""
|
*reply = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if configOp == "" {
|
// Switch to the config entry manipulating flavor:
|
||||||
return nil // no-op
|
args.Mutation = mut
|
||||||
}
|
args.Intention = nil
|
||||||
|
|
||||||
// Commit indirectly by invoking the other RPC handler directly.
|
resp, err := s.srv.raftApply(structs.IntentionRequestType, args)
|
||||||
|
if err != nil {
|
||||||
if configOp == structs.ConfigEntryDelete {
|
|
||||||
configReq := &structs.ConfigEntryRequest{
|
|
||||||
Datacenter: args.Datacenter,
|
|
||||||
WriteRequest: args.WriteRequest,
|
|
||||||
Op: structs.ConfigEntryDelete,
|
|
||||||
Entry: configEntry,
|
|
||||||
}
|
|
||||||
|
|
||||||
var ignored struct{}
|
|
||||||
return s.configEntryEndpoint.Delete(configReq, &ignored)
|
|
||||||
}
|
|
||||||
|
|
||||||
if configOp != structs.ConfigEntryUpsertCAS {
|
|
||||||
return fmt.Errorf("Invalid Intention config entry operation: %v", configOp)
|
|
||||||
}
|
|
||||||
|
|
||||||
configReq := &structs.ConfigEntryRequest{
|
|
||||||
Datacenter: args.Datacenter,
|
|
||||||
WriteRequest: args.WriteRequest,
|
|
||||||
Op: structs.ConfigEntryUpsertCAS,
|
|
||||||
Entry: configEntry,
|
|
||||||
}
|
|
||||||
|
|
||||||
var normalizeAndValidateFn func(raw structs.ConfigEntry) error
|
|
||||||
if legacyWrite {
|
|
||||||
normalizeAndValidateFn = func(raw structs.ConfigEntry) error {
|
|
||||||
entry := raw.(*structs.ServiceIntentionsConfigEntry)
|
|
||||||
if err := entry.LegacyNormalize(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if respErr, ok := resp.(error); ok {
|
||||||
return entry.LegacyValidate()
|
return respErr
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var applied bool
|
|
||||||
if err = s.configEntryEndpoint.applyInternal(configReq, &applied, normalizeAndValidateFn); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !applied {
|
|
||||||
return fmt.Errorf("config entry failed to persist due to CAS failure: kind=%q, name=%q", configEntry.Kind, configEntry.Name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -203,14 +154,11 @@ func (s *Intention) computeApplyChangesLegacyCreate(
|
||||||
authz acl.Authorizer,
|
authz acl.Authorizer,
|
||||||
entMeta *structs.EnterpriseMeta,
|
entMeta *structs.EnterpriseMeta,
|
||||||
args *structs.IntentionRequest,
|
args *structs.IntentionRequest,
|
||||||
) (structs.ConfigEntryOp, *structs.ServiceIntentionsConfigEntry, error) {
|
) (*structs.IntentionMutation, error) {
|
||||||
// This variant is just for legacy UUID-based intentions.
|
// This variant is just for legacy UUID-based intentions.
|
||||||
|
|
||||||
args.Intention.DefaultNamespaces(entMeta)
|
args.Intention.DefaultNamespaces(entMeta)
|
||||||
|
|
||||||
// Even though the eventual config entry RPC will do an authz check and
|
|
||||||
// validation, if we do them here too we can generate error messages that
|
|
||||||
// make more sense for legacy edits.
|
|
||||||
if !args.Intention.CanWrite(authz) {
|
if !args.Intention.CanWrite(authz) {
|
||||||
sn := args.Intention.SourceServiceName()
|
sn := args.Intention.SourceServiceName()
|
||||||
dn := args.Intention.DestinationServiceName()
|
dn := args.Intention.DestinationServiceName()
|
||||||
|
@ -219,7 +167,7 @@ func (s *Intention) computeApplyChangesLegacyCreate(
|
||||||
"source", sn.String(),
|
"source", sn.String(),
|
||||||
"destination", dn.String(),
|
"destination", dn.String(),
|
||||||
"accessorID", accessorID)
|
"accessorID", accessorID)
|
||||||
return "", nil, acl.ErrPermissionDenied
|
return nil, acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no ID is provided, generate a new ID. This must be done prior to
|
// If no ID is provided, generate a new ID. This must be done prior to
|
||||||
|
@ -227,13 +175,13 @@ func (s *Intention) computeApplyChangesLegacyCreate(
|
||||||
// the entry is in the log, the state update MUST be deterministic or
|
// the entry is in the log, the state update MUST be deterministic or
|
||||||
// the followers will not converge.
|
// the followers will not converge.
|
||||||
if args.Intention.ID != "" {
|
if args.Intention.ID != "" {
|
||||||
return "", nil, fmt.Errorf("ID must be empty when creating a new intention")
|
return nil, fmt.Errorf("ID must be empty when creating a new intention")
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
args.Intention.ID, err = lib.GenerateUUID(s.checkIntentionID)
|
args.Intention.ID, err = lib.GenerateUUID(s.checkIntentionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Set the created at
|
// Set the created at
|
||||||
args.Intention.CreatedAt = time.Now().UTC()
|
args.Intention.CreatedAt = time.Now().UTC()
|
||||||
|
@ -245,36 +193,30 @@ func (s *Intention) computeApplyChangesLegacyCreate(
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.validateEnterpriseIntention(args.Intention); err != nil {
|
if err := s.validateEnterpriseIntention(args.Intention); err != nil {
|
||||||
return "", nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
if err := args.Intention.Validate(); err != nil {
|
if err := args.Intention.Validate(); err != nil {
|
||||||
return "", nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, configEntry, err := s.srv.fsm.State().ConfigEntry(nil, structs.ServiceIntentions, args.Intention.DestinationName, args.Intention.DestinationEnterpriseMeta())
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, fmt.Errorf("service-intentions config entry lookup failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if configEntry == nil {
|
|
||||||
return structs.ConfigEntryUpsertCAS, args.Intention.ToConfigEntry(true), nil
|
|
||||||
}
|
|
||||||
prevEntry := configEntry.(*structs.ServiceIntentionsConfigEntry)
|
|
||||||
|
|
||||||
if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
upsertEntry := prevEntry.Clone()
|
|
||||||
upsertEntry.Sources = append(upsertEntry.Sources, args.Intention.ToSourceIntention(true))
|
|
||||||
|
|
||||||
// NOTE: if the append of this source causes a duplicate source name the
|
// NOTE: if the append of this source causes a duplicate source name the
|
||||||
// config entry validation will fail so we don't have to check that
|
// config entry validation will fail so we don't have to check that
|
||||||
// explicitly here.
|
// explicitly here.
|
||||||
|
|
||||||
return structs.ConfigEntryUpsertCAS, upsertEntry, nil
|
mut := &structs.IntentionMutation{
|
||||||
|
Destination: args.Intention.DestinationServiceName(),
|
||||||
|
Value: args.Intention.ToSourceIntention(true),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the created/updated times. If this is an update instead of an insert
|
||||||
|
// the UpdateOver() will fix it up appropriately.
|
||||||
|
now := time.Now().UTC()
|
||||||
|
mut.Value.LegacyCreateTime = timePointer(now)
|
||||||
|
mut.Value.LegacyUpdateTime = timePointer(now)
|
||||||
|
|
||||||
|
return mut, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Intention) computeApplyChangesLegacyUpdate(
|
func (s *Intention) computeApplyChangesLegacyUpdate(
|
||||||
|
@ -282,28 +224,21 @@ func (s *Intention) computeApplyChangesLegacyUpdate(
|
||||||
authz acl.Authorizer,
|
authz acl.Authorizer,
|
||||||
entMeta *structs.EnterpriseMeta,
|
entMeta *structs.EnterpriseMeta,
|
||||||
args *structs.IntentionRequest,
|
args *structs.IntentionRequest,
|
||||||
) (structs.ConfigEntryOp, *structs.ServiceIntentionsConfigEntry, error) {
|
) (*structs.IntentionMutation, error) {
|
||||||
// This variant is just for legacy UUID-based intentions.
|
// This variant is just for legacy UUID-based intentions.
|
||||||
|
|
||||||
_, prevEntry, ixn, err := s.srv.fsm.State().IntentionGet(nil, args.Intention.ID)
|
_, _, ixn, err := s.srv.fsm.State().IntentionGet(nil, args.Intention.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, fmt.Errorf("Intention lookup failed: %v", err)
|
return nil, fmt.Errorf("Intention lookup failed: %v", err)
|
||||||
}
|
}
|
||||||
if ixn == nil || prevEntry == nil {
|
if ixn == nil {
|
||||||
return "", nil, fmt.Errorf("Cannot modify non-existent intention: '%s'", args.Intention.ID)
|
return nil, fmt.Errorf("Cannot modify non-existent intention: '%s'", args.Intention.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Even though the eventual config entry RPC will do an authz check and
|
|
||||||
// validation, if we do them here too we can generate error messages that
|
|
||||||
// make more sense for legacy edits.
|
|
||||||
if !ixn.CanWrite(authz) {
|
if !ixn.CanWrite(authz) {
|
||||||
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
|
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
|
||||||
s.logger.Warn("Update operation on intention denied due to ACLs", "intention", args.Intention.ID, "accessorID", accessorID)
|
s.logger.Warn("Update operation on intention denied due to ACLs", "intention", args.Intention.ID, "accessorID", accessorID)
|
||||||
return "", nil, acl.ErrPermissionDenied
|
return nil, acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
args.Intention.DefaultNamespaces(entMeta)
|
args.Intention.DefaultNamespaces(entMeta)
|
||||||
|
@ -311,76 +246,77 @@ func (s *Intention) computeApplyChangesLegacyUpdate(
|
||||||
// Prior to v1.9.0 renames of the destination side of an intention were
|
// Prior to v1.9.0 renames of the destination side of an intention were
|
||||||
// allowed, but that behavior doesn't work anymore.
|
// allowed, but that behavior doesn't work anymore.
|
||||||
if ixn.DestinationServiceName() != args.Intention.DestinationServiceName() {
|
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 DestinationNS or DestinationName for an intention once it exists.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We always update the updatedat field.
|
|
||||||
args.Intention.UpdatedAt = time.Now().UTC()
|
|
||||||
|
|
||||||
// Default source type
|
// Default source type
|
||||||
if args.Intention.SourceType == "" {
|
if args.Intention.SourceType == "" {
|
||||||
args.Intention.SourceType = structs.IntentionSourceConsul
|
args.Intention.SourceType = structs.IntentionSourceConsul
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.validateEnterpriseIntention(args.Intention); err != nil {
|
if err := s.validateEnterpriseIntention(args.Intention); err != nil {
|
||||||
return "", nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate. We do not validate on delete since it is valid to only
|
// Validate. We do not validate on delete since it is valid to only
|
||||||
// send an ID in that case.
|
// send an ID in that case.
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
if err := args.Intention.Validate(); err != nil {
|
if err := args.Intention.Validate(); err != nil {
|
||||||
return "", nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
upsertEntry := prevEntry.Clone()
|
mut := &structs.IntentionMutation{
|
||||||
|
ID: args.Intention.ID,
|
||||||
foundMatch := upsertEntry.UpdateSourceByLegacyID(
|
Value: args.Intention.ToSourceIntention(true),
|
||||||
args.Intention.ID,
|
|
||||||
args.Intention.ToSourceIntention(true),
|
|
||||||
)
|
|
||||||
if !foundMatch {
|
|
||||||
return "", nil, fmt.Errorf("Cannot modify non-existent intention: '%s'", args.Intention.ID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return structs.ConfigEntryUpsertCAS, upsertEntry, nil
|
// Set the created/updated times. If this is an update instead of an insert
|
||||||
|
// the UpdateOver() will fix it up appropriately.
|
||||||
|
now := time.Now().UTC()
|
||||||
|
mut.Value.LegacyCreateTime = timePointer(now)
|
||||||
|
mut.Value.LegacyUpdateTime = timePointer(now)
|
||||||
|
|
||||||
|
return mut, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Intention) computeApplyChangesUpsert(
|
func (s *Intention) computeApplyChangesUpsert(
|
||||||
|
accessorID string,
|
||||||
|
authz acl.Authorizer,
|
||||||
entMeta *structs.EnterpriseMeta,
|
entMeta *structs.EnterpriseMeta,
|
||||||
args *structs.IntentionRequest,
|
args *structs.IntentionRequest,
|
||||||
) (structs.ConfigEntryOp, *structs.ServiceIntentionsConfigEntry, error) {
|
) (*structs.IntentionMutation, error) {
|
||||||
// This variant is just for config-entry based intentions.
|
// This variant is just for config-entry based intentions.
|
||||||
|
|
||||||
if args.Intention.ID != "" {
|
if args.Intention.ID != "" {
|
||||||
// This is a new-style only endpoint
|
// This is a new-style only endpoint
|
||||||
return "", nil, fmt.Errorf("ID must not be specified")
|
return nil, fmt.Errorf("ID must not be specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
args.Intention.DefaultNamespaces(entMeta)
|
args.Intention.DefaultNamespaces(entMeta)
|
||||||
|
|
||||||
prevEntry, err := s.getServiceIntentionsConfigEntry(args.Intention.DestinationName, args.Intention.DestinationEnterpriseMeta())
|
if !args.Intention.CanWrite(authz) {
|
||||||
if err != nil {
|
sn := args.Intention.SourceServiceName()
|
||||||
return "", nil, err
|
dn := args.Intention.DestinationServiceName()
|
||||||
|
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
|
||||||
|
s.logger.Warn("Intention upsert denied due to ACLs",
|
||||||
|
"source", sn.String(),
|
||||||
|
"destination", dn.String(),
|
||||||
|
"accessorID", accessorID)
|
||||||
|
return nil, acl.ErrPermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
_, prevEntry, err := s.srv.fsm.State().ConfigEntry(nil, structs.ServiceIntentions, args.Intention.DestinationName, args.Intention.DestinationEnterpriseMeta())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Intention lookup failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(intentions): have service-intentions validation functions
|
|
||||||
// return structured errors so that we can rewrite the field prefix
|
|
||||||
// here so that the validation errors are not misleading.
|
|
||||||
if prevEntry == nil {
|
if prevEntry == nil {
|
||||||
// Meta is NOT permitted here, as it would need to be persisted on
|
// Meta is NOT permitted here, as it would need to be persisted on
|
||||||
// the enclosing config entry.
|
// the enclosing config entry.
|
||||||
if len(args.Intention.Meta) > 0 {
|
if len(args.Intention.Meta) > 0 {
|
||||||
return "", nil, fmt.Errorf("Meta must not be specified")
|
return nil, fmt.Errorf("Meta must not be specified")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
upsertEntry := args.Intention.ToConfigEntry(false)
|
|
||||||
|
|
||||||
return structs.ConfigEntryUpsertCAS, upsertEntry, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
upsertEntry := prevEntry.Clone()
|
|
||||||
|
|
||||||
if len(args.Intention.Meta) > 0 {
|
if len(args.Intention.Meta) > 0 {
|
||||||
// Meta is NOT permitted here, but there is one exception. If
|
// Meta is NOT permitted here, but there is one exception. If
|
||||||
// you are updating a previous record, but that record lives
|
// you are updating a previous record, but that record lives
|
||||||
|
@ -390,19 +326,20 @@ func (s *Intention) computeApplyChangesUpsert(
|
||||||
// In that case if Meta is provided, it has to be a perfect
|
// In that case if Meta is provided, it has to be a perfect
|
||||||
// match for what is already on the enclosing config entry so
|
// match for what is already on the enclosing config entry so
|
||||||
// it's safe to discard.
|
// it's safe to discard.
|
||||||
if !equalStringMaps(upsertEntry.Meta, args.Intention.Meta) {
|
if !equalStringMaps(prevEntry.GetMeta(), args.Intention.Meta) {
|
||||||
return "", nil, fmt.Errorf("Meta must not be specified, or should be unchanged during an update.")
|
return nil, fmt.Errorf("Meta must not be specified, or should be unchanged during an update.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now it is safe to discard
|
// Now it is safe to discard
|
||||||
args.Intention.Meta = nil
|
args.Intention.Meta = nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sn := args.Intention.SourceServiceName()
|
return &structs.IntentionMutation{
|
||||||
|
Destination: args.Intention.DestinationServiceName(),
|
||||||
upsertEntry.UpsertSourceByName(sn, args.Intention.ToSourceIntention(false))
|
Source: args.Intention.SourceServiceName(),
|
||||||
|
Value: args.Intention.ToSourceIntention(false),
|
||||||
return structs.ConfigEntryUpsertCAS, upsertEntry, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Intention) computeApplyChangesLegacyDelete(
|
func (s *Intention) computeApplyChangesLegacyDelete(
|
||||||
|
@ -410,93 +347,58 @@ func (s *Intention) computeApplyChangesLegacyDelete(
|
||||||
authz acl.Authorizer,
|
authz acl.Authorizer,
|
||||||
entMeta *structs.EnterpriseMeta,
|
entMeta *structs.EnterpriseMeta,
|
||||||
args *structs.IntentionRequest,
|
args *structs.IntentionRequest,
|
||||||
) (structs.ConfigEntryOp, *structs.ServiceIntentionsConfigEntry, error) {
|
) (*structs.IntentionMutation, error) {
|
||||||
_, prevEntry, ixn, err := s.srv.fsm.State().IntentionGet(nil, args.Intention.ID)
|
_, _, ixn, err := s.srv.fsm.State().IntentionGet(nil, args.Intention.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, fmt.Errorf("Intention lookup failed: %v", err)
|
return nil, fmt.Errorf("Intention lookup failed: %v", err)
|
||||||
}
|
}
|
||||||
if ixn == nil || prevEntry == nil {
|
if ixn == nil {
|
||||||
return "", nil, fmt.Errorf("Cannot delete non-existent intention: '%s'", args.Intention.ID)
|
return nil, fmt.Errorf("Cannot delete non-existent intention: '%s'", args.Intention.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Even though the eventual config entry RPC will do an authz check and
|
|
||||||
// validation, if we do them here too we can generate error messages that
|
|
||||||
// make more sense for legacy edits.
|
|
||||||
if !ixn.CanWrite(authz) {
|
if !ixn.CanWrite(authz) {
|
||||||
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
|
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
|
||||||
s.logger.Warn("Deletion operation on intention denied due to ACLs", "intention", args.Intention.ID, "accessorID", accessorID)
|
s.logger.Warn("Deletion operation on intention denied due to ACLs", "intention", args.Intention.ID, "accessorID", accessorID)
|
||||||
return "", nil, acl.ErrPermissionDenied
|
return nil, acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
upsertEntry := prevEntry.Clone()
|
return &structs.IntentionMutation{
|
||||||
|
ID: args.Intention.ID,
|
||||||
deleted := upsertEntry.DeleteSourceByLegacyID(args.Intention.ID)
|
|
||||||
if !deleted {
|
|
||||||
return "", nil, fmt.Errorf("Cannot delete non-existent intention: '%s'", args.Intention.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if upsertEntry == nil || len(upsertEntry.Sources) == 0 {
|
|
||||||
return structs.ConfigEntryDelete, &structs.ServiceIntentionsConfigEntry{
|
|
||||||
Kind: structs.ServiceIntentions,
|
|
||||||
Name: prevEntry.Name,
|
|
||||||
EnterpriseMeta: prevEntry.EnterpriseMeta,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
|
||||||
|
|
||||||
return structs.ConfigEntryUpsertCAS, upsertEntry, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Intention) computeApplyChangesDelete(
|
func (s *Intention) computeApplyChangesDelete(
|
||||||
|
accessorID string,
|
||||||
|
authz acl.Authorizer,
|
||||||
entMeta *structs.EnterpriseMeta,
|
entMeta *structs.EnterpriseMeta,
|
||||||
args *structs.IntentionRequest,
|
args *structs.IntentionRequest,
|
||||||
) (structs.ConfigEntryOp, *structs.ServiceIntentionsConfigEntry, error) {
|
) (*structs.IntentionMutation, error) {
|
||||||
args.Intention.DefaultNamespaces(entMeta)
|
args.Intention.DefaultNamespaces(entMeta)
|
||||||
|
|
||||||
prevEntry, err := s.getServiceIntentionsConfigEntry(args.Intention.DestinationName, args.Intention.DestinationEnterpriseMeta())
|
if !args.Intention.CanWrite(authz) {
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if prevEntry == nil {
|
|
||||||
return "", nil, nil // no op means no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: validation errors may be misleading!
|
|
||||||
|
|
||||||
upsertEntry := prevEntry.Clone()
|
|
||||||
|
|
||||||
sn := args.Intention.SourceServiceName()
|
sn := args.Intention.SourceServiceName()
|
||||||
|
dn := args.Intention.DestinationServiceName()
|
||||||
deleted := upsertEntry.DeleteSourceByName(sn)
|
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
|
||||||
if !deleted {
|
s.logger.Warn("Intention delete denied due to ACLs",
|
||||||
return "", nil, nil // no op means no-op
|
"source", sn.String(),
|
||||||
|
"destination", dn.String(),
|
||||||
|
"accessorID", accessorID)
|
||||||
|
return nil, acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
if upsertEntry == nil || len(upsertEntry.Sources) == 0 {
|
// Pre-flight to avoid pointless raft operations.
|
||||||
return structs.ConfigEntryDelete, &structs.ServiceIntentionsConfigEntry{
|
_, _, ixn, err := s.srv.fsm.State().IntentionGetExact(nil, args.Intention.ToExact())
|
||||||
Kind: structs.ServiceIntentions,
|
if err != nil {
|
||||||
Name: prevEntry.Name,
|
return nil, fmt.Errorf("Intention lookup failed: %v", err)
|
||||||
EnterpriseMeta: prevEntry.EnterpriseMeta,
|
}
|
||||||
|
if ixn == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &structs.IntentionMutation{
|
||||||
|
Destination: args.Intention.DestinationServiceName(),
|
||||||
|
Source: args.Intention.SourceServiceName(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
|
||||||
|
|
||||||
return structs.ConfigEntryUpsertCAS, upsertEntry, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkLegacyIntentionApplyAllowed(prevEntry *structs.ServiceIntentionsConfigEntry) error {
|
|
||||||
if prevEntry == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if prevEntry.LegacyIDFieldsAreAllSet() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
sn := prevEntry.DestinationServiceName()
|
|
||||||
return fmt.Errorf("cannot use legacy intention API to edit intentions with a destination of %q after editing them via a service-intentions config entry", sn.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns a single intention by ID.
|
// Get returns a single intention by ID.
|
||||||
|
@ -835,23 +737,6 @@ func (s *Intention) validateEnterpriseIntention(ixn *structs.Intention) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Intention) getServiceIntentionsConfigEntry(name string, entMeta *structs.EnterpriseMeta) (*structs.ServiceIntentionsConfigEntry, error) {
|
|
||||||
_, raw, err := s.srv.fsm.State().ConfigEntry(nil, structs.ServiceIntentions, name, entMeta)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Intention lookup failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if raw == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
configEntry, ok := raw.(*structs.ServiceIntentionsConfigEntry)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("invalid service config type %T", raw)
|
|
||||||
}
|
|
||||||
return configEntry, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalStringMaps(a, b map[string]string) bool {
|
func equalStringMaps(a, b map[string]string) bool {
|
||||||
if len(a) != len(b) {
|
if len(a) != len(b) {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -11,7 +11,7 @@ func init() {
|
||||||
registerEndpoint(func(s *Server) interface{} { return &FederationState{s} })
|
registerEndpoint(func(s *Server) interface{} { return &FederationState{s} })
|
||||||
registerEndpoint(func(s *Server) interface{} { return &DiscoveryChain{s} })
|
registerEndpoint(func(s *Server) interface{} { return &DiscoveryChain{s} })
|
||||||
registerEndpoint(func(s *Server) interface{} { return &Health{s} })
|
registerEndpoint(func(s *Server) interface{} { return &Health{s} })
|
||||||
registerEndpoint(func(s *Server) interface{} { return NewIntentionEndpoint(s, s.loggers.Named(logging.Intentions)) })
|
registerEndpoint(func(s *Server) interface{} { return &Intention{s, s.loggers.Named(logging.Intentions)} })
|
||||||
registerEndpoint(func(s *Server) interface{} { return &Internal{s, s.loggers.Named(logging.Internal)} })
|
registerEndpoint(func(s *Server) interface{} { return &Internal{s, s.loggers.Named(logging.Internal)} })
|
||||||
registerEndpoint(func(s *Server) interface{} { return &KVS{s, s.loggers.Named(logging.KV)} })
|
registerEndpoint(func(s *Server) interface{} { return &KVS{s, s.loggers.Named(logging.KV)} })
|
||||||
registerEndpoint(func(s *Server) interface{} { return &Operator{s, s.loggers.Named(logging.Operator)} })
|
registerEndpoint(func(s *Server) interface{} { return &Operator{s, s.loggers.Named(logging.Operator)} })
|
||||||
|
|
|
@ -180,7 +180,7 @@ func (s *Store) EnsureConfigEntry(idx uint64, conf structs.ConfigEntry, entMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensureConfigEntryTxn upserts a config entry inside of a transaction.
|
// ensureConfigEntryTxn upserts a config entry inside of a transaction.
|
||||||
func ensureConfigEntryTxn(tx *txn, idx uint64, conf structs.ConfigEntry, entMeta *structs.EnterpriseMeta) error {
|
func ensureConfigEntryTxn(tx WriteTxn, idx uint64, conf structs.ConfigEntry, entMeta *structs.EnterpriseMeta) error {
|
||||||
// Check for existing configuration.
|
// Check for existing configuration.
|
||||||
existing, err := firstConfigEntryWithTxn(tx, conf.GetKind(), conf.GetName(), entMeta)
|
existing, err := firstConfigEntryWithTxn(tx, conf.GetKind(), conf.GetName(), entMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -254,6 +254,14 @@ func (s *Store) DeleteConfigEntry(idx uint64, kind, name string, entMeta *struct
|
||||||
tx := s.db.WriteTxn(idx)
|
tx := s.db.WriteTxn(idx)
|
||||||
defer tx.Abort()
|
defer tx.Abort()
|
||||||
|
|
||||||
|
if err := deleteConfigEntryTxn(tx, idx, kind, name, entMeta); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteConfigEntryTxn(tx WriteTxn, idx uint64, kind, name string, entMeta *structs.EnterpriseMeta) error {
|
||||||
// Try to retrieve the existing config entry.
|
// Try to retrieve the existing config entry.
|
||||||
existing, err := firstConfigEntryWithTxn(tx, kind, name, entMeta)
|
existing, err := firstConfigEntryWithTxn(tx, kind, name, entMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -298,10 +306,10 @@ func (s *Store) DeleteConfigEntry(idx uint64, kind, name string, entMeta *struct
|
||||||
return fmt.Errorf("failed updating index: %s", err)
|
return fmt.Errorf("failed updating index: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tx.Commit()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func insertConfigEntryWithTxn(tx *txn, idx uint64, conf structs.ConfigEntry) error {
|
func insertConfigEntryWithTxn(tx WriteTxn, idx uint64, conf structs.ConfigEntry) error {
|
||||||
if conf == nil {
|
if conf == nil {
|
||||||
return fmt.Errorf("cannot insert nil config entry")
|
return fmt.Errorf("cannot insert nil config entry")
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,6 +207,294 @@ func (s *Store) legacyIntentionsListTxn(tx ReadTxn, ws memdb.WatchSet, entMeta *
|
||||||
|
|
||||||
var ErrLegacyIntentionsAreDisabled = errors.New("Legacy intention modifications are disabled after the config entry migration.")
|
var ErrLegacyIntentionsAreDisabled = errors.New("Legacy intention modifications are disabled after the config entry migration.")
|
||||||
|
|
||||||
|
func (s *Store) IntentionMutation(idx uint64, op structs.IntentionOp, mut *structs.IntentionMutation) error {
|
||||||
|
tx := s.db.WriteTxn(idx)
|
||||||
|
defer tx.Abort()
|
||||||
|
|
||||||
|
usingConfigEntries, err := areIntentionsInConfigEntries(tx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !usingConfigEntries {
|
||||||
|
return errors.New("state: IntentionMutation() is not allowed when intentions are not stored in config entries")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch op {
|
||||||
|
case structs.IntentionOpCreate:
|
||||||
|
if err := s.intentionMutationLegacyCreate(tx, idx, mut.Destination, mut.Value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case structs.IntentionOpUpdate:
|
||||||
|
if err := s.intentionMutationLegacyUpdate(tx, idx, mut.ID, mut.Value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case structs.IntentionOpDelete:
|
||||||
|
if mut.ID == "" {
|
||||||
|
if err := s.intentionMutationDelete(tx, idx, mut.Destination, mut.Source); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := s.intentionMutationLegacyDelete(tx, idx, mut.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case structs.IntentionOpUpsert:
|
||||||
|
if err := s.intentionMutationUpsert(tx, idx, mut.Destination, mut.Source, mut.Value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case structs.IntentionOpDeleteAll:
|
||||||
|
// This is an internal operation initiated by the leader and is not
|
||||||
|
// exposed for general RPC use.
|
||||||
|
return fmt.Errorf("Invalid Intention mutation operation '%s'", op)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Invalid Intention mutation operation '%s'", op)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) intentionMutationLegacyCreate(
|
||||||
|
tx WriteTxn,
|
||||||
|
idx uint64,
|
||||||
|
dest structs.ServiceName,
|
||||||
|
value *structs.SourceIntention,
|
||||||
|
) error {
|
||||||
|
_, configEntry, err := configEntryTxn(tx, nil, structs.ServiceIntentions, dest.Name, &dest.EnterpriseMeta)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("service-intentions config entry lookup failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var upsertEntry *structs.ServiceIntentionsConfigEntry
|
||||||
|
if configEntry == nil {
|
||||||
|
upsertEntry = &structs.ServiceIntentionsConfigEntry{
|
||||||
|
Kind: structs.ServiceIntentions,
|
||||||
|
Name: dest.Name,
|
||||||
|
EnterpriseMeta: dest.EnterpriseMeta,
|
||||||
|
Sources: []*structs.SourceIntention{value},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prevEntry := configEntry.(*structs.ServiceIntentionsConfigEntry)
|
||||||
|
|
||||||
|
if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
upsertEntry = prevEntry.Clone()
|
||||||
|
upsertEntry.Sources = append(upsertEntry.Sources, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := upsertEntry.LegacyNormalize(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := upsertEntry.LegacyValidate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ensureConfigEntryTxn(tx, idx, upsertEntry, upsertEntry.GetEnterpriseMeta()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) intentionMutationLegacyUpdate(
|
||||||
|
tx WriteTxn,
|
||||||
|
idx uint64,
|
||||||
|
legacyID string,
|
||||||
|
value *structs.SourceIntention,
|
||||||
|
) error {
|
||||||
|
// This variant is just for legacy UUID-based intentions.
|
||||||
|
|
||||||
|
_, prevEntry, ixn, err := s.IntentionGet(nil, legacyID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Intention lookup failed: %v", err)
|
||||||
|
}
|
||||||
|
if ixn == nil || prevEntry == nil {
|
||||||
|
return fmt.Errorf("Cannot modify non-existent intention: '%s'", legacyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
upsertEntry := prevEntry.Clone()
|
||||||
|
|
||||||
|
foundMatch := upsertEntry.UpdateSourceByLegacyID(
|
||||||
|
legacyID,
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
if !foundMatch {
|
||||||
|
return fmt.Errorf("Cannot modify non-existent intention: '%s'", legacyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := upsertEntry.LegacyNormalize(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := upsertEntry.LegacyValidate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ensureConfigEntryTxn(tx, idx, upsertEntry, upsertEntry.GetEnterpriseMeta()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) intentionMutationDelete(
|
||||||
|
tx WriteTxn,
|
||||||
|
idx uint64,
|
||||||
|
dest structs.ServiceName,
|
||||||
|
src structs.ServiceName,
|
||||||
|
) error {
|
||||||
|
_, configEntry, err := configEntryTxn(tx, nil, structs.ServiceIntentions, dest.Name, &dest.EnterpriseMeta)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("service-intentions config entry lookup failed: %v", err)
|
||||||
|
}
|
||||||
|
if configEntry == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
prevEntry := configEntry.(*structs.ServiceIntentionsConfigEntry)
|
||||||
|
upsertEntry := prevEntry.Clone()
|
||||||
|
|
||||||
|
deleted := upsertEntry.DeleteSourceByName(src)
|
||||||
|
if !deleted {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if upsertEntry == nil || len(upsertEntry.Sources) == 0 {
|
||||||
|
return deleteConfigEntryTxn(
|
||||||
|
tx,
|
||||||
|
idx,
|
||||||
|
structs.ServiceIntentions,
|
||||||
|
dest.Name,
|
||||||
|
&dest.EnterpriseMeta,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := upsertEntry.Normalize(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := upsertEntry.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ensureConfigEntryTxn(tx, idx, upsertEntry, upsertEntry.GetEnterpriseMeta()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) intentionMutationLegacyDelete(
|
||||||
|
tx WriteTxn,
|
||||||
|
idx uint64,
|
||||||
|
legacyID string,
|
||||||
|
) error {
|
||||||
|
_, prevEntry, ixn, err := s.IntentionGet(nil, legacyID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Intention lookup failed: %v", err)
|
||||||
|
}
|
||||||
|
if ixn == nil || prevEntry == nil {
|
||||||
|
return fmt.Errorf("Cannot delete non-existent intention: '%s'", legacyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
upsertEntry := prevEntry.Clone()
|
||||||
|
|
||||||
|
deleted := upsertEntry.DeleteSourceByLegacyID(legacyID)
|
||||||
|
if !deleted {
|
||||||
|
return fmt.Errorf("Cannot delete non-existent intention: '%s'", legacyID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if upsertEntry == nil || len(upsertEntry.Sources) == 0 {
|
||||||
|
return deleteConfigEntryTxn(
|
||||||
|
tx,
|
||||||
|
idx,
|
||||||
|
structs.ServiceIntentions,
|
||||||
|
prevEntry.Name,
|
||||||
|
&prevEntry.EnterpriseMeta,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := upsertEntry.LegacyNormalize(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := upsertEntry.LegacyValidate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ensureConfigEntryTxn(tx, idx, upsertEntry, upsertEntry.GetEnterpriseMeta()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) intentionMutationUpsert(
|
||||||
|
tx WriteTxn,
|
||||||
|
idx uint64,
|
||||||
|
dest structs.ServiceName,
|
||||||
|
src structs.ServiceName,
|
||||||
|
value *structs.SourceIntention,
|
||||||
|
) error {
|
||||||
|
// This variant is just for config-entry based intentions.
|
||||||
|
|
||||||
|
_, configEntry, err := configEntryTxn(tx, nil, structs.ServiceIntentions, dest.Name, &dest.EnterpriseMeta)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("service-intentions config entry lookup failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var prevEntry *structs.ServiceIntentionsConfigEntry
|
||||||
|
if configEntry != nil {
|
||||||
|
prevEntry = configEntry.(*structs.ServiceIntentionsConfigEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
var upsertEntry *structs.ServiceIntentionsConfigEntry
|
||||||
|
|
||||||
|
if prevEntry == nil {
|
||||||
|
upsertEntry = &structs.ServiceIntentionsConfigEntry{
|
||||||
|
Kind: structs.ServiceIntentions,
|
||||||
|
Name: dest.Name,
|
||||||
|
EnterpriseMeta: dest.EnterpriseMeta,
|
||||||
|
Sources: []*structs.SourceIntention{value},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
upsertEntry = prevEntry.Clone()
|
||||||
|
|
||||||
|
upsertEntry.UpsertSourceByName(src, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := upsertEntry.Normalize(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := upsertEntry.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ensureConfigEntryTxn(tx, idx, upsertEntry, upsertEntry.GetEnterpriseMeta()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkLegacyIntentionApplyAllowed(prevEntry *structs.ServiceIntentionsConfigEntry) error {
|
||||||
|
if prevEntry == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if prevEntry.LegacyIDFieldsAreAllSet() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sn := prevEntry.DestinationServiceName()
|
||||||
|
return fmt.Errorf("cannot use legacy intention API to edit intentions with a destination of %q after editing them via a service-intentions config entry", sn.String())
|
||||||
|
}
|
||||||
|
|
||||||
// LegacyIntentionSet creates or updates an intention.
|
// LegacyIntentionSet creates or updates an intention.
|
||||||
//
|
//
|
||||||
// Deprecated: Edit service-intentions config entries directly.
|
// Deprecated: Edit service-intentions config entries directly.
|
||||||
|
|
|
@ -13,6 +13,14 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testLocation = time.FixedZone("UTC-8", -8*60*60)
|
||||||
|
|
||||||
|
testTimeA = time.Date(1955, 11, 5, 6, 15, 0, 0, testLocation)
|
||||||
|
testTimeB = time.Date(1985, 10, 26, 1, 35, 0, 0, testLocation)
|
||||||
|
testTimeC = time.Date(2015, 10, 21, 16, 29, 0, 0, testLocation)
|
||||||
|
)
|
||||||
|
|
||||||
func testBothIntentionFormats(t *testing.T, f func(t *testing.T, s *Store, legacy bool)) {
|
func testBothIntentionFormats(t *testing.T, f func(t *testing.T, s *Store, legacy bool)) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
@ -72,6 +80,8 @@ func TestStore_IntentionSetGet_basic(t *testing.T) {
|
||||||
DestinationNS: "default",
|
DestinationNS: "default",
|
||||||
DestinationName: "web",
|
DestinationName: "web",
|
||||||
Meta: map[string]string{},
|
Meta: map[string]string{},
|
||||||
|
CreatedAt: testTimeA,
|
||||||
|
UpdatedAt: testTimeA,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inserting a with empty ID is disallowed.
|
// Inserting a with empty ID is disallowed.
|
||||||
|
@ -89,6 +99,8 @@ func TestStore_IntentionSetGet_basic(t *testing.T) {
|
||||||
DestinationNS: "default",
|
DestinationNS: "default",
|
||||||
DestinationName: "web",
|
DestinationName: "web",
|
||||||
Meta: map[string]string{},
|
Meta: map[string]string{},
|
||||||
|
CreatedAt: testTimeA,
|
||||||
|
UpdatedAt: testTimeA,
|
||||||
RaftIndex: structs.RaftIndex{
|
RaftIndex: structs.RaftIndex{
|
||||||
CreateIndex: lastIndex,
|
CreateIndex: lastIndex,
|
||||||
ModifyIndex: lastIndex,
|
ModifyIndex: lastIndex,
|
||||||
|
@ -107,6 +119,8 @@ func TestStore_IntentionSetGet_basic(t *testing.T) {
|
||||||
Name: "*",
|
Name: "*",
|
||||||
Action: structs.IntentionActionAllow,
|
Action: structs.IntentionActionAllow,
|
||||||
LegacyMeta: map[string]string{},
|
LegacyMeta: map[string]string{},
|
||||||
|
LegacyCreateTime: &testTimeA,
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -258,6 +272,534 @@ func TestStore_LegacyIntentionDelete_failsAfterUpgrade(t *testing.T) {
|
||||||
testutil.RequireErrorContains(t, err, ErrLegacyIntentionsAreDisabled.Error())
|
testutil.RequireErrorContains(t, err, ErrLegacyIntentionsAreDisabled.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStore_IntentionMutation(t *testing.T) {
|
||||||
|
testBothIntentionFormats(t, func(t *testing.T, s *Store, legacy bool) {
|
||||||
|
if legacy {
|
||||||
|
mut := &structs.IntentionMutation{}
|
||||||
|
err := s.IntentionMutation(1, structs.IntentionOpCreate, mut)
|
||||||
|
testutil.RequireErrorContains(t, err, "state: IntentionMutation() is not allowed when intentions are not stored in config entries")
|
||||||
|
} else {
|
||||||
|
testStore_IntentionMutation(t, s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testStore_IntentionMutation(t *testing.T, s *Store) {
|
||||||
|
lastIndex := uint64(1)
|
||||||
|
|
||||||
|
defaultEntMeta := structs.DefaultEnterpriseMeta()
|
||||||
|
|
||||||
|
var (
|
||||||
|
id1 = testUUID()
|
||||||
|
id2 = testUUID()
|
||||||
|
id3 = testUUID()
|
||||||
|
)
|
||||||
|
|
||||||
|
eqEntry := func(t *testing.T, expect, got *structs.ServiceIntentionsConfigEntry) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
// Zero out some fields for comparison.
|
||||||
|
got = got.Clone()
|
||||||
|
got.RaftIndex = structs.RaftIndex{}
|
||||||
|
for _, src := range got.Sources {
|
||||||
|
src.LegacyCreateTime = nil
|
||||||
|
src.LegacyUpdateTime = nil
|
||||||
|
if len(src.LegacyMeta) == 0 {
|
||||||
|
src.LegacyMeta = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.Equal(t, expect, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to create an intention without an ID to prove that LegacyValidate is being called.
|
||||||
|
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpCreate, &structs.IntentionMutation{
|
||||||
|
Destination: structs.NewServiceName("api", defaultEntMeta),
|
||||||
|
Value: &structs.SourceIntention{
|
||||||
|
Name: "web",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
LegacyCreateTime: &testTimeA,
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
|
},
|
||||||
|
}), `Sources[0].LegacyID must be set`)
|
||||||
|
|
||||||
|
// Create intention and create config entry
|
||||||
|
{
|
||||||
|
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpCreate, &structs.IntentionMutation{
|
||||||
|
Destination: structs.NewServiceName("api", defaultEntMeta),
|
||||||
|
Value: &structs.SourceIntention{
|
||||||
|
Name: "web",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
LegacyID: id1,
|
||||||
|
LegacyCreateTime: &testTimeA,
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Ensure it's there now.
|
||||||
|
idx, entry, ixn, err := s.IntentionGet(nil, id1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, entry)
|
||||||
|
require.NotNil(t, ixn)
|
||||||
|
require.Equal(t, lastIndex, idx)
|
||||||
|
|
||||||
|
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
|
||||||
|
Kind: structs.ServiceIntentions,
|
||||||
|
Name: "api",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Sources: []*structs.SourceIntention{
|
||||||
|
{
|
||||||
|
LegacyID: id1,
|
||||||
|
Name: "web",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
Precedence: 9,
|
||||||
|
Type: structs.IntentionSourceConsul,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, entry)
|
||||||
|
|
||||||
|
// only one
|
||||||
|
_, entries, err := s.ConfigEntries(nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, entries, 1)
|
||||||
|
|
||||||
|
lastIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to create a duplicate intention.
|
||||||
|
{
|
||||||
|
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpCreate, &structs.IntentionMutation{
|
||||||
|
Destination: structs.NewServiceName("api", defaultEntMeta),
|
||||||
|
Value: &structs.SourceIntention{
|
||||||
|
Name: "web",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionDeny,
|
||||||
|
LegacyID: id2,
|
||||||
|
LegacyCreateTime: &testTimeB,
|
||||||
|
LegacyUpdateTime: &testTimeB,
|
||||||
|
},
|
||||||
|
}), `more than once`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create intention with existing config entry
|
||||||
|
{
|
||||||
|
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpCreate, &structs.IntentionMutation{
|
||||||
|
Destination: structs.NewServiceName("api", defaultEntMeta),
|
||||||
|
Value: &structs.SourceIntention{
|
||||||
|
Name: "debug",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionDeny,
|
||||||
|
LegacyID: id2,
|
||||||
|
LegacyCreateTime: &testTimeB,
|
||||||
|
LegacyUpdateTime: &testTimeB,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Ensure it's there now.
|
||||||
|
idx, entry, ixn, err := s.IntentionGet(nil, id2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, entry)
|
||||||
|
require.NotNil(t, ixn)
|
||||||
|
require.Equal(t, lastIndex, idx)
|
||||||
|
|
||||||
|
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
|
||||||
|
Kind: structs.ServiceIntentions,
|
||||||
|
Name: "api",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Sources: []*structs.SourceIntention{
|
||||||
|
{
|
||||||
|
Name: "web",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
LegacyID: id1,
|
||||||
|
Precedence: 9,
|
||||||
|
Type: structs.IntentionSourceConsul,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "debug",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionDeny,
|
||||||
|
LegacyID: id2,
|
||||||
|
Precedence: 9,
|
||||||
|
Type: structs.IntentionSourceConsul,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, entry)
|
||||||
|
|
||||||
|
// only one
|
||||||
|
_, entries, err := s.ConfigEntries(nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, entries, 1)
|
||||||
|
|
||||||
|
lastIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to update an intention without specifying an ID
|
||||||
|
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpdate, &structs.IntentionMutation{
|
||||||
|
ID: "",
|
||||||
|
Destination: structs.NewServiceName("api", defaultEntMeta),
|
||||||
|
Value: &structs.SourceIntention{
|
||||||
|
Name: "web",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
},
|
||||||
|
}), `failed config entry lookup: index error: UUID must be 36 characters`)
|
||||||
|
|
||||||
|
// Try to update a non-existent intention
|
||||||
|
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpdate, &structs.IntentionMutation{
|
||||||
|
ID: id3,
|
||||||
|
Destination: structs.NewServiceName("api", defaultEntMeta),
|
||||||
|
Value: &structs.SourceIntention{
|
||||||
|
Name: "web",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
},
|
||||||
|
}), `Cannot modify non-existent intention`)
|
||||||
|
|
||||||
|
// Update an existing intention by ID
|
||||||
|
{
|
||||||
|
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpdate, &structs.IntentionMutation{
|
||||||
|
ID: id2,
|
||||||
|
Destination: structs.NewServiceName("api", defaultEntMeta),
|
||||||
|
Value: &structs.SourceIntention{
|
||||||
|
Name: "debug",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionDeny,
|
||||||
|
LegacyID: id2,
|
||||||
|
LegacyCreateTime: &testTimeB,
|
||||||
|
LegacyUpdateTime: &testTimeC,
|
||||||
|
Description: "op update",
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Ensure it's there now.
|
||||||
|
idx, entry, ixn, err := s.IntentionGet(nil, id2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, entry)
|
||||||
|
require.NotNil(t, ixn)
|
||||||
|
require.Equal(t, lastIndex, idx)
|
||||||
|
|
||||||
|
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
|
||||||
|
Kind: structs.ServiceIntentions,
|
||||||
|
Name: "api",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Sources: []*structs.SourceIntention{
|
||||||
|
{
|
||||||
|
Name: "web",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
LegacyID: id1,
|
||||||
|
Precedence: 9,
|
||||||
|
Type: structs.IntentionSourceConsul,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "debug",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionDeny,
|
||||||
|
LegacyID: id2,
|
||||||
|
Precedence: 9,
|
||||||
|
Type: structs.IntentionSourceConsul,
|
||||||
|
Description: "op update",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, entry)
|
||||||
|
|
||||||
|
// only one
|
||||||
|
_, entries, err := s.ConfigEntries(nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, entries, 1)
|
||||||
|
|
||||||
|
lastIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to delete a non-existent intention
|
||||||
|
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
|
||||||
|
ID: id3,
|
||||||
|
}), `Cannot delete non-existent intention`)
|
||||||
|
|
||||||
|
// delete by id
|
||||||
|
{
|
||||||
|
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
|
||||||
|
ID: id1,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// only one
|
||||||
|
_, entries, err := s.ConfigEntries(nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, entries, 1)
|
||||||
|
|
||||||
|
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
|
||||||
|
Kind: structs.ServiceIntentions,
|
||||||
|
Name: "api",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Sources: []*structs.SourceIntention{
|
||||||
|
{
|
||||||
|
Name: "debug",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionDeny,
|
||||||
|
LegacyID: id2,
|
||||||
|
Precedence: 9,
|
||||||
|
Type: structs.IntentionSourceConsul,
|
||||||
|
Description: "op update",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, entries[0].(*structs.ServiceIntentionsConfigEntry))
|
||||||
|
|
||||||
|
lastIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete last one by id
|
||||||
|
{
|
||||||
|
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
|
||||||
|
ID: id2,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// none one
|
||||||
|
_, entries, err := s.ConfigEntries(nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, entries)
|
||||||
|
|
||||||
|
lastIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
// upsert intention for first time
|
||||||
|
{
|
||||||
|
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpsert, &structs.IntentionMutation{
|
||||||
|
Destination: structs.NewServiceName("api", defaultEntMeta),
|
||||||
|
Value: &structs.SourceIntention{
|
||||||
|
Name: "web",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Ensure it's there now.
|
||||||
|
idx, entry, ixn, err := s.IntentionGetExact(nil, &structs.IntentionQueryExact{
|
||||||
|
SourceNS: "default",
|
||||||
|
SourceName: "web",
|
||||||
|
DestinationNS: "default",
|
||||||
|
DestinationName: "api",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, entry)
|
||||||
|
require.NotNil(t, ixn)
|
||||||
|
require.Equal(t, lastIndex, idx)
|
||||||
|
|
||||||
|
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
|
||||||
|
Kind: structs.ServiceIntentions,
|
||||||
|
Name: "api",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Sources: []*structs.SourceIntention{
|
||||||
|
{
|
||||||
|
Name: "web",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
Precedence: 9,
|
||||||
|
Type: structs.IntentionSourceConsul,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, entry)
|
||||||
|
|
||||||
|
// only one
|
||||||
|
_, entries, err := s.ConfigEntries(nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, entries, 1)
|
||||||
|
|
||||||
|
lastIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
// upsert over itself (REPLACE)
|
||||||
|
{
|
||||||
|
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpsert, &structs.IntentionMutation{
|
||||||
|
Destination: structs.NewServiceName("api", defaultEntMeta),
|
||||||
|
Source: structs.NewServiceName("web", defaultEntMeta),
|
||||||
|
Value: &structs.SourceIntention{
|
||||||
|
Name: "web",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
Description: "upserted over",
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Ensure it's there now.
|
||||||
|
idx, entry, ixn, err := s.IntentionGetExact(nil, &structs.IntentionQueryExact{
|
||||||
|
SourceNS: "default",
|
||||||
|
SourceName: "web",
|
||||||
|
DestinationNS: "default",
|
||||||
|
DestinationName: "api",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, entry)
|
||||||
|
require.NotNil(t, ixn)
|
||||||
|
require.Equal(t, lastIndex, idx)
|
||||||
|
require.Equal(t, "upserted over", ixn.Description)
|
||||||
|
|
||||||
|
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
|
||||||
|
Kind: structs.ServiceIntentions,
|
||||||
|
Name: "api",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Sources: []*structs.SourceIntention{
|
||||||
|
{
|
||||||
|
Name: "web",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
Precedence: 9,
|
||||||
|
Type: structs.IntentionSourceConsul,
|
||||||
|
Description: "upserted over",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, entry)
|
||||||
|
|
||||||
|
// only one
|
||||||
|
_, entries, err := s.ConfigEntries(nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, entries, 1)
|
||||||
|
|
||||||
|
lastIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
// upsert into existing config entry (APPEND)
|
||||||
|
{
|
||||||
|
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpsert, &structs.IntentionMutation{
|
||||||
|
Destination: structs.NewServiceName("api", defaultEntMeta),
|
||||||
|
Source: structs.NewServiceName("debug", defaultEntMeta),
|
||||||
|
Value: &structs.SourceIntention{
|
||||||
|
Name: "debug",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionDeny,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Ensure it's there now.
|
||||||
|
idx, entry, ixn, err := s.IntentionGetExact(nil, &structs.IntentionQueryExact{
|
||||||
|
SourceNS: "default",
|
||||||
|
SourceName: "debug",
|
||||||
|
DestinationNS: "default",
|
||||||
|
DestinationName: "api",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, entry)
|
||||||
|
require.NotNil(t, ixn)
|
||||||
|
require.Equal(t, lastIndex, idx)
|
||||||
|
|
||||||
|
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
|
||||||
|
Kind: structs.ServiceIntentions,
|
||||||
|
Name: "api",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Sources: []*structs.SourceIntention{
|
||||||
|
{
|
||||||
|
Name: "web",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
Precedence: 9,
|
||||||
|
Type: structs.IntentionSourceConsul,
|
||||||
|
Description: "upserted over",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "debug",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionDeny,
|
||||||
|
Precedence: 9,
|
||||||
|
Type: structs.IntentionSourceConsul,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, entry)
|
||||||
|
|
||||||
|
// only one
|
||||||
|
_, entries, err := s.ConfigEntries(nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, entries, 1)
|
||||||
|
|
||||||
|
lastIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to delete a non-existent intention by name
|
||||||
|
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
|
||||||
|
Destination: structs.NewServiceName("api", defaultEntMeta),
|
||||||
|
Source: structs.NewServiceName("blurb", defaultEntMeta),
|
||||||
|
}))
|
||||||
|
|
||||||
|
// delete by name
|
||||||
|
{
|
||||||
|
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
|
||||||
|
Destination: structs.NewServiceName("api", defaultEntMeta),
|
||||||
|
Source: structs.NewServiceName("web", defaultEntMeta),
|
||||||
|
}))
|
||||||
|
|
||||||
|
// only one
|
||||||
|
_, entries, err := s.ConfigEntries(nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, entries, 1)
|
||||||
|
|
||||||
|
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
|
||||||
|
Kind: structs.ServiceIntentions,
|
||||||
|
Name: "api",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Sources: []*structs.SourceIntention{
|
||||||
|
{
|
||||||
|
Name: "debug",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionDeny,
|
||||||
|
Precedence: 9,
|
||||||
|
Type: structs.IntentionSourceConsul,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, entries[0].(*structs.ServiceIntentionsConfigEntry))
|
||||||
|
|
||||||
|
lastIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete last one by name
|
||||||
|
{
|
||||||
|
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
|
||||||
|
Destination: structs.NewServiceName("api", defaultEntMeta),
|
||||||
|
Source: structs.NewServiceName("debug", defaultEntMeta),
|
||||||
|
}))
|
||||||
|
|
||||||
|
// none one
|
||||||
|
_, entries, err := s.ConfigEntries(nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Empty(t, entries)
|
||||||
|
|
||||||
|
lastIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to update an intention with an ID on a non-legacy config entry.
|
||||||
|
{
|
||||||
|
idFake := testUUID()
|
||||||
|
|
||||||
|
require.NoError(t, s.EnsureConfigEntry(lastIndex, &structs.ServiceIntentionsConfigEntry{
|
||||||
|
Kind: structs.ServiceIntentions,
|
||||||
|
Name: "new",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Sources: []*structs.SourceIntention{
|
||||||
|
{
|
||||||
|
Name: "web",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
Precedence: 9,
|
||||||
|
Type: structs.IntentionSourceConsul,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil))
|
||||||
|
|
||||||
|
lastIndex++
|
||||||
|
|
||||||
|
// ...via create
|
||||||
|
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpCreate, &structs.IntentionMutation{
|
||||||
|
Destination: structs.NewServiceName("new", defaultEntMeta),
|
||||||
|
Value: &structs.SourceIntention{
|
||||||
|
Name: "old",
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
LegacyID: idFake,
|
||||||
|
},
|
||||||
|
}), `cannot use legacy intention API to edit intentions with a destination`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStore_LegacyIntentionSet_emptyId(t *testing.T) {
|
func TestStore_LegacyIntentionSet_emptyId(t *testing.T) {
|
||||||
// note: irrelevant test for config entries variant
|
// note: irrelevant test for config entries variant
|
||||||
s := testStateStore(t)
|
s := testStateStore(t)
|
||||||
|
@ -283,23 +825,26 @@ func TestStore_IntentionSet_updateCreatedAt(t *testing.T) {
|
||||||
// Build a valid intention
|
// Build a valid intention
|
||||||
var (
|
var (
|
||||||
id = testUUID()
|
id = testUUID()
|
||||||
createTime time.Time
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if legacy {
|
if legacy {
|
||||||
ixn := structs.Intention{
|
ixn := structs.Intention{
|
||||||
ID: id,
|
ID: id,
|
||||||
CreatedAt: time.Now().UTC(),
|
SourceNS: "default",
|
||||||
|
SourceName: "*",
|
||||||
|
DestinationNS: "default",
|
||||||
|
DestinationName: "web",
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
CreatedAt: testTimeA,
|
||||||
|
UpdatedAt: testTimeA,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert
|
// Insert
|
||||||
require.NoError(t, s.LegacyIntentionSet(1, &ixn))
|
require.NoError(t, s.LegacyIntentionSet(1, &ixn))
|
||||||
|
|
||||||
createTime = ixn.CreatedAt
|
|
||||||
|
|
||||||
// Change a value and test updating
|
// Change a value and test updating
|
||||||
ixnUpdate := ixn
|
ixnUpdate := ixn
|
||||||
ixnUpdate.CreatedAt = createTime.Add(10 * time.Second)
|
ixnUpdate.CreatedAt = testTimeB
|
||||||
require.NoError(t, s.LegacyIntentionSet(2, &ixnUpdate))
|
require.NoError(t, s.LegacyIntentionSet(2, &ixnUpdate))
|
||||||
|
|
||||||
id = ixn.ID
|
id = ixn.ID
|
||||||
|
@ -314,6 +859,8 @@ func TestStore_IntentionSet_updateCreatedAt(t *testing.T) {
|
||||||
Name: "*",
|
Name: "*",
|
||||||
Action: structs.IntentionActionAllow,
|
Action: structs.IntentionActionAllow,
|
||||||
LegacyMeta: map[string]string{},
|
LegacyMeta: map[string]string{},
|
||||||
|
LegacyCreateTime: &testTimeA,
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -321,15 +868,13 @@ func TestStore_IntentionSet_updateCreatedAt(t *testing.T) {
|
||||||
require.NoError(t, conf.LegacyNormalize())
|
require.NoError(t, conf.LegacyNormalize())
|
||||||
require.NoError(t, conf.LegacyValidate())
|
require.NoError(t, conf.LegacyValidate())
|
||||||
require.NoError(t, s.EnsureConfigEntry(1, conf.Clone(), nil))
|
require.NoError(t, s.EnsureConfigEntry(1, conf.Clone(), nil))
|
||||||
|
|
||||||
createTime = *conf.Sources[0].LegacyCreateTime
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read it back and verify
|
// Read it back and verify
|
||||||
_, _, actual, err := s.IntentionGet(nil, id)
|
_, _, actual, err := s.IntentionGet(nil, id)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, actual)
|
require.NotNil(t, actual)
|
||||||
require.Equal(t, createTime, actual.CreatedAt)
|
require.Equal(t, testTimeA, actual.CreatedAt)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,6 +885,13 @@ func TestStore_IntentionSet_metaNil(t *testing.T) {
|
||||||
// Build a valid intention
|
// Build a valid intention
|
||||||
ixn := &structs.Intention{
|
ixn := &structs.Intention{
|
||||||
ID: id,
|
ID: id,
|
||||||
|
SourceNS: "default",
|
||||||
|
SourceName: "*",
|
||||||
|
DestinationNS: "default",
|
||||||
|
DestinationName: "web",
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
CreatedAt: testTimeA,
|
||||||
|
UpdatedAt: testTimeA,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert
|
// Insert
|
||||||
|
@ -354,6 +906,8 @@ func TestStore_IntentionSet_metaNil(t *testing.T) {
|
||||||
LegacyID: id,
|
LegacyID: id,
|
||||||
Name: "*",
|
Name: "*",
|
||||||
Action: structs.IntentionActionAllow,
|
Action: structs.IntentionActionAllow,
|
||||||
|
LegacyCreateTime: &testTimeA,
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -381,6 +935,13 @@ func TestStore_IntentionSet_metaSet(t *testing.T) {
|
||||||
// Build a valid intention
|
// Build a valid intention
|
||||||
ixn := structs.Intention{
|
ixn := structs.Intention{
|
||||||
ID: id,
|
ID: id,
|
||||||
|
SourceNS: "default",
|
||||||
|
SourceName: "*",
|
||||||
|
DestinationNS: "default",
|
||||||
|
DestinationName: "web",
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
CreatedAt: testTimeA,
|
||||||
|
UpdatedAt: testTimeA,
|
||||||
Meta: expectMeta,
|
Meta: expectMeta,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,6 +958,8 @@ func TestStore_IntentionSet_metaSet(t *testing.T) {
|
||||||
LegacyID: id,
|
LegacyID: id,
|
||||||
Name: "*",
|
Name: "*",
|
||||||
Action: structs.IntentionActionAllow,
|
Action: structs.IntentionActionAllow,
|
||||||
|
LegacyCreateTime: &testTimeA,
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
LegacyMeta: expectMeta,
|
LegacyMeta: expectMeta,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -429,6 +992,13 @@ func TestStore_IntentionDelete(t *testing.T) {
|
||||||
if legacy {
|
if legacy {
|
||||||
ixn := &structs.Intention{
|
ixn := &structs.Intention{
|
||||||
ID: id,
|
ID: id,
|
||||||
|
SourceNS: "default",
|
||||||
|
SourceName: "*",
|
||||||
|
DestinationNS: "default",
|
||||||
|
DestinationName: "web",
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
CreatedAt: testTimeA,
|
||||||
|
UpdatedAt: testTimeA,
|
||||||
}
|
}
|
||||||
lastIndex++
|
lastIndex++
|
||||||
require.NoError(t, s.LegacyIntentionSet(lastIndex, ixn))
|
require.NoError(t, s.LegacyIntentionSet(lastIndex, ixn))
|
||||||
|
@ -445,6 +1015,8 @@ func TestStore_IntentionDelete(t *testing.T) {
|
||||||
LegacyID: id,
|
LegacyID: id,
|
||||||
Name: "*",
|
Name: "*",
|
||||||
Action: structs.IntentionActionAllow,
|
Action: structs.IntentionActionAllow,
|
||||||
|
LegacyCreateTime: &testTimeA,
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -519,6 +1091,8 @@ func TestStore_IntentionsList(t *testing.T) {
|
||||||
SourceType: structs.IntentionSourceConsul,
|
SourceType: structs.IntentionSourceConsul,
|
||||||
Action: structs.IntentionActionAllow,
|
Action: structs.IntentionActionAllow,
|
||||||
Meta: map[string]string{},
|
Meta: map[string]string{},
|
||||||
|
CreatedAt: testTimeA,
|
||||||
|
UpdatedAt: testTimeA,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -533,19 +1107,14 @@ func TestStore_IntentionsList(t *testing.T) {
|
||||||
LegacyID: id,
|
LegacyID: id,
|
||||||
Name: src,
|
Name: src,
|
||||||
Action: structs.IntentionActionAllow,
|
Action: structs.IntentionActionAllow,
|
||||||
|
LegacyCreateTime: &testTimeA,
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return conf
|
return conf
|
||||||
}
|
}
|
||||||
|
|
||||||
cmpIntention := func(ixn *structs.Intention, id string) *structs.Intention {
|
clearIrrelevantFields := func(ixns ...*structs.Intention) {
|
||||||
ixn.ID = id
|
|
||||||
//nolint:staticcheck
|
|
||||||
ixn.UpdatePrecedence()
|
|
||||||
return ixn
|
|
||||||
}
|
|
||||||
|
|
||||||
clearIrrelevantFields := func(ixns []*structs.Intention) {
|
|
||||||
// Clear fields irrelevant for comparison.
|
// Clear fields irrelevant for comparison.
|
||||||
for _, ixn := range ixns {
|
for _, ixn := range ixns {
|
||||||
ixn.Hash = nil
|
ixn.Hash = nil
|
||||||
|
@ -556,6 +1125,15 @@ func TestStore_IntentionsList(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmpIntention := func(ixn *structs.Intention, id string) *structs.Intention {
|
||||||
|
ixn2 := ixn.Clone()
|
||||||
|
ixn2.ID = id
|
||||||
|
clearIrrelevantFields(ixn2)
|
||||||
|
//nolint:staticcheck
|
||||||
|
ixn2.UpdatePrecedence()
|
||||||
|
return ixn2
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
expectIDs []string
|
expectIDs []string
|
||||||
)
|
)
|
||||||
|
@ -610,7 +1188,7 @@ func TestStore_IntentionsList(t *testing.T) {
|
||||||
require.Equal(t, !legacy, fromConfig)
|
require.Equal(t, !legacy, fromConfig)
|
||||||
require.Equal(t, lastIndex, idx)
|
require.Equal(t, lastIndex, idx)
|
||||||
|
|
||||||
clearIrrelevantFields(actual)
|
clearIrrelevantFields(actual...)
|
||||||
require.Equal(t, expected, actual)
|
require.Equal(t, expected, actual)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -405,6 +405,9 @@ func (e *ServiceIntentionsConfigEntry) normalize(legacyWrite bool) error {
|
||||||
return fmt.Errorf("config entry is nil")
|
return fmt.Errorf("config entry is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: this function must be deterministic so that the raft log doesn't
|
||||||
|
// diverge. This means no ID assignments or time.Now() usage!
|
||||||
|
|
||||||
e.Kind = ServiceIntentions
|
e.Kind = ServiceIntentions
|
||||||
|
|
||||||
e.EnterpriseMeta.Normalize()
|
e.EnterpriseMeta.Normalize()
|
||||||
|
@ -430,11 +433,6 @@ func (e *ServiceIntentionsConfigEntry) normalize(legacyWrite bool) error {
|
||||||
if src.LegacyMeta == nil {
|
if src.LegacyMeta == nil {
|
||||||
src.LegacyMeta = make(map[string]string)
|
src.LegacyMeta = make(map[string]string)
|
||||||
}
|
}
|
||||||
// Set the created/updated times. If this is an update instead of an insert
|
|
||||||
// the UpdateOver() will fix it up appropriately.
|
|
||||||
now := time.Now().UTC()
|
|
||||||
src.LegacyCreateTime = timePointer(now)
|
|
||||||
src.LegacyUpdateTime = timePointer(now)
|
|
||||||
} else {
|
} else {
|
||||||
// Legacy fields are cleared, except LegacyMeta which we leave
|
// Legacy fields are cleared, except LegacyMeta which we leave
|
||||||
// populated so that we can later fail the write in Validate() and
|
// populated so that we can later fail the write in Validate() and
|
||||||
|
@ -594,11 +592,25 @@ func (e *ServiceIntentionsConfigEntry) validate(legacyWrite bool) error {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if src.LegacyCreateTime == nil {
|
||||||
|
return fmt.Errorf("Sources[%d].LegacyCreateTime must be set", i)
|
||||||
|
}
|
||||||
|
if src.LegacyUpdateTime == nil {
|
||||||
|
return fmt.Errorf("Sources[%d].LegacyUpdateTime must be set", i)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(src.LegacyMeta) > 0 {
|
if len(src.LegacyMeta) > 0 {
|
||||||
return fmt.Errorf("Sources[%d].LegacyMeta must be omitted", i)
|
return fmt.Errorf("Sources[%d].LegacyMeta must be omitted", i)
|
||||||
}
|
}
|
||||||
src.LegacyMeta = nil // ensure it's completely unset
|
src.LegacyMeta = nil // ensure it's completely unset
|
||||||
|
|
||||||
|
if src.LegacyCreateTime != nil {
|
||||||
|
return fmt.Errorf("Sources[%d].LegacyCreateTime must be omitted", i)
|
||||||
|
}
|
||||||
|
if src.LegacyUpdateTime != nil {
|
||||||
|
return fmt.Errorf("Sources[%d].LegacyUpdateTime must be omitted", i)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if legacyWrite {
|
if legacyWrite {
|
||||||
|
|
|
@ -21,6 +21,14 @@ func generateUUID() (ret string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceIntentionsConfigEntry(t *testing.T) {
|
func TestServiceIntentionsConfigEntry(t *testing.T) {
|
||||||
|
var (
|
||||||
|
testLocation = time.FixedZone("UTC-8", -8*60*60)
|
||||||
|
|
||||||
|
testTimeA = time.Date(1955, 11, 5, 6, 15, 0, 0, testLocation)
|
||||||
|
testTimeB = time.Date(1985, 10, 26, 1, 35, 0, 0, testLocation)
|
||||||
|
testTimeC = time.Date(2015, 10, 21, 16, 29, 0, 0, testLocation)
|
||||||
|
)
|
||||||
|
|
||||||
type testcase struct {
|
type testcase struct {
|
||||||
entry *ServiceIntentionsConfigEntry
|
entry *ServiceIntentionsConfigEntry
|
||||||
legacy bool
|
legacy bool
|
||||||
|
@ -129,6 +137,8 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
|
||||||
LegacyID: legacyIDs[0],
|
LegacyID: legacyIDs[0],
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Action: IntentionActionAllow,
|
Action: IntentionActionAllow,
|
||||||
|
LegacyCreateTime: &testTimeA,
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Meta: map[string]string{
|
Meta: map[string]string{
|
||||||
|
@ -202,6 +212,8 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Action: IntentionActionAllow,
|
Action: IntentionActionAllow,
|
||||||
Description: strings.Repeat("x", 512),
|
Description: strings.Repeat("x", 512),
|
||||||
|
LegacyCreateTime: &testTimeA,
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
LegacyMeta: map[string]string{ // stray Meta will be dropped
|
LegacyMeta: map[string]string{ // stray Meta will be dropped
|
||||||
"old": "data",
|
"old": "data",
|
||||||
},
|
},
|
||||||
|
@ -220,6 +232,8 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
|
||||||
LegacyID: legacyIDs[0],
|
LegacyID: legacyIDs[0],
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Action: IntentionActionAllow,
|
Action: IntentionActionAllow,
|
||||||
|
LegacyCreateTime: &testTimeA,
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
LegacyMeta: makeStringMap(65, 5, 5),
|
LegacyMeta: makeStringMap(65, 5, 5),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -236,6 +250,8 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
|
||||||
LegacyID: legacyIDs[0],
|
LegacyID: legacyIDs[0],
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Action: IntentionActionAllow,
|
Action: IntentionActionAllow,
|
||||||
|
LegacyCreateTime: &testTimeA,
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
LegacyMeta: makeStringMap(64, 129, 5),
|
LegacyMeta: makeStringMap(64, 129, 5),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -252,6 +268,8 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
|
||||||
LegacyID: legacyIDs[0],
|
LegacyID: legacyIDs[0],
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Action: IntentionActionAllow,
|
Action: IntentionActionAllow,
|
||||||
|
LegacyCreateTime: &testTimeA,
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
LegacyMeta: makeStringMap(64, 128, 513),
|
LegacyMeta: makeStringMap(64, 128, 513),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -268,6 +286,8 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
|
||||||
LegacyID: legacyIDs[0],
|
LegacyID: legacyIDs[0],
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Action: IntentionActionAllow,
|
Action: IntentionActionAllow,
|
||||||
|
LegacyCreateTime: &testTimeA,
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
LegacyMeta: makeStringMap(64, 128, 512),
|
LegacyMeta: makeStringMap(64, 128, 512),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -283,6 +303,8 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Action: IntentionActionAllow,
|
Action: IntentionActionAllow,
|
||||||
Description: strings.Repeat("x", 512),
|
Description: strings.Repeat("x", 512),
|
||||||
|
LegacyCreateTime: &testTimeA,
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1010,6 +1032,8 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
|
||||||
{
|
{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Action: IntentionActionAllow,
|
Action: IntentionActionAllow,
|
||||||
|
LegacyCreateTime: &testTimeA, // stray times will be dropped
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "bar",
|
Name: "bar",
|
||||||
|
@ -1062,6 +1086,8 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
|
||||||
Name: WildcardSpecifier,
|
Name: WildcardSpecifier,
|
||||||
Action: IntentionActionDeny,
|
Action: IntentionActionDeny,
|
||||||
LegacyID: legacyIDs[0],
|
LegacyID: legacyIDs[0],
|
||||||
|
LegacyCreateTime: &testTimeA,
|
||||||
|
LegacyUpdateTime: &testTimeA,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
|
@ -1071,23 +1097,27 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
|
||||||
"key1": "val1",
|
"key1": "val1",
|
||||||
"key2": "val2",
|
"key2": "val2",
|
||||||
},
|
},
|
||||||
|
LegacyCreateTime: &testTimeB,
|
||||||
|
LegacyUpdateTime: &testTimeB,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "bar",
|
Name: "bar",
|
||||||
Action: IntentionActionDeny,
|
Action: IntentionActionDeny,
|
||||||
LegacyID: legacyIDs[2],
|
LegacyID: legacyIDs[2],
|
||||||
|
LegacyCreateTime: &testTimeC,
|
||||||
|
LegacyUpdateTime: &testTimeC,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
check: func(t *testing.T, entry *ServiceIntentionsConfigEntry) {
|
check: func(t *testing.T, entry *ServiceIntentionsConfigEntry) {
|
||||||
require.Len(t, entry.Sources, 3)
|
require.Len(t, entry.Sources, 3)
|
||||||
|
|
||||||
assert.False(t, entry.Sources[0].LegacyCreateTime.IsZero())
|
// assert.False(t, entry.Sources[0].LegacyCreateTime.IsZero())
|
||||||
assert.False(t, entry.Sources[0].LegacyUpdateTime.IsZero())
|
// assert.False(t, entry.Sources[0].LegacyUpdateTime.IsZero())
|
||||||
assert.False(t, entry.Sources[1].LegacyCreateTime.IsZero())
|
// assert.False(t, entry.Sources[1].LegacyCreateTime.IsZero())
|
||||||
assert.False(t, entry.Sources[1].LegacyUpdateTime.IsZero())
|
// assert.False(t, entry.Sources[1].LegacyUpdateTime.IsZero())
|
||||||
assert.False(t, entry.Sources[2].LegacyCreateTime.IsZero())
|
// assert.False(t, entry.Sources[2].LegacyCreateTime.IsZero())
|
||||||
assert.False(t, entry.Sources[2].LegacyUpdateTime.IsZero())
|
// assert.False(t, entry.Sources[2].LegacyUpdateTime.IsZero())
|
||||||
|
|
||||||
assert.Equal(t, []*SourceIntention{
|
assert.Equal(t, []*SourceIntention{
|
||||||
{
|
{
|
||||||
|
@ -1299,6 +1329,8 @@ func TestMigrateIntentions(t *testing.T) {
|
||||||
LegacyMeta: map[string]string{
|
LegacyMeta: map[string]string{
|
||||||
"key1": "val1",
|
"key1": "val1",
|
||||||
},
|
},
|
||||||
|
LegacyCreateTime: &anyTime,
|
||||||
|
LegacyUpdateTime: &anyTime,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1349,6 +1381,8 @@ func TestMigrateIntentions(t *testing.T) {
|
||||||
LegacyMeta: map[string]string{
|
LegacyMeta: map[string]string{
|
||||||
"key1": "val1",
|
"key1": "val1",
|
||||||
},
|
},
|
||||||
|
LegacyCreateTime: &anyTime,
|
||||||
|
LegacyUpdateTime: &anyTime,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
LegacyID: legacyIDs[1],
|
LegacyID: legacyIDs[1],
|
||||||
|
@ -1359,6 +1393,8 @@ func TestMigrateIntentions(t *testing.T) {
|
||||||
LegacyMeta: map[string]string{
|
LegacyMeta: map[string]string{
|
||||||
"key2": "val2",
|
"key2": "val2",
|
||||||
},
|
},
|
||||||
|
LegacyCreateTime: &anyTime,
|
||||||
|
LegacyUpdateTime: &anyTime,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1409,6 +1445,8 @@ func TestMigrateIntentions(t *testing.T) {
|
||||||
LegacyMeta: map[string]string{
|
LegacyMeta: map[string]string{
|
||||||
"key1": "val1",
|
"key1": "val1",
|
||||||
},
|
},
|
||||||
|
LegacyCreateTime: &anyTime,
|
||||||
|
LegacyUpdateTime: &anyTime,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1425,6 +1463,8 @@ func TestMigrateIntentions(t *testing.T) {
|
||||||
LegacyMeta: map[string]string{
|
LegacyMeta: map[string]string{
|
||||||
"key2": "val2",
|
"key2": "val2",
|
||||||
},
|
},
|
||||||
|
LegacyCreateTime: &anyTime,
|
||||||
|
LegacyUpdateTime: &anyTime,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -440,6 +440,9 @@ func (x *Intention) ToConfigEntry(legacy bool) *ServiceIntentionsConfigEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Intention) ToSourceIntention(legacy bool) *SourceIntention {
|
func (x *Intention) ToSourceIntention(legacy bool) *SourceIntention {
|
||||||
|
ct := x.CreatedAt // copy
|
||||||
|
ut := x.UpdatedAt
|
||||||
|
|
||||||
src := &SourceIntention{
|
src := &SourceIntention{
|
||||||
Name: x.SourceName,
|
Name: x.SourceName,
|
||||||
EnterpriseMeta: *x.SourceEnterpriseMeta(),
|
EnterpriseMeta: *x.SourceEnterpriseMeta(),
|
||||||
|
@ -450,8 +453,8 @@ func (x *Intention) ToSourceIntention(legacy bool) *SourceIntention {
|
||||||
Type: x.SourceType,
|
Type: x.SourceType,
|
||||||
Description: x.Description,
|
Description: x.Description,
|
||||||
LegacyMeta: x.Meta,
|
LegacyMeta: x.Meta,
|
||||||
LegacyCreateTime: nil, // Ignore
|
LegacyCreateTime: &ct,
|
||||||
LegacyUpdateTime: nil, // Ignore
|
LegacyUpdateTime: &ut,
|
||||||
}
|
}
|
||||||
if !legacy {
|
if !legacy {
|
||||||
src.Permissions = x.Permissions
|
src.Permissions = x.Permissions
|
||||||
|
@ -522,13 +525,30 @@ type IntentionRequest struct {
|
||||||
Op IntentionOp
|
Op IntentionOp
|
||||||
|
|
||||||
// Intention is the intention.
|
// Intention is the intention.
|
||||||
|
//
|
||||||
|
// This is mutually exclusive with the Mutation field.
|
||||||
Intention *Intention
|
Intention *Intention
|
||||||
|
|
||||||
|
// Mutation is a change to make to an Intention.
|
||||||
|
//
|
||||||
|
// This is mutually exclusive with the Intention field.
|
||||||
|
//
|
||||||
|
// This field is only set by the leader before writing to the raft log and
|
||||||
|
// is not settable via the API or an RPC.
|
||||||
|
Mutation *IntentionMutation
|
||||||
|
|
||||||
// WriteRequest is a common struct containing ACL tokens and other
|
// WriteRequest is a common struct containing ACL tokens and other
|
||||||
// write-related common elements for requests.
|
// write-related common elements for requests.
|
||||||
WriteRequest
|
WriteRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IntentionMutation struct {
|
||||||
|
ID string
|
||||||
|
Destination ServiceName
|
||||||
|
Source ServiceName
|
||||||
|
Value *SourceIntention
|
||||||
|
}
|
||||||
|
|
||||||
// RequestDatacenter returns the datacenter for a given request.
|
// RequestDatacenter returns the datacenter for a given request.
|
||||||
func (q *IntentionRequest) RequestDatacenter() string {
|
func (q *IntentionRequest) RequestDatacenter() string {
|
||||||
return q.Datacenter
|
return q.Datacenter
|
||||||
|
|
Loading…
Reference in New Issue