diff --git a/consul/prepared_query_endpoint_test.go b/consul/prepared_query_endpoint_test.go index 8ac16419d7..bb5b0f231b 100644 --- a/consul/prepared_query_endpoint_test.go +++ b/consul/prepared_query_endpoint_test.go @@ -22,7 +22,7 @@ func TestPreparedQuery_Apply(t *testing.T) { // Set up a node and service in the catalog. { - arg := structs.RegisterRequest{ + req := structs.RegisterRequest{ Datacenter: "dc1", Node: "foo", Address: "127.0.0.1", @@ -33,15 +33,14 @@ func TestPreparedQuery_Apply(t *testing.T) { }, } var reply struct{} - - err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &reply) + err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply) if err != nil { t.Fatalf("err: %v", err) } } // Set up a bare bones query. - arg := structs.PreparedQueryRequest{ + query := structs.PreparedQueryRequest{ Datacenter: "dc1", Op: structs.PreparedQueryCreate, Query: &structs.PreparedQuery{ @@ -53,16 +52,16 @@ func TestPreparedQuery_Apply(t *testing.T) { var reply string // Set an ID which should fail the create. - arg.Query.ID = "nope" - err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &arg, &reply) + query.Query.ID = "nope" + err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply) if err == nil || !strings.Contains(err.Error(), "ID must be empty") { t.Fatalf("bad: %v", err) } // Change it to a bogus modify which should also fail. - arg.Op = structs.PreparedQueryUpdate - arg.Query.ID = generateUUID() - err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &arg, &reply) + query.Op = structs.PreparedQueryUpdate + query.Query.ID = generateUUID() + err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply) if err == nil || !strings.Contains(err.Error(), "Cannot modify non-existent prepared query") { t.Fatalf("bad: %v", err) } @@ -70,34 +69,34 @@ func TestPreparedQuery_Apply(t *testing.T) { // Fix up the ID but invalidate the query itself. This proves we call // parseQuery for a create, but that function is checked in detail as // part of another test. - arg.Op = structs.PreparedQueryCreate - arg.Query.ID = "" - arg.Query.Service.Failover.NearestN = -1 - err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &arg, &reply) + query.Op = structs.PreparedQueryCreate + query.Query.ID = "" + query.Query.Service.Failover.NearestN = -1 + err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply) if err == nil || !strings.Contains(err.Error(), "Bad NearestN") { t.Fatalf("bad: %v", err) } // Fix that and make sure it propagates an error from the Raft apply. - arg.Query.Service.Failover.NearestN = 0 - arg.Query.Service.Service = "nope" - err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &arg, &reply) + query.Query.Service.Failover.NearestN = 0 + query.Query.Service.Service = "nope" + err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply) if err == nil || !strings.Contains(err.Error(), "invalid service") { t.Fatalf("bad: %v", err) } // Fix that and make sure the apply goes through. - arg.Query.Service.Service = "redis" - if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &arg, &reply); err != nil { + query.Query.Service.Service = "redis" + if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil { t.Fatalf("err: %v", err) } // Capture the ID and read the query back to verify. - arg.Query.ID = reply + query.Query.ID = reply { req := &structs.PreparedQuerySpecificRequest{ Datacenter: "dc1", - QueryIDOrName: arg.Query.ID, + QueryIDOrName: query.Query.ID, } var resp structs.IndexedPreparedQueries if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Lookup", req, &resp); err != nil { @@ -112,15 +111,15 @@ func TestPreparedQuery_Apply(t *testing.T) { t.Fatalf("bad index: %d", resp.Index) } actual.CreateIndex, actual.ModifyIndex = 0, 0 - if !reflect.DeepEqual(actual, arg.Query) { + if !reflect.DeepEqual(actual, query.Query) { t.Fatalf("bad: %v", actual) } } // Make the op an update. This should go through now that we have an ID. - arg.Op = structs.PreparedQueryUpdate - arg.Query.Service.Failover.NearestN = 2 - if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &arg, &reply); err != nil { + query.Op = structs.PreparedQueryUpdate + query.Query.Service.Failover.NearestN = 2 + if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil { t.Fatalf("err: %v", err) } @@ -128,7 +127,7 @@ func TestPreparedQuery_Apply(t *testing.T) { { req := &structs.PreparedQuerySpecificRequest{ Datacenter: "dc1", - QueryIDOrName: arg.Query.ID, + QueryIDOrName: query.Query.ID, } var resp structs.IndexedPreparedQueries if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Lookup", req, &resp); err != nil { @@ -143,30 +142,30 @@ func TestPreparedQuery_Apply(t *testing.T) { t.Fatalf("bad index: %d", resp.Index) } actual.CreateIndex, actual.ModifyIndex = 0, 0 - if !reflect.DeepEqual(actual, arg.Query) { + if !reflect.DeepEqual(actual, query.Query) { t.Fatalf("bad: %v", actual) } } // Give a bogus op and make sure it fails. - arg.Op = "nope" - err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &arg, &reply) + query.Op = "nope" + err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply) if err == nil || !strings.Contains(err.Error(), "Unknown prepared query operation:") { t.Fatalf("bad: %v", err) } // Prove that an update also goes through the parseQuery validation. - arg.Op = structs.PreparedQueryUpdate - arg.Query.Service.Failover.NearestN = -1 - err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &arg, &reply) + query.Op = structs.PreparedQueryUpdate + query.Query.Service.Failover.NearestN = -1 + err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply) if err == nil || !strings.Contains(err.Error(), "Bad NearestN") { t.Fatalf("bad: %v", err) } // Now change the op to delete; the bad query field should be ignored // because all we care about for a delete op is the ID. - arg.Op = structs.PreparedQueryDelete - if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &arg, &reply); err != nil { + query.Op = structs.PreparedQueryDelete + if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil { t.Fatalf("err: %v", err) } @@ -174,7 +173,315 @@ func TestPreparedQuery_Apply(t *testing.T) { { req := &structs.PreparedQuerySpecificRequest{ Datacenter: "dc1", - QueryIDOrName: arg.Query.ID, + QueryIDOrName: query.Query.ID, + } + var resp structs.IndexedPreparedQueries + if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Lookup", req, &resp); err != nil { + t.Fatalf("err: %v", err) + } + + if len(resp.Queries) != 0 { + t.Fatalf("bad: %v", resp) + } + } +} + +func TestPreparedQuery_Apply_ACLDeny(t *testing.T) { + dir1, s1 := testServerWithConfig(t, func(c *Config) { + c.ACLDatacenter = "dc1" + c.ACLMasterToken = "root" + c.ACLDefaultPolicy = "deny" + }) + defer os.RemoveAll(dir1) + defer s1.Shutdown() + codec := rpcClient(t, s1) + defer codec.Close() + + testutil.WaitForLeader(t, s1.RPC, "dc1") + + // Create two ACLs with read permission to the service. + var token1, token2 string + { + var rules = ` + service "redis" { + policy = "read" + } + ` + + req := structs.ACLRequest{ + Datacenter: "dc1", + Op: structs.ACLSet, + ACL: structs.ACL{ + Name: "User token", + Type: structs.ACLTypeClient, + Rules: rules, + }, + WriteRequest: structs.WriteRequest{Token: "root"}, + } + var reply string + + if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &reply); err != nil { + t.Fatalf("err: %v", err) + } + token1 = reply + + if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &reply); err != nil { + t.Fatalf("err: %v", err) + } + token2 = reply + } + + // Set up a node and service in the catalog. + { + req := structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "redis", + Tags: []string{"master"}, + Port: 8000, + }, + WriteRequest: structs.WriteRequest{Token: "root"}, + } + var reply struct{} + err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &req, &reply) + if err != nil { + t.Fatalf("err: %v", err) + } + } + + // Set up a bare bones query. + query := structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Service: structs.ServiceQuery{ + Service: "redis", + }, + }, + } + var reply string + + // Creating without a token should fail since the default policy is to + // deny. + err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply) + if err == nil || !strings.Contains(err.Error(), permissionDenied) { + t.Fatalf("bad: %v", err) + } + + // Now add the token and try again. + query.WriteRequest.Token = token1 + if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil { + t.Fatalf("err: %v", err) + } + + // Capture the ID and set the token, then read back the query to verify. + query.Query.ID = reply + query.Query.Token = token1 + { + req := &structs.PreparedQuerySpecificRequest{ + Datacenter: "dc1", + QueryIDOrName: query.Query.ID, + QueryOptions: structs.QueryOptions{Token: "root"}, + } + var resp structs.IndexedPreparedQueries + if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Lookup", req, &resp); err != nil { + t.Fatalf("err: %v", err) + } + + if len(resp.Queries) != 1 { + t.Fatalf("bad: %v", resp) + } + actual := resp.Queries[0] + if resp.Index != actual.ModifyIndex { + t.Fatalf("bad index: %d", resp.Index) + } + actual.CreateIndex, actual.ModifyIndex = 0, 0 + if !reflect.DeepEqual(actual, query.Query) { + t.Fatalf("bad: %v", actual) + } + } + + // Try to do an update with a different token that does have access to + // the service, but isn't the one that was used to create the query. + query.Op = structs.PreparedQueryUpdate + query.WriteRequest.Token = token2 + err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply) + if err == nil || !strings.Contains(err.Error(), permissionDenied) { + t.Fatalf("bad: %v", err) + } + + // Try again with no token. + query.WriteRequest.Token = "" + err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply) + if err == nil || !strings.Contains(err.Error(), permissionDenied) { + t.Fatalf("bad: %v", err) + } + + // Try again with the original token. This should go through. + query.WriteRequest.Token = token1 + if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil { + t.Fatalf("err: %v", err) + } + + // Try to do a delete with a different token that does have access to + // the service, but isn't the one that was used to create the query. + query.Op = structs.PreparedQueryDelete + query.WriteRequest.Token = token2 + err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply) + if err == nil || !strings.Contains(err.Error(), permissionDenied) { + t.Fatalf("bad: %v", err) + } + + // Try again with no token. + query.WriteRequest.Token = "" + err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply) + if err == nil || !strings.Contains(err.Error(), permissionDenied) { + t.Fatalf("bad: %v", err) + } + + // Try again with the original token. This should go through. + query.WriteRequest.Token = token1 + if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil { + t.Fatalf("err: %v", err) + } + + // Make sure the query got deleted. + { + req := &structs.PreparedQuerySpecificRequest{ + Datacenter: "dc1", + QueryIDOrName: query.Query.ID, + QueryOptions: structs.QueryOptions{Token: "root"}, + } + var resp structs.IndexedPreparedQueries + if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Lookup", req, &resp); err != nil { + t.Fatalf("err: %v", err) + } + + if len(resp.Queries) != 0 { + t.Fatalf("bad: %v", resp) + } + } + + // Make the query again. + query.Op = structs.PreparedQueryCreate + query.Query.ID = "" + query.Query.Token = "" + query.WriteRequest.Token = token1 + if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil { + t.Fatalf("err: %v", err) + } + + // Check that it's there. + query.Query.ID = reply + query.Query.Token = token1 + { + req := &structs.PreparedQuerySpecificRequest{ + Datacenter: "dc1", + QueryIDOrName: query.Query.ID, + QueryOptions: structs.QueryOptions{Token: "root"}, + } + var resp structs.IndexedPreparedQueries + if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Lookup", req, &resp); err != nil { + t.Fatalf("err: %v", err) + } + + if len(resp.Queries) != 1 { + t.Fatalf("bad: %v", resp) + } + actual := resp.Queries[0] + if resp.Index != actual.ModifyIndex { + t.Fatalf("bad index: %d", resp.Index) + } + actual.CreateIndex, actual.ModifyIndex = 0, 0 + if !reflect.DeepEqual(actual, query.Query) { + t.Fatalf("bad: %v", actual) + } + } + + // A management token should be able to update the query no matter what. + query.Op = structs.PreparedQueryUpdate + query.WriteRequest.Token = "root" + if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil { + t.Fatalf("err: %v", err) + } + + // That last update should have changed the token to the management one. + query.Query.Token = "root" + { + req := &structs.PreparedQuerySpecificRequest{ + Datacenter: "dc1", + QueryIDOrName: query.Query.ID, + QueryOptions: structs.QueryOptions{Token: "root"}, + } + var resp structs.IndexedPreparedQueries + if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Lookup", req, &resp); err != nil { + t.Fatalf("err: %v", err) + } + + if len(resp.Queries) != 1 { + t.Fatalf("bad: %v", resp) + } + actual := resp.Queries[0] + if resp.Index != actual.ModifyIndex { + t.Fatalf("bad index: %d", resp.Index) + } + actual.CreateIndex, actual.ModifyIndex = 0, 0 + if !reflect.DeepEqual(actual, query.Query) { + t.Fatalf("bad: %v", actual) + } + } + + // Make another query. + query.Op = structs.PreparedQueryCreate + query.Query.ID = "" + query.Query.Token = "" + query.WriteRequest.Token = token1 + if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil { + t.Fatalf("err: %v", err) + } + + // Check that it's there. + query.Query.ID = reply + query.Query.Token = token1 + { + req := &structs.PreparedQuerySpecificRequest{ + Datacenter: "dc1", + QueryIDOrName: query.Query.ID, + QueryOptions: structs.QueryOptions{Token: "root"}, + } + var resp structs.IndexedPreparedQueries + if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Lookup", req, &resp); err != nil { + t.Fatalf("err: %v", err) + } + + if len(resp.Queries) != 1 { + t.Fatalf("bad: %v", resp) + } + actual := resp.Queries[0] + if resp.Index != actual.ModifyIndex { + t.Fatalf("bad index: %d", resp.Index) + } + actual.CreateIndex, actual.ModifyIndex = 0, 0 + if !reflect.DeepEqual(actual, query.Query) { + t.Fatalf("bad: %v", actual) + } + } + + // A management token should be able to delete the query no matter what. + query.Op = structs.PreparedQueryDelete + query.WriteRequest.Token = "root" + if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil { + t.Fatalf("err: %v", err) + } + + // Make sure the query got deleted. + { + req := &structs.PreparedQuerySpecificRequest{ + Datacenter: "dc1", + QueryIDOrName: query.Query.ID, + QueryOptions: structs.QueryOptions{Token: "root"}, } var resp structs.IndexedPreparedQueries if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Lookup", req, &resp); err != nil {