diff --git a/agent/acl_endpoint.go b/agent/acl_endpoint.go index f997530a9..138d07923 100644 --- a/agent/acl_endpoint.go +++ b/agent/acl_endpoint.go @@ -217,7 +217,7 @@ func (s *HTTPServer) ACLPolicyCRUD(resp http.ResponseWriter, req *http.Request) switch req.Method { case "GET": - fn = s.ACLPolicyRead + fn = s.ACLPolicyReadByID case "PUT": fn = s.ACLPolicyWrite @@ -237,10 +237,11 @@ func (s *HTTPServer) ACLPolicyCRUD(resp http.ResponseWriter, req *http.Request) return fn(resp, req, policyID) } -func (s *HTTPServer) ACLPolicyRead(resp http.ResponseWriter, req *http.Request, policyID string) (interface{}, error) { +func (s *HTTPServer) ACLPolicyRead(resp http.ResponseWriter, req *http.Request, policyID, policyName string) (interface{}, error) { args := structs.ACLPolicyGetRequest{ Datacenter: s.agent.config.Datacenter, PolicyID: policyID, + PolicyName: policyName, } if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { return nil, nil @@ -268,6 +269,23 @@ func (s *HTTPServer) ACLPolicyRead(resp http.ResponseWriter, req *http.Request, return out.Policy, nil } +func (s *HTTPServer) ACLPolicyReadByName(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + if s.checkACLDisabled(resp, req) { + return nil, nil + } + + policyName := strings.TrimPrefix(req.URL.Path, "/v1/acl/policy/name/") + if policyName == "" { + return nil, BadRequestError{Reason: "Missing policy Name"} + } + + return s.ACLPolicyRead(resp, req, "", policyName) +} + +func (s *HTTPServer) ACLPolicyReadByID(resp http.ResponseWriter, req *http.Request, policyID string) (interface{}, error) { + return s.ACLPolicyRead(resp, req, policyID, "") +} + func (s *HTTPServer) ACLPolicyCreate(resp http.ResponseWriter, req *http.Request) (interface{}, error) { if s.checkACLDisabled(resp, req) { return nil, nil diff --git a/agent/acl_endpoint_test.go b/agent/acl_endpoint_test.go index f29ec136a..5cfda03e2 100644 --- a/agent/acl_endpoint_test.go +++ b/agent/acl_endpoint_test.go @@ -374,6 +374,17 @@ func TestACL_HTTP(t *testing.T) { require.True(t, ok) require.Equal(t, policyMap[idMap["policy-read-all-nodes"]], policy) }) + + t.Run("Read Name", func(t *testing.T) { + policyName := "read-all-nodes" + req, _ := http.NewRequest("GET", "/v1/acl/policy/name/"+policyName+"?token=root", nil) + resp := httptest.NewRecorder() + raw, err := a.srv.ACLPolicyReadByName(resp, req) + require.NoError(t, err) + policy, ok := raw.(*structs.ACLPolicy) + require.True(t, ok) + require.Equal(t, policyMap[idMap["policy-"+policyName]], policy) + }) }) t.Run("Role", func(t *testing.T) { diff --git a/agent/consul/acl_endpoint.go b/agent/consul/acl_endpoint.go index 0a0315723..e5dda2bf5 100644 --- a/agent/consul/acl_endpoint.go +++ b/agent/consul/acl_endpoint.go @@ -951,7 +951,16 @@ func (a *ACL) PolicyRead(args *structs.ACLPolicyGetRequest, reply *structs.ACLPo return a.srv.blockingQuery(&args.QueryOptions, &reply.QueryMeta, func(ws memdb.WatchSet, state *state.Store) error { - index, policy, err := state.ACLPolicyGetByID(ws, args.PolicyID, &args.EnterpriseMeta) + var ( + index uint64 + policy *structs.ACLPolicy + err error + ) + if args.PolicyID != "" { + index, policy, err = state.ACLPolicyGetByID(ws, args.PolicyID, &args.EnterpriseMeta) + } else { + index, policy, err = state.ACLPolicyGetByName(ws, args.PolicyName, &args.EnterpriseMeta) + } if err != nil { return err diff --git a/agent/consul/acl_endpoint_test.go b/agent/consul/acl_endpoint_test.go index 07dc65401..8d8f79a0d 100644 --- a/agent/consul/acl_endpoint_test.go +++ b/agent/consul/acl_endpoint_test.go @@ -2240,6 +2240,45 @@ func TestACLEndpoint_PolicyRead(t *testing.T) { } } +func TestACLEndpoint_PolicyReadByName(t *testing.T) { + t.Parallel() + dir1, s1 := testServerWithConfig(t, func(c *Config) { + c.ACLDatacenter = "dc1" + c.ACLsEnabled = true + c.ACLMasterToken = "root" + }) + defer os.RemoveAll(dir1) + defer s1.Shutdown() + codec := rpcClient(t, s1) + defer codec.Close() + + testrpc.WaitForLeader(t, s1.RPC, "dc1") + + policy, err := upsertTestPolicy(codec, "root", "dc1") + if err != nil { + t.Fatalf("err: %v", err) + } + + acl := ACL{srv: s1} + + req := structs.ACLPolicyGetRequest{ + Datacenter: "dc1", + PolicyName: policy.Name, + QueryOptions: structs.QueryOptions{Token: "root"}, + } + + resp := structs.ACLPolicyResponse{} + + err = acl.PolicyRead(&req, &resp) + if err != nil { + t.Fatalf("err: %v", err) + } + + if !reflect.DeepEqual(resp.Policy, policy) { + t.Fatalf("tokens are not equal: %v != %v", resp.Policy, policy) + } +} + func TestACLEndpoint_PolicyBatchRead(t *testing.T) { t.Parallel() diff --git a/agent/http_register.go b/agent/http_register.go index 9ee69c702..d18dd4fff 100644 --- a/agent/http_register.go +++ b/agent/http_register.go @@ -14,6 +14,7 @@ func init() { registerEndpoint("/v1/acl/policies", []string{"GET"}, (*HTTPServer).ACLPolicyList) registerEndpoint("/v1/acl/policy", []string{"PUT"}, (*HTTPServer).ACLPolicyCreate) registerEndpoint("/v1/acl/policy/", []string{"GET", "PUT", "DELETE"}, (*HTTPServer).ACLPolicyCRUD) + registerEndpoint("/v1/acl/policy/name/", []string{"GET"}, (*HTTPServer).ACLPolicyReadByName) registerEndpoint("/v1/acl/roles", []string{"GET"}, (*HTTPServer).ACLRoleList) registerEndpoint("/v1/acl/role", []string{"PUT"}, (*HTTPServer).ACLRoleCreate) registerEndpoint("/v1/acl/role/name/", []string{"GET"}, (*HTTPServer).ACLRoleReadByName) diff --git a/agent/structs/acl.go b/agent/structs/acl.go index 793125c0c..be0631079 100644 --- a/agent/structs/acl.go +++ b/agent/structs/acl.go @@ -1238,7 +1238,8 @@ func (r *ACLPolicyDeleteRequest) RequestDatacenter() string { // ACLPolicyGetRequest is used at the RPC layer to perform policy read operations type ACLPolicyGetRequest struct { - PolicyID string // id used for the policy lookup + PolicyID string // id used for the policy lookup (one of PolicyID or PolicyName is allowed) + PolicyName string // name used for the policy lookup (one of PolicyID or PolicyName is allowed) Datacenter string // The datacenter to perform the request within EnterpriseMeta QueryOptions diff --git a/api/acl.go b/api/acl.go index 4057344d8..f1b6fcd46 100644 --- a/api/acl.go +++ b/api/acl.go @@ -666,6 +666,32 @@ func (a *ACL) PolicyRead(policyID string, q *QueryOptions) (*ACLPolicy, *QueryMe return &out, qm, nil } +// PolicyReadByName retrieves the policy details including the rule set with name. +func (a *ACL) PolicyReadByName(policyName string, q *QueryOptions) (*ACLPolicy, *QueryMeta, error) { + r := a.c.newRequest("GET", "/v1/acl/policy/name/"+url.QueryEscape(policyName)) + r.setQueryOptions(q) + found, rtt, resp, err := requireNotFoundOrOK(a.c.doRequest(r)) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + qm := &QueryMeta{} + parseQueryMeta(resp, qm) + qm.RequestTime = rtt + + if !found { + return nil, qm, nil + } + + var out ACLPolicy + if err := decodeBody(resp, &out); err != nil { + return nil, nil, err + } + + return &out, qm, nil +} + // PolicyList retrieves a listing of all policies. The listing does not include the // rules for any policy as those should be retrieved by subsequent calls to PolicyRead. func (a *ACL) PolicyList(q *QueryOptions) ([]*ACLPolicyListEntry, *QueryMeta, error) { diff --git a/api/acl_test.go b/api/acl_test.go index 714ed16be..ace0ae22c 100644 --- a/api/acl_test.go +++ b/api/acl_test.go @@ -205,6 +205,41 @@ func TestAPI_ACLPolicy_CreateReadDelete(t *testing.T) { require.Error(t, err) } +func TestAPI_ACLPolicy_CreateReadByNameDelete(t *testing.T) { + t.Parallel() + c, s := makeACLClient(t) + defer s.Stop() + + acl := c.ACL() + + created, wm, err := acl.PolicyCreate(&ACLPolicy{ + Name: "test-policy", + Description: "test-policy description", + Rules: `node_prefix "" { policy = "read" }`, + Datacenters: []string{"dc1"}, + }, nil) + + require.NoError(t, err) + require.NotNil(t, created) + require.NotEqual(t, "", created.ID) + require.NotEqual(t, 0, wm.RequestTime) + + read, qm, err := acl.PolicyReadByName(created.Name, nil) + require.NoError(t, err) + require.NotEqual(t, 0, qm.LastIndex) + require.True(t, qm.KnownLeader) + + require.Equal(t, created, read) + + wm, err = acl.PolicyDelete(created.ID, nil) + require.NoError(t, err) + require.NotEqual(t, 0, wm.RequestTime) + + read, _, err = acl.PolicyRead(created.ID, nil) + require.Nil(t, read) + require.Error(t, err) +} + func TestAPI_ACLPolicy_CreateUpdate(t *testing.T) { t.Parallel() c, s := makeACLClient(t)