From 97dda5474bf74f40a4971c249ce60a65fef6311a Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Fri, 13 Oct 2017 11:30:27 -0700 Subject: [PATCH] ListPolicies and GetPolicy work w/o management token --- dev/acls/default-ns.hcl | 3 ++ dev/acls/node-read.hcl | 3 ++ nomad/acl_endpoint.go | 66 +++++++++++++++++++++++-- nomad/acl_endpoint_test.go | 50 +++++++++++++++++-- website/source/api/acl-policies.html.md | 4 +- 5 files changed, 115 insertions(+), 11 deletions(-) create mode 100644 dev/acls/default-ns.hcl create mode 100644 dev/acls/node-read.hcl diff --git a/dev/acls/default-ns.hcl b/dev/acls/default-ns.hcl new file mode 100644 index 000000000..6af4c50d0 --- /dev/null +++ b/dev/acls/default-ns.hcl @@ -0,0 +1,3 @@ +namespace "default" { + policy = "write" +} diff --git a/dev/acls/node-read.hcl b/dev/acls/node-read.hcl new file mode 100644 index 000000000..94f712b6f --- /dev/null +++ b/dev/acls/node-read.hcl @@ -0,0 +1,3 @@ +node { + policy = "read" +} diff --git a/nomad/acl_endpoint.go b/nomad/acl_endpoint.go index 135e77ad7..e77cd1048 100644 --- a/nomad/acl_endpoint.go +++ b/nomad/acl_endpoint.go @@ -121,12 +121,36 @@ func (a *ACL) ListPolicies(args *structs.ACLPolicyListRequest, reply *structs.AC defer metrics.MeasureSince([]string{"nomad", "acl", "list_policies"}, time.Now()) // Check management level permissions - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + acl, err := a.srv.ResolveToken(args.AuthToken) + if err != nil { return err - } else if acl == nil || !acl.IsManagement() { + } else if acl == nil { return structs.ErrPermissionDenied } + // If it is not a management token determine the policies that may be listed + mgt := acl.IsManagement() + var policies map[string]struct{} + if !mgt { + snap, err := a.srv.fsm.State().Snapshot() + if err != nil { + return err + } + + token, err := snap.ACLTokenBySecretID(nil, args.AuthToken) + if err != nil { + return err + } + if token == nil { + return structs.ErrTokenNotFound + } + + policies = make(map[string]struct{}, len(token.Policies)) + for _, p := range token.Policies { + policies[p] = struct{}{} + } + } + // Setup the blocking query opts := blockingOptions{ queryOpts: &args.QueryOptions, @@ -152,7 +176,9 @@ func (a *ACL) ListPolicies(args *structs.ACLPolicyListRequest, reply *structs.AC break } policy := raw.(*structs.ACLPolicy) - reply.Policies = append(reply.Policies, policy.Stub()) + if _, ok := policies[policy.Name]; ok || mgt { + reply.Policies = append(reply.Policies, policy.Stub()) + } } // Use the last index that affected the policy table @@ -183,12 +209,42 @@ func (a *ACL) GetPolicy(args *structs.ACLPolicySpecificRequest, reply *structs.S defer metrics.MeasureSince([]string{"nomad", "acl", "get_policy"}, time.Now()) // Check management level permissions - if acl, err := a.srv.ResolveToken(args.AuthToken); err != nil { + acl, err := a.srv.ResolveToken(args.AuthToken) + if err != nil { return err - } else if acl == nil || !acl.IsManagement() { + } else if acl == nil { return structs.ErrPermissionDenied } + // If it is not a management token determine if it can get this policy + mgt := acl.IsManagement() + if !mgt { + snap, err := a.srv.fsm.State().Snapshot() + if err != nil { + return err + } + + token, err := snap.ACLTokenBySecretID(nil, args.AuthToken) + if err != nil { + return err + } + if token == nil { + return structs.ErrTokenNotFound + } + + found := false + for _, p := range token.Policies { + if p == args.Name { + found = true + break + } + } + + if !found { + return structs.ErrPermissionDenied + } + } + // Setup the blocking query opts := blockingOptions{ queryOpts: &args.QueryOptions, diff --git a/nomad/acl_endpoint_test.go b/nomad/acl_endpoint_test.go index e9e7e9286..8c668aa75 100644 --- a/nomad/acl_endpoint_test.go +++ b/nomad/acl_endpoint_test.go @@ -28,6 +28,11 @@ func TestACLEndpoint_GetPolicy(t *testing.T) { policy := mock.ACLPolicy() s1.fsm.State().UpsertACLPolicies(1000, []*structs.ACLPolicy{policy}) + // Create a token with one the policy + token := mock.ACLToken() + token.Policies = []string{policy.Name} + s1.fsm.State().UpsertACLTokens(1001, []*structs.ACLToken{token}) + // Lookup the policy get := &structs.ACLPolicySpecificRequest{ Name: policy.Name, @@ -50,6 +55,21 @@ func TestACLEndpoint_GetPolicy(t *testing.T) { } assert.Equal(t, uint64(1000), resp.Index) assert.Nil(t, resp.Policy) + + // Lookup the policy with the token + get = &structs.ACLPolicySpecificRequest{ + Name: policy.Name, + QueryOptions: structs.QueryOptions{ + Region: "global", + AuthToken: token.SecretID, + }, + } + var resp2 structs.SingleACLPolicyResponse + if err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicy", get, &resp2); err != nil { + t.Fatalf("err: %v", err) + } + assert.EqualValues(t, 1000, resp2.Index) + assert.Equal(t, policy, resp2.Policy) } func TestACLEndpoint_GetPolicy_Blocking(t *testing.T) { @@ -290,6 +310,7 @@ func TestACLEndpoint_GetPolicies_Blocking(t *testing.T) { } func TestACLEndpoint_ListPolicies(t *testing.T) { + assert := assert.New(t) t.Parallel() s1, root := testACLServer(t, nil) defer s1.Shutdown() @@ -304,6 +325,11 @@ func TestACLEndpoint_ListPolicies(t *testing.T) { p2.Name = "aaaabbbb-3350-4b4b-d185-0e1992ed43e9" s1.fsm.State().UpsertACLPolicies(1000, []*structs.ACLPolicy{p1, p2}) + // Create a token with one of those policies + token := mock.ACLToken() + token.Policies = []string{p1.Name} + s1.fsm.State().UpsertACLTokens(1001, []*structs.ACLToken{token}) + // Lookup the policies get := &structs.ACLPolicyListRequest{ QueryOptions: structs.QueryOptions{ @@ -315,8 +341,8 @@ func TestACLEndpoint_ListPolicies(t *testing.T) { if err := msgpackrpc.CallWithCodec(codec, "ACL.ListPolicies", get, &resp); err != nil { t.Fatalf("err: %v", err) } - assert.Equal(t, uint64(1000), resp.Index) - assert.Equal(t, 2, len(resp.Policies)) + assert.EqualValues(1000, resp.Index) + assert.Len(resp.Policies, 2) // Lookup the policies by prefix get = &structs.ACLPolicyListRequest{ @@ -330,8 +356,24 @@ func TestACLEndpoint_ListPolicies(t *testing.T) { if err := msgpackrpc.CallWithCodec(codec, "ACL.ListPolicies", get, &resp2); err != nil { t.Fatalf("err: %v", err) } - assert.Equal(t, uint64(1000), resp2.Index) - assert.Equal(t, 1, len(resp2.Policies)) + assert.EqualValues(1000, resp2.Index) + assert.Len(resp2.Policies, 1) + + // List policies using the created token + get = &structs.ACLPolicyListRequest{ + QueryOptions: structs.QueryOptions{ + Region: "global", + AuthToken: token.SecretID, + }, + } + var resp3 structs.ACLPolicyListResponse + if err := msgpackrpc.CallWithCodec(codec, "ACL.ListPolicies", get, &resp3); err != nil { + t.Fatalf("err: %v", err) + } + assert.EqualValues(1000, resp3.Index) + if assert.Len(resp3.Policies, 1) { + assert.Equal(resp3.Policies[0].Name, p1.Name) + } } func TestACLEndpoint_ListPolicies_Blocking(t *testing.T) { diff --git a/website/source/api/acl-policies.html.md b/website/source/api/acl-policies.html.md index a9891b18a..3150a30a0 100644 --- a/website/source/api/acl-policies.html.md +++ b/website/source/api/acl-policies.html.md @@ -26,7 +26,7 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | ACL Required | | ---------------- | ----------------- | ------------ | -| `YES` | `all` | `management` | +| `YES` | `all` | `management` for all policies.
Output when given a non-management token will be limited to the policies on the token itself | ### Sample Request @@ -110,7 +110,7 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | ACL Required | | ---------------- | ----------------- | ------------ | -| `YES` | `all` | `management` | +| `YES` | `all` | `management` or token with access to policy | ### Sample Request