diff --git a/agent/consul/intention_endpoint.go b/agent/consul/intention_endpoint.go index 030c9922d..118dfb5b9 100644 --- a/agent/consul/intention_endpoint.go +++ b/agent/consul/intention_endpoint.go @@ -2,6 +2,7 @@ package consul import ( "errors" + "fmt" "time" "github.com/armon/go-metrics" @@ -58,6 +59,18 @@ func (s *Intention) Apply( } *reply = args.Intention.ID + // If this is not a create, then we have to verify the ID. + if args.Op != structs.IntentionOpCreate { + state := s.srv.fsm.State() + _, ixn, err := state.IntentionGet(nil, args.Intention.ID) + if err != nil { + return fmt.Errorf("Intention lookup failed: %v", err) + } + if ixn == nil { + return fmt.Errorf("Cannot modify non-existent intention: '%s'", args.Intention.ID) + } + } + // Commit resp, err := s.srv.raftApply(structs.IntentionRequestType, args) if err != nil { diff --git a/agent/consul/intention_endpoint_test.go b/agent/consul/intention_endpoint_test.go index 5a2405d89..6049c5f35 100644 --- a/agent/consul/intention_endpoint_test.go +++ b/agent/consul/intention_endpoint_test.go @@ -3,6 +3,7 @@ package consul import ( "os" "reflect" + "strings" "testing" "github.com/hashicorp/consul/agent/structs" @@ -10,6 +11,7 @@ import ( "github.com/hashicorp/net-rpc-msgpackrpc" ) +// Test basic creation func TestIntentionApply_new(t *testing.T) { t.Parallel() dir1, s1 := testServer(t) @@ -63,6 +65,94 @@ func TestIntentionApply_new(t *testing.T) { } } +// Test basic updating +func TestIntentionApply_updateGood(t *testing.T) { + t.Parallel() + dir1, s1 := testServer(t) + defer os.RemoveAll(dir1) + defer s1.Shutdown() + codec := rpcClient(t, s1) + defer codec.Close() + + testrpc.WaitForLeader(t, s1.RPC, "dc1") + + // Setup a basic record to create + ixn := structs.IntentionRequest{ + Datacenter: "dc1", + Op: structs.IntentionOpCreate, + Intention: &structs.Intention{ + SourceName: "test", + }, + } + var reply string + + // Create + if err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply); err != nil { + t.Fatalf("err: %v", err) + } + if reply == "" { + t.Fatal("reply should be non-empty") + } + + // Update + ixn.Op = structs.IntentionOpUpdate + ixn.Intention.ID = reply + ixn.Intention.SourceName = "bar" + if err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply); err != nil { + t.Fatalf("err: %v", err) + } + + // Read + ixn.Intention.ID = reply + { + req := &structs.IntentionQueryRequest{ + Datacenter: "dc1", + IntentionID: ixn.Intention.ID, + } + var resp structs.IndexedIntentions + if err := msgpackrpc.CallWithCodec(codec, "Intention.Get", req, &resp); err != nil { + t.Fatalf("err: %v", err) + } + if len(resp.Intentions) != 1 { + t.Fatalf("bad: %v", resp) + } + actual := resp.Intentions[0] + actual.CreateIndex, actual.ModifyIndex = 0, 0 + if !reflect.DeepEqual(actual, ixn.Intention) { + t.Fatalf("bad: %v", actual) + } + } +} + +// Shouldn't be able to update a non-existent intention +func TestIntentionApply_updateNonExist(t *testing.T) { + t.Parallel() + dir1, s1 := testServer(t) + defer os.RemoveAll(dir1) + defer s1.Shutdown() + codec := rpcClient(t, s1) + defer codec.Close() + + testrpc.WaitForLeader(t, s1.RPC, "dc1") + + // Setup a basic record to create + ixn := structs.IntentionRequest{ + Datacenter: "dc1", + Op: structs.IntentionOpUpdate, + Intention: &structs.Intention{ + ID: generateUUID(), + SourceName: "test", + }, + } + var reply string + + // Create + err := msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply) + if err == nil || !strings.Contains(err.Error(), "Cannot modify non-existent intention") { + t.Fatalf("bad: %v", err) + } +} + func TestIntentionList(t *testing.T) { t.Parallel() dir1, s1 := testServer(t)