mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
http: add ACL Role HTTP endpoints for CRUD actions.
These new endpoints are exposed under the /v1/acl/roles and /v1/acl/role endpoints.
This commit is contained in:
@@ -322,3 +322,206 @@ func (s *HTTPServer) ExchangeOneTimeToken(resp http.ResponseWriter, req *http.Re
|
||||
setIndex(resp, out.Index)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ACLRoleListRequest performs a listing of ACL roles and is callable via the
|
||||
// /v1/acl/roles HTTP API.
|
||||
func (s *HTTPServer) ACLRoleListRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
|
||||
// The endpoint only supports GET requests.
|
||||
if req.Method != http.MethodGet {
|
||||
return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
|
||||
}
|
||||
|
||||
// Set up the request args and parse this to ensure the query options are
|
||||
// set.
|
||||
args := structs.ACLRolesListRequest{}
|
||||
|
||||
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Perform the RPC request.
|
||||
var reply structs.ACLRolesListResponse
|
||||
if err := s.agent.RPC(structs.ACLListRolesRPCMethod, &args, &reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
setMeta(resp, &reply.QueryMeta)
|
||||
|
||||
if reply.ACLRoles == nil {
|
||||
reply.ACLRoles = make([]*structs.ACLRole, 0)
|
||||
}
|
||||
return reply.ACLRoles, nil
|
||||
}
|
||||
|
||||
// ACLRoleRequest creates a new ACL role and is callable via the
|
||||
// /v1/acl/role HTTP API.
|
||||
func (s *HTTPServer) ACLRoleRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
|
||||
// // The endpoint only supports PUT or POST requests.
|
||||
if !(req.Method == http.MethodPut || req.Method == http.MethodPost) {
|
||||
return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
|
||||
}
|
||||
|
||||
// Use the generic upsert function without setting an ID as this will be
|
||||
// handled by the Nomad leader.
|
||||
return s.aclRoleUpsertRequest(resp, req, "")
|
||||
}
|
||||
|
||||
// ACLRoleSpecificRequest is callable via the /v1/acl/role/ HTTP API and
|
||||
// handles read via both the role name and ID, updates, and deletions.
|
||||
func (s *HTTPServer) ACLRoleSpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
|
||||
// Grab the suffix of the request, so we can further understand it.
|
||||
reqSuffix := strings.TrimPrefix(req.URL.Path, "/v1/acl/role/")
|
||||
|
||||
// Split the request suffix in order to identify whether this is a lookup
|
||||
// of a service, or whether this includes a service and service identifier.
|
||||
suffixParts := strings.Split(reqSuffix, "/")
|
||||
|
||||
switch len(suffixParts) {
|
||||
case 1:
|
||||
// Ensure the role ID is not an empty string which is possible if the
|
||||
// caller requested "/v1/acl/role/"
|
||||
if suffixParts[0] == "" {
|
||||
return nil, CodedError(http.StatusBadRequest, "missing ACL role ID")
|
||||
}
|
||||
return s.aclRoleRequest(resp, req, suffixParts[0])
|
||||
case 2:
|
||||
// This endpoint only supports GET.
|
||||
if req.Method != http.MethodGet {
|
||||
return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
|
||||
}
|
||||
|
||||
// Ensure that the path is correct, otherwise the call could use
|
||||
// "/v1/acl/role/foobar/role-name" and successfully pass through here.
|
||||
if suffixParts[0] != "name" {
|
||||
return nil, CodedError(http.StatusBadRequest, "invalid URI")
|
||||
}
|
||||
|
||||
// Ensure the role name is not an empty string which is possible if the
|
||||
// caller requested "/v1/acl/role/name/"
|
||||
if suffixParts[1] == "" {
|
||||
return nil, CodedError(http.StatusBadRequest, "missing ACL role name")
|
||||
}
|
||||
|
||||
return s.aclRoleGetByNameRequest(resp, req, suffixParts[1])
|
||||
|
||||
default:
|
||||
return nil, CodedError(http.StatusBadRequest, "invalid URI")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HTTPServer) aclRoleRequest(
|
||||
resp http.ResponseWriter, req *http.Request, roleID string) (interface{}, error) {
|
||||
|
||||
// Identify the method which indicates which downstream function should be
|
||||
// called.
|
||||
switch req.Method {
|
||||
case http.MethodGet:
|
||||
return s.aclRoleGetByIDRequest(resp, req, roleID)
|
||||
case http.MethodDelete:
|
||||
return s.aclRoleDeleteRequest(resp, req, roleID)
|
||||
case http.MethodPost, http.MethodPut:
|
||||
return s.aclRoleUpsertRequest(resp, req, roleID)
|
||||
default:
|
||||
return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HTTPServer) aclRoleGetByIDRequest(
|
||||
resp http.ResponseWriter, req *http.Request, roleID string) (interface{}, error) {
|
||||
|
||||
args := structs.ACLRoleByIDRequest{
|
||||
RoleID: roleID,
|
||||
}
|
||||
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var reply structs.ACLRoleByIDResponse
|
||||
if err := s.agent.RPC(structs.ACLGetRoleByIDRPCMethod, &args, &reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setMeta(resp, &reply.QueryMeta)
|
||||
|
||||
if reply.ACLRole == nil {
|
||||
return nil, CodedError(http.StatusNotFound, "ACL role not found")
|
||||
}
|
||||
return reply.ACLRole, nil
|
||||
}
|
||||
|
||||
func (s *HTTPServer) aclRoleDeleteRequest(
|
||||
resp http.ResponseWriter, req *http.Request, roleID string) (interface{}, error) {
|
||||
|
||||
args := structs.ACLRolesDeleteByIDRequest{
|
||||
ACLRoleIDs: []string{roleID},
|
||||
}
|
||||
s.parseWriteRequest(req, &args.WriteRequest)
|
||||
|
||||
var reply structs.ACLRolesDeleteByIDResponse
|
||||
if err := s.agent.RPC(structs.ACLDeleteRolesByIDRPCMethod, &args, &reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setIndex(resp, reply.Index)
|
||||
return nil, nil
|
||||
|
||||
}
|
||||
|
||||
// aclRoleUpsertRequest handles upserting an ACL to the Nomad servers. It can
|
||||
// handle both new creations, and updates to existing roles.
|
||||
func (s *HTTPServer) aclRoleUpsertRequest(
|
||||
resp http.ResponseWriter, req *http.Request, roleID string) (interface{}, error) {
|
||||
|
||||
// Decode the ACL role.
|
||||
var aclRole structs.ACLRole
|
||||
if err := decodeBody(req, &aclRole); err != nil {
|
||||
return nil, CodedError(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
// Ensure the request path ID matches the ACL role ID that was decoded.
|
||||
// Only perform this check on updates as a generic error on creation might
|
||||
// be confusing to operators as there is no specific role request path.
|
||||
if roleID != "" && roleID != aclRole.ID {
|
||||
return nil, CodedError(http.StatusBadRequest, "ACL role ID does not match request path")
|
||||
}
|
||||
|
||||
args := structs.ACLRolesUpsertRequest{
|
||||
ACLRoles: []*structs.ACLRole{&aclRole},
|
||||
}
|
||||
s.parseWriteRequest(req, &args.WriteRequest)
|
||||
|
||||
var out structs.ACLRolesUpsertResponse
|
||||
if err := s.agent.RPC(structs.ACLUpsertRolesRPCMethod, &args, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setIndex(resp, out.Index)
|
||||
|
||||
if len(out.ACLRoles) > 0 {
|
||||
return out.ACLRoles[0], nil
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
}
|
||||
|
||||
func (s *HTTPServer) aclRoleGetByNameRequest(
|
||||
resp http.ResponseWriter, req *http.Request, roleName string) (interface{}, error) {
|
||||
|
||||
args := structs.ACLRoleByNameRequest{
|
||||
RoleName: roleName,
|
||||
}
|
||||
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var reply structs.ACLRoleByNameResponse
|
||||
if err := s.agent.RPC(structs.ACLGetRoleByNameRPCMethod, &args, &reply); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setMeta(resp, &reply.QueryMeta)
|
||||
|
||||
if reply.ACLRole == nil {
|
||||
return nil, CodedError(http.StatusNotFound, "ACL role not found")
|
||||
}
|
||||
return reply.ACLRole, nil
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/ci"
|
||||
"github.com/hashicorp/nomad/helper/uuid"
|
||||
"github.com/hashicorp/nomad/nomad/mock"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -558,3 +560,438 @@ func TestHTTP_OneTimeToken(t *testing.T) {
|
||||
require.EqualError(t, err, structs.ErrPermissionDenied.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func TestHTTPServer_ACLRoleListRequest(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
testFn func(srv *TestAgent)
|
||||
}{
|
||||
{
|
||||
name: "no auth token set",
|
||||
testFn: func(srv *TestAgent) {
|
||||
|
||||
// Build the HTTP request.
|
||||
req, err := http.NewRequest(http.MethodGet, "/v1/acl/roles", nil)
|
||||
require.NoError(t, err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err := srv.Server.ACLRoleListRequest(respW, req)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "Permission denied")
|
||||
require.Nil(t, obj)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid method",
|
||||
testFn: func(srv *TestAgent) {
|
||||
|
||||
// Build the HTTP request.
|
||||
req, err := http.NewRequest(http.MethodConnect, "/v1/acl/roles", nil)
|
||||
require.NoError(t, err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Ensure we have a token set.
|
||||
setToken(req, srv.RootToken)
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err := srv.Server.ACLRoleListRequest(respW, req)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "Invalid method")
|
||||
require.Nil(t, obj)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no roles in state",
|
||||
testFn: func(srv *TestAgent) {
|
||||
|
||||
// Build the HTTP request.
|
||||
req, err := http.NewRequest(http.MethodGet, "/v1/acl/roles", nil)
|
||||
require.NoError(t, err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Ensure we have a token set.
|
||||
setToken(req, srv.RootToken)
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err := srv.Server.ACLRoleListRequest(respW, req)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, obj.([]*structs.ACLRole))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "roles in state",
|
||||
testFn: func(srv *TestAgent) {
|
||||
|
||||
// Create the policies our ACL roles wants to link to.
|
||||
policy1 := mock.ACLPolicy()
|
||||
policy1.Name = "mocked-test-policy-1"
|
||||
policy2 := mock.ACLPolicy()
|
||||
policy2.Name = "mocked-test-policy-2"
|
||||
|
||||
require.NoError(t, srv.server.State().UpsertACLPolicies(
|
||||
structs.MsgTypeTestSetup, 10, []*structs.ACLPolicy{policy1, policy2}))
|
||||
|
||||
// Create two ACL roles and put these directly into state.
|
||||
aclRoles := []*structs.ACLRole{mock.ACLRole(), mock.ACLRole()}
|
||||
require.NoError(t, srv.server.State().UpsertACLRoles(structs.MsgTypeTestSetup, 20, aclRoles))
|
||||
|
||||
// Build the HTTP request.
|
||||
req, err := http.NewRequest(http.MethodGet, "/v1/acl/roles", nil)
|
||||
require.NoError(t, err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Ensure we have a token set.
|
||||
setToken(req, srv.RootToken)
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err := srv.Server.ACLRoleListRequest(respW, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, obj.([]*structs.ACLRole), 2)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "roles in state using prefix",
|
||||
testFn: func(srv *TestAgent) {
|
||||
|
||||
// Create the policies our ACL roles wants to link to.
|
||||
policy1 := mock.ACLPolicy()
|
||||
policy1.Name = "mocked-test-policy-1"
|
||||
policy2 := mock.ACLPolicy()
|
||||
policy2.Name = "mocked-test-policy-2"
|
||||
|
||||
require.NoError(t, srv.server.State().UpsertACLPolicies(
|
||||
structs.MsgTypeTestSetup, 10, []*structs.ACLPolicy{policy1, policy2}))
|
||||
|
||||
// Create two ACL roles and put these directly into state, one
|
||||
// using a custom prefix.
|
||||
aclRoles := []*structs.ACLRole{mock.ACLRole(), mock.ACLRole()}
|
||||
aclRoles[1].ID = "badger-badger-badger-" + uuid.Generate()
|
||||
require.NoError(t, srv.server.State().UpsertACLRoles(structs.MsgTypeTestSetup, 20, aclRoles))
|
||||
|
||||
// Build the HTTP request.
|
||||
req, err := http.NewRequest(http.MethodGet, "/v1/acl/roles?prefix=badger-badger-badger", nil)
|
||||
require.NoError(t, err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Ensure we have a token set.
|
||||
setToken(req, srv.RootToken)
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err := srv.Server.ACLRoleListRequest(respW, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, obj.([]*structs.ACLRole), 1)
|
||||
require.Contains(t, obj.([]*structs.ACLRole)[0].ID, "badger-badger-badger")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
httpACLTest(t, nil, tc.testFn)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPServer_ACLRoleRequest(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
testFn func(srv *TestAgent)
|
||||
}{
|
||||
{
|
||||
name: "no auth token set",
|
||||
testFn: func(srv *TestAgent) {
|
||||
|
||||
// Create a mock role to use in the request body.
|
||||
mockACLRole := mock.ACLRole()
|
||||
mockACLRole.ID = ""
|
||||
|
||||
// Build the HTTP request.
|
||||
req, err := http.NewRequest(http.MethodPut, "/v1/acl/role", encodeReq(mockACLRole))
|
||||
require.NoError(t, err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err := srv.Server.ACLRoleRequest(respW, req)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "Permission denied")
|
||||
require.Nil(t, obj)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid method",
|
||||
testFn: func(srv *TestAgent) {
|
||||
|
||||
// Build the HTTP request.
|
||||
req, err := http.NewRequest(http.MethodConnect, "/v1/acl/role", nil)
|
||||
require.NoError(t, err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Ensure we have a token set.
|
||||
setToken(req, srv.RootToken)
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err := srv.Server.ACLRoleRequest(respW, req)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "Invalid method")
|
||||
require.Nil(t, obj)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful upsert",
|
||||
testFn: func(srv *TestAgent) {
|
||||
|
||||
// Create the policies our ACL roles wants to link to.
|
||||
policy1 := mock.ACLPolicy()
|
||||
policy1.Name = "mocked-test-policy-1"
|
||||
policy2 := mock.ACLPolicy()
|
||||
policy2.Name = "mocked-test-policy-2"
|
||||
|
||||
require.NoError(t, srv.server.State().UpsertACLPolicies(
|
||||
structs.MsgTypeTestSetup, 10, []*structs.ACLPolicy{policy1, policy2}))
|
||||
|
||||
// Create a mock role to use in the request body.
|
||||
mockACLRole := mock.ACLRole()
|
||||
mockACLRole.ID = ""
|
||||
|
||||
// Build the HTTP request.
|
||||
req, err := http.NewRequest(http.MethodPut, "/v1/acl/role", encodeReq(mockACLRole))
|
||||
require.NoError(t, err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Ensure we have a token set.
|
||||
setToken(req, srv.RootToken)
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err := srv.Server.ACLRoleRequest(respW, req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, obj)
|
||||
require.Equal(t, obj.(*structs.ACLRole).Hash, mockACLRole.Hash)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
httpACLTest(t, nil, tc.testFn)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPServer_ACLRoleSpecificRequest(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
testFn func(srv *TestAgent)
|
||||
}{
|
||||
{
|
||||
name: "invalid URI",
|
||||
testFn: func(srv *TestAgent) {
|
||||
|
||||
// Build the HTTP request.
|
||||
req, err := http.NewRequest(http.MethodGet, "/v1/acl/role/name/this/is/will/not/work", nil)
|
||||
require.NoError(t, err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err := srv.Server.ACLRoleSpecificRequest(respW, req)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "invalid URI")
|
||||
require.Nil(t, obj)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid role name lookalike URI",
|
||||
testFn: func(srv *TestAgent) {
|
||||
|
||||
// Build the HTTP request.
|
||||
req, err := http.NewRequest(http.MethodGet, "/v1/acl/role/foobar/rolename", nil)
|
||||
require.NoError(t, err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err := srv.Server.ACLRoleSpecificRequest(respW, req)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "invalid URI")
|
||||
require.Nil(t, obj)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing role name",
|
||||
testFn: func(srv *TestAgent) {
|
||||
|
||||
// Build the HTTP request.
|
||||
req, err := http.NewRequest(http.MethodGet, "/v1/acl/role/name/", nil)
|
||||
require.NoError(t, err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err := srv.Server.ACLRoleSpecificRequest(respW, req)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "missing ACL role name")
|
||||
require.Nil(t, obj)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing role ID",
|
||||
testFn: func(srv *TestAgent) {
|
||||
|
||||
// Build the HTTP request.
|
||||
req, err := http.NewRequest(http.MethodGet, "/v1/acl/role/", nil)
|
||||
require.NoError(t, err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err := srv.Server.ACLRoleSpecificRequest(respW, req)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "missing ACL role ID")
|
||||
require.Nil(t, obj)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "role name incorrect method",
|
||||
testFn: func(srv *TestAgent) {
|
||||
|
||||
// Build the HTTP request.
|
||||
req, err := http.NewRequest(http.MethodConnect, "/v1/acl/role/name/foobar", nil)
|
||||
require.NoError(t, err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err := srv.Server.ACLRoleSpecificRequest(respW, req)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "Invalid method")
|
||||
require.Nil(t, obj)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "role ID incorrect method",
|
||||
testFn: func(srv *TestAgent) {
|
||||
|
||||
// Build the HTTP request.
|
||||
req, err := http.NewRequest(http.MethodConnect, "/v1/acl/role/foobar", nil)
|
||||
require.NoError(t, err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err := srv.Server.ACLRoleSpecificRequest(respW, req)
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "Invalid method")
|
||||
require.Nil(t, obj)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get role by name",
|
||||
testFn: func(srv *TestAgent) {
|
||||
|
||||
// Create the policies our ACL roles wants to link to.
|
||||
policy1 := mock.ACLPolicy()
|
||||
policy1.Name = "mocked-test-policy-1"
|
||||
policy2 := mock.ACLPolicy()
|
||||
policy2.Name = "mocked-test-policy-2"
|
||||
|
||||
require.NoError(t, srv.server.State().UpsertACLPolicies(
|
||||
structs.MsgTypeTestSetup, 10, []*structs.ACLPolicy{policy1, policy2}))
|
||||
|
||||
// Create a mock role and put directly into state.
|
||||
mockACLRole := mock.ACLRole()
|
||||
require.NoError(t, srv.server.State().UpsertACLRoles(
|
||||
structs.MsgTypeTestSetup, 20, []*structs.ACLRole{mockACLRole}))
|
||||
|
||||
url := fmt.Sprintf("/v1/acl/role/name/%s", mockACLRole.Name)
|
||||
|
||||
// Build the HTTP request.
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
require.NoError(t, err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Ensure we have a token set.
|
||||
setToken(req, srv.RootToken)
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err := srv.Server.ACLRoleSpecificRequest(respW, req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, obj.(*structs.ACLRole).Hash, mockACLRole.Hash)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get, update, and delete role by ID",
|
||||
testFn: func(srv *TestAgent) {
|
||||
|
||||
// Create the policies our ACL roles wants to link to.
|
||||
policy1 := mock.ACLPolicy()
|
||||
policy1.Name = "mocked-test-policy-1"
|
||||
policy2 := mock.ACLPolicy()
|
||||
policy2.Name = "mocked-test-policy-2"
|
||||
|
||||
require.NoError(t, srv.server.State().UpsertACLPolicies(
|
||||
structs.MsgTypeTestSetup, 10, []*structs.ACLPolicy{policy1, policy2}))
|
||||
|
||||
// Create a mock role and put directly into state.
|
||||
mockACLRole := mock.ACLRole()
|
||||
require.NoError(t, srv.server.State().UpsertACLRoles(
|
||||
structs.MsgTypeTestSetup, 20, []*structs.ACLRole{mockACLRole}))
|
||||
|
||||
url := fmt.Sprintf("/v1/acl/role/%s", mockACLRole.ID)
|
||||
|
||||
// Build the HTTP request to read the role using its ID.
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
require.NoError(t, err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Ensure we have a token set.
|
||||
setToken(req, srv.RootToken)
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err := srv.Server.ACLRoleSpecificRequest(respW, req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, obj.(*structs.ACLRole).Hash, mockACLRole.Hash)
|
||||
|
||||
// Update the role policy list and make the request via the
|
||||
// HTTP API.
|
||||
mockACLRole.Policies = []*structs.ACLRolePolicyLink{{Name: "mocked-test-policy-1"}}
|
||||
|
||||
req, err = http.NewRequest(http.MethodPost, url, encodeReq(mockACLRole))
|
||||
require.NoError(t, err)
|
||||
respW = httptest.NewRecorder()
|
||||
|
||||
// Ensure we have a token set.
|
||||
setToken(req, srv.RootToken)
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err = srv.Server.ACLRoleSpecificRequest(respW, req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, obj.(*structs.ACLRole).Policies, mockACLRole.Policies)
|
||||
|
||||
// Delete the ACL role using its ID.
|
||||
req, err = http.NewRequest(http.MethodDelete, url, nil)
|
||||
require.NoError(t, err)
|
||||
respW = httptest.NewRecorder()
|
||||
|
||||
// Ensure we have a token set.
|
||||
setToken(req, srv.RootToken)
|
||||
|
||||
// Send the HTTP request.
|
||||
obj, err = srv.Server.ACLRoleSpecificRequest(respW, req)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, obj)
|
||||
|
||||
// Ensure the ACL role is no longer stored within state.
|
||||
aclRole, err := srv.server.State().GetACLRoleByID(nil, mockACLRole.ID)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, aclRole)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
httpACLTest(t, nil, tc.testFn)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,6 +381,11 @@ func (s HTTPServer) registerHandlers(enableDebug bool) {
|
||||
s.mux.HandleFunc("/v1/acl/token", s.wrap(s.ACLTokenSpecificRequest))
|
||||
s.mux.HandleFunc("/v1/acl/token/", s.wrap(s.ACLTokenSpecificRequest))
|
||||
|
||||
// Register our ACL role handlers.
|
||||
s.mux.HandleFunc("/v1/acl/roles", s.wrap(s.ACLRoleListRequest))
|
||||
s.mux.HandleFunc("/v1/acl/role", s.wrap(s.ACLRoleRequest))
|
||||
s.mux.HandleFunc("/v1/acl/role/", s.wrap(s.ACLRoleSpecificRequest))
|
||||
|
||||
s.mux.Handle("/v1/client/fs/", wrapCORS(s.wrap(s.FsRequest)))
|
||||
s.mux.HandleFunc("/v1/client/gc", s.wrap(s.ClientGCRequest))
|
||||
s.mux.Handle("/v1/client/stats", wrapCORS(s.wrap(s.ClientStatsRequest)))
|
||||
|
||||
Reference in New Issue
Block a user