agent/structs: Intention validation
This commit is contained in:
parent
d34ee200de
commit
8e2462e301
|
@ -94,6 +94,11 @@ func (s *Intention) Apply(
|
|||
args.Intention.DestinationNS = structs.IntentionDefaultNamespace
|
||||
}
|
||||
|
||||
// Validate
|
||||
if err := args.Intention.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Commit
|
||||
resp, err := s.srv.raftApply(structs.IntentionRequestType, args)
|
||||
if err != nil {
|
||||
|
|
|
@ -32,6 +32,8 @@ func TestIntentionApply_new(t *testing.T) {
|
|||
SourceName: "test",
|
||||
DestinationNS: structs.IntentionDefaultNamespace,
|
||||
DestinationName: "test",
|
||||
Action: structs.IntentionActionAllow,
|
||||
Meta: map[string]string{},
|
||||
},
|
||||
}
|
||||
var reply string
|
||||
|
@ -86,7 +88,7 @@ func TestIntentionApply_new(t *testing.T) {
|
|||
actual.CreatedAt = ixn.Intention.CreatedAt
|
||||
actual.UpdatedAt = ixn.Intention.UpdatedAt
|
||||
if !reflect.DeepEqual(actual, ixn.Intention) {
|
||||
t.Fatalf("bad: %v", actual)
|
||||
t.Fatalf("bad:\n\n%#v\n\n%#v", actual, ixn.Intention)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -140,6 +142,8 @@ func TestIntentionApply_updateGood(t *testing.T) {
|
|||
SourceName: "test",
|
||||
DestinationNS: structs.IntentionDefaultNamespace,
|
||||
DestinationName: "test",
|
||||
Action: structs.IntentionActionAllow,
|
||||
Meta: map[string]string{},
|
||||
},
|
||||
}
|
||||
var reply string
|
||||
|
@ -265,7 +269,11 @@ func TestIntentionApply_deleteGood(t *testing.T) {
|
|||
Datacenter: "dc1",
|
||||
Op: structs.IntentionOpCreate,
|
||||
Intention: &structs.Intention{
|
||||
SourceName: "test",
|
||||
SourceNS: "test",
|
||||
SourceName: "test",
|
||||
DestinationNS: "test",
|
||||
DestinationName: "test",
|
||||
Action: structs.IntentionActionAllow,
|
||||
},
|
||||
}
|
||||
var reply string
|
||||
|
@ -358,6 +366,7 @@ func TestIntentionMatch_good(t *testing.T) {
|
|||
SourceName: "test",
|
||||
DestinationNS: v[0],
|
||||
DestinationName: v[1],
|
||||
Action: structs.IntentionActionAllow,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package structs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -61,6 +65,91 @@ type Intention struct {
|
|||
RaftIndex
|
||||
}
|
||||
|
||||
// Validate returns an error if the intention is invalid for inserting
|
||||
// or updating.
|
||||
func (x *Intention) Validate() error {
|
||||
var result error
|
||||
|
||||
// Empty values
|
||||
if x.SourceNS == "" {
|
||||
result = multierror.Append(result, fmt.Errorf("SourceNS must be set"))
|
||||
}
|
||||
if x.SourceName == "" {
|
||||
result = multierror.Append(result, fmt.Errorf("SourceName must be set"))
|
||||
}
|
||||
if x.DestinationNS == "" {
|
||||
result = multierror.Append(result, fmt.Errorf("DestinationNS must be set"))
|
||||
}
|
||||
if x.DestinationName == "" {
|
||||
result = multierror.Append(result, fmt.Errorf("DestinationName must be set"))
|
||||
}
|
||||
|
||||
// Wildcard usage verification
|
||||
if x.SourceNS != IntentionWildcard {
|
||||
if strings.Contains(x.SourceNS, IntentionWildcard) {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"SourceNS: wildcard character '*' cannot be used with partial values"))
|
||||
}
|
||||
}
|
||||
if x.SourceName != IntentionWildcard {
|
||||
if strings.Contains(x.SourceName, IntentionWildcard) {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"SourceName: wildcard character '*' cannot be used with partial values"))
|
||||
}
|
||||
|
||||
if x.SourceNS == IntentionWildcard {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"SourceName: exact value cannot follow wildcard namespace"))
|
||||
}
|
||||
}
|
||||
if x.DestinationNS != IntentionWildcard {
|
||||
if strings.Contains(x.DestinationNS, IntentionWildcard) {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"DestinationNS: wildcard character '*' cannot be used with partial values"))
|
||||
}
|
||||
}
|
||||
if x.DestinationName != IntentionWildcard {
|
||||
if strings.Contains(x.DestinationName, IntentionWildcard) {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"DestinationName: wildcard character '*' cannot be used with partial values"))
|
||||
}
|
||||
|
||||
if x.DestinationNS == IntentionWildcard {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"DestinationName: exact value cannot follow wildcard namespace"))
|
||||
}
|
||||
}
|
||||
|
||||
// Length of opaque values
|
||||
if len(x.Description) > metaValueMaxLength {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"Description exceeds maximum length %d", metaValueMaxLength))
|
||||
}
|
||||
if len(x.Meta) > metaMaxKeyPairs {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"Meta exceeds maximum element count %d", metaMaxKeyPairs))
|
||||
}
|
||||
for k, v := range x.Meta {
|
||||
if len(k) > metaKeyMaxLength {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"Meta key %q exceeds maximum length %d", k, metaKeyMaxLength))
|
||||
}
|
||||
if len(v) > metaValueMaxLength {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"Meta value for key %q exceeds maximum length %d", k, metaValueMaxLength))
|
||||
}
|
||||
}
|
||||
|
||||
switch x.Action {
|
||||
case IntentionActionAllow, IntentionActionDeny:
|
||||
default:
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"Action must be set to 'allow' or 'deny'"))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// IntentionAction is the action that the intention represents. This
|
||||
// can be "allow" or "deny" to whitelist or blacklist intentions.
|
||||
type IntentionAction string
|
||||
|
|
|
@ -3,9 +3,116 @@ package structs
|
|||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntentionValidate(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Modify func(*Intention)
|
||||
Err string
|
||||
}{
|
||||
{
|
||||
"long description",
|
||||
func(x *Intention) {
|
||||
x.Description = strings.Repeat("x", metaValueMaxLength+1)
|
||||
},
|
||||
"description exceeds",
|
||||
},
|
||||
|
||||
{
|
||||
"no action set",
|
||||
func(x *Intention) { x.Action = "" },
|
||||
"action must be set",
|
||||
},
|
||||
|
||||
{
|
||||
"no SourceNS",
|
||||
func(x *Intention) { x.SourceNS = "" },
|
||||
"SourceNS must be set",
|
||||
},
|
||||
|
||||
{
|
||||
"no SourceName",
|
||||
func(x *Intention) { x.SourceName = "" },
|
||||
"SourceName must be set",
|
||||
},
|
||||
|
||||
{
|
||||
"no DestinationNS",
|
||||
func(x *Intention) { x.DestinationNS = "" },
|
||||
"DestinationNS must be set",
|
||||
},
|
||||
|
||||
{
|
||||
"no DestinationName",
|
||||
func(x *Intention) { x.DestinationName = "" },
|
||||
"DestinationName must be set",
|
||||
},
|
||||
|
||||
{
|
||||
"SourceNS partial wildcard",
|
||||
func(x *Intention) { x.SourceNS = "foo*" },
|
||||
"partial value",
|
||||
},
|
||||
|
||||
{
|
||||
"SourceName partial wildcard",
|
||||
func(x *Intention) { x.SourceName = "foo*" },
|
||||
"partial value",
|
||||
},
|
||||
|
||||
{
|
||||
"SourceName exact following wildcard",
|
||||
func(x *Intention) {
|
||||
x.SourceNS = "*"
|
||||
x.SourceName = "foo"
|
||||
},
|
||||
"follow wildcard",
|
||||
},
|
||||
|
||||
{
|
||||
"DestinationNS partial wildcard",
|
||||
func(x *Intention) { x.DestinationNS = "foo*" },
|
||||
"partial value",
|
||||
},
|
||||
|
||||
{
|
||||
"DestinationName partial wildcard",
|
||||
func(x *Intention) { x.DestinationName = "foo*" },
|
||||
"partial value",
|
||||
},
|
||||
|
||||
{
|
||||
"DestinationName exact following wildcard",
|
||||
func(x *Intention) {
|
||||
x.DestinationNS = "*"
|
||||
x.DestinationName = "foo"
|
||||
},
|
||||
"follow wildcard",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
ixn := TestIntention(t)
|
||||
tc.Modify(ixn)
|
||||
|
||||
err := ixn.Validate()
|
||||
if (err != nil) != (tc.Err != "") {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(tc.Err)) {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntentionPrecedenceSorter(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
|
|
15
agent/structs/testing_intention.go
Normal file
15
agent/structs/testing_intention.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package structs
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/go-testing-interface"
|
||||
)
|
||||
|
||||
// TestIntention returns a valid, uninserted (no ID set) intention.
|
||||
func TestIntention(t testing.T) *Intention {
|
||||
return &Intention{
|
||||
SourceNS: "eng",
|
||||
SourceName: "api",
|
||||
DestinationNS: "eng",
|
||||
DestinationName: "db",
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue