From c0707ebd14535826c185f1519402a548589c335e Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Tue, 10 Oct 2017 10:51:38 -0700 Subject: [PATCH] System ACL enforcement Enforce ACL for System.GarbageCollect and System.ReconcileJobSummaries RPC endpoints. --- nomad/system_endpoint.go | 14 +++++ nomad/system_endpoint_test.go | 90 +++++++++++++++++++++++++++++++ website/source/api/system.html.md | 4 +- 3 files changed, 106 insertions(+), 2 deletions(-) diff --git a/nomad/system_endpoint.go b/nomad/system_endpoint.go index 92d35845b..6a314385e 100644 --- a/nomad/system_endpoint.go +++ b/nomad/system_endpoint.go @@ -18,6 +18,13 @@ func (s *System) GarbageCollect(args *structs.GenericRequest, reply *structs.Gen return err } + // Check management level permissions + if acl, err := s.srv.ResolveToken(args.SecretID); err != nil { + return err + } else if acl != nil && !acl.IsManagement() { + return structs.ErrPermissionDenied + } + // Get the states current index snapshotIndex, err := s.srv.fsm.State().LatestIndex() if err != nil { @@ -35,6 +42,13 @@ func (s *System) ReconcileJobSummaries(args *structs.GenericRequest, reply *stru return err } + // Check management level permissions + if acl, err := s.srv.ResolveToken(args.SecretID); err != nil { + return err + } else if acl != nil && !acl.IsManagement() { + return structs.ErrPermissionDenied + } + _, index, err := s.srv.raftApply(structs.ReconcileJobSummariesRequestType, args) if err != nil { return fmt.Errorf("reconciliation of job summaries failed: %v", err) diff --git a/nomad/system_endpoint_test.go b/nomad/system_endpoint_test.go index bca47357c..46d08ae12 100644 --- a/nomad/system_endpoint_test.go +++ b/nomad/system_endpoint_test.go @@ -7,9 +7,11 @@ import ( memdb "github.com/hashicorp/go-memdb" "github.com/hashicorp/net-rpc-msgpackrpc" + "github.com/hashicorp/nomad/acl" "github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/testutil" + "github.com/stretchr/testify/assert" ) func TestSystemEndpoint_GarbageCollect(t *testing.T) { @@ -62,6 +64,50 @@ func TestSystemEndpoint_GarbageCollect(t *testing.T) { }) } +func TestSystemEndpoint_GarbageCollect_ACL(t *testing.T) { + t.Parallel() + s1, root := testACLServer(t, nil) + defer s1.Shutdown() + codec := rpcClient(t, s1) + assert := assert.New(t) + testutil.WaitForLeader(t, s1.RPC) + state := s1.fsm.State() + + // Create ACL tokens + invalidToken := mock.CreatePolicyAndToken(t, state, 1001, "test-invalid", mock.NodePolicy(acl.PolicyWrite)) + + // Make the GC request + req := &structs.GenericRequest{ + QueryOptions: structs.QueryOptions{ + Region: "global", + }, + } + + // Try without a token and expect failure + { + var resp structs.GenericResponse + err := msgpackrpc.CallWithCodec(codec, "System.GarbageCollect", req, &resp) + assert.NotNil(err) + assert.Contains(err.Error(), structs.ErrPermissionDenied.Error()) + } + + // Try with an invalid token and expect failure + { + req.SecretID = invalidToken.SecretID + var resp structs.GenericResponse + err := msgpackrpc.CallWithCodec(codec, "System.GarbageCollect", req, &resp) + assert.NotNil(err) + assert.Contains(err.Error(), structs.ErrPermissionDenied.Error()) + } + + // Try with a management token + { + req.SecretID = root.SecretID + var resp structs.GenericResponse + assert.Nil(msgpackrpc.CallWithCodec(codec, "System.GarbageCollect", req, &resp)) + } +} + func TestSystemEndpoint_ReconcileSummaries(t *testing.T) { t.Parallel() s1 := testServer(t, nil) @@ -123,3 +169,47 @@ func TestSystemEndpoint_ReconcileSummaries(t *testing.T) { t.Fatalf("err: %s", err) }) } + +func TestSystemEndpoint_ReconcileJobSummaries_ACL(t *testing.T) { + t.Parallel() + s1, root := testACLServer(t, nil) + defer s1.Shutdown() + codec := rpcClient(t, s1) + assert := assert.New(t) + testutil.WaitForLeader(t, s1.RPC) + state := s1.fsm.State() + + // Create ACL tokens + invalidToken := mock.CreatePolicyAndToken(t, state, 1001, "test-invalid", mock.NodePolicy(acl.PolicyWrite)) + + // Make the request + req := &structs.GenericRequest{ + QueryOptions: structs.QueryOptions{ + Region: "global", + }, + } + + // Try without a token and expect failure + { + var resp structs.GenericResponse + err := msgpackrpc.CallWithCodec(codec, "System.ReconcileJobSummaries", req, &resp) + assert.NotNil(err) + assert.Contains(err.Error(), structs.ErrPermissionDenied.Error()) + } + + // Try with an invalid token and expect failure + { + req.SecretID = invalidToken.SecretID + var resp structs.GenericResponse + err := msgpackrpc.CallWithCodec(codec, "System.ReconcileJobSummaries", req, &resp) + assert.NotNil(err) + assert.Contains(err.Error(), structs.ErrPermissionDenied.Error()) + } + + // Try with a management token + { + req.SecretID = root.SecretID + var resp structs.GenericResponse + assert.Nil(msgpackrpc.CallWithCodec(codec, "System.ReconcileJobSummaries", req, &resp)) + } +} diff --git a/website/source/api/system.html.md b/website/source/api/system.html.md index 4b824958c..9e0fe3f70 100644 --- a/website/source/api/system.html.md +++ b/website/source/api/system.html.md @@ -26,7 +26,7 @@ The table below shows this endpoint's support for | Blocking Queries | ACL Required | | ---------------- | ------------ | -| `NO` | `none` | +| `NO` | `management` | ### Sample Request @@ -50,7 +50,7 @@ The table below shows this endpoint's support for | Blocking Queries | ACL Required | | ---------------- | ------------ | -| `NO` | `none` | +| `NO` | `management` | ### Sample Request ```text