Skip to content

Commit

Permalink
Applies prefix ACL to a catch-all template as a special case.
Browse files Browse the repository at this point in the history
  • Loading branch information
James Phillips committed Mar 3, 2016
1 parent df7b78b commit 89b24e3
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 3 deletions.
157 changes: 157 additions & 0 deletions consul/prepared_query_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,163 @@ func TestPreparedQuery_parseQuery(t *testing.T) {
}
}

func TestPreparedQuery_ACLDeny_Template(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 an ACL with write permissions for any prefix.
var token string
{
var rules = `
query "" {
policy = "write"
}
`

req := structs.ACLRequest{
Datacenter: "dc1",
Op: structs.ACLSet,
ACL: structs.ACL{
Name: "User token",
Type: structs.ACLTypeClient,
Rules: rules,
},
WriteRequest: structs.WriteRequest{Token: "root"},
}
if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token); err != nil {
t.Fatalf("err: %v", err)
}
}

// Set up a catch-all template.
query := structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Name: "",
Token: "5e1e24e5-1329-f86f-18c6-3d3734edb2cd",
Template: structs.QueryTemplateOptions{
Type: structs.QueryTemplateTypeNamePrefixMatch,
},
Service: structs.ServiceQuery{
Service: "${name.full}",
},
},
}
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 = token
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply); err != nil {
t.Fatalf("err: %v", err)
}

// Capture the ID and read back the query to verify. Note that the token
// will be redacted since this isn't a management token.
query.Query.ID = reply
query.Query.Token = redactedToken
{
req := &structs.PreparedQuerySpecificRequest{
Datacenter: "dc1",
QueryID: query.Query.ID,
QueryOptions: structs.QueryOptions{Token: token},
}
var resp structs.IndexedPreparedQueries
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", 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 query by ID without a token and make sure it gets denied, even
// though this has an empty name and would normally be shown.
{
req := &structs.PreparedQuerySpecificRequest{
Datacenter: "dc1",
QueryID: query.Query.ID,
}

var resp structs.IndexedPreparedQueries
err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", req, &resp)
if err == nil || !strings.Contains(err.Error(), permissionDenied) {
t.Fatalf("bad: %v", err)
}

if len(resp.Queries) != 0 {
t.Fatalf("bad: %v", resp)
}
}

// We should get the same result listing all the queries without a
// token.
{
req := &structs.DCSpecificRequest{
Datacenter: "dc1",
}
var resp structs.IndexedPreparedQueries
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.List", req, &resp); err != nil {
t.Fatalf("err: %v", err)
}

if len(resp.Queries) != 0 {
t.Fatalf("bad: %v", resp)
}
}

// But a management token should be able to see it, and the real token.
query.Query.Token = "5e1e24e5-1329-f86f-18c6-3d3734edb2cd"
{
req := &structs.PreparedQuerySpecificRequest{
Datacenter: "dc1",
QueryID: query.Query.ID,
QueryOptions: structs.QueryOptions{Token: "root"},
}
var resp structs.IndexedPreparedQueries
if err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Get", 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)
}
}
}

func TestPreparedQuery_Get(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.ACLDatacenter = "dc1"
Expand Down
2 changes: 1 addition & 1 deletion consul/structs/prepared_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ type PreparedQuery struct {
// this query, and whether the prefix applies to this query. You always need to
// check the ok value before using the prefix.
func (pq *PreparedQuery) GetACLPrefix() (string, bool) {
if pq.Name != "" {
if pq.Name != "" || pq.Template.Type != "" {
return pq.Name, true
}

Expand Down
16 changes: 14 additions & 2 deletions consul/structs/prepared_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,20 @@ func TestStructs_PreparedQuery_GetACLPrefix(t *testing.T) {
t.Fatalf("bad: %s", prefix)
}

named := &PreparedQuery{Name: "hello"}
named := &PreparedQuery{
Name: "hello",
}
if prefix, ok := named.GetACLPrefix(); !ok || prefix != "hello" {
t.Fatalf("bad: %#v", prefix)
t.Fatalf("bad: ok=%v, prefix=%#v", ok, prefix)
}

tmpl := &PreparedQuery{
Name: "",
Template: QueryTemplateOptions{
Type: QueryTemplateTypeNamePrefixMatch,
},
}
if prefix, ok := tmpl.GetACLPrefix(); !ok || prefix != "" {
t.Fatalf("bad: ok=%v prefix=%#v", ok, prefix)
}
}

0 comments on commit 89b24e3

Please sign in to comment.