Merge pull request #3336 from hashicorp/f-acl-client-agent
/v1/client/agent/* ACL enforcement
This commit is contained in:
commit
1184df7d8f
|
@ -46,6 +46,16 @@ func (s *HTTPServer) AgentSelfRequest(resp http.ResponseWriter, req *http.Reques
|
|||
return nil, CodedError(405, ErrInvalidMethod)
|
||||
}
|
||||
|
||||
var secret string
|
||||
s.parseToken(req, &secret)
|
||||
|
||||
// Check agent read permissions
|
||||
if aclObj, err := s.agent.Client().ResolveToken(secret); err != nil {
|
||||
return nil, err
|
||||
} else if aclObj != nil && !aclObj.AllowAgentRead() {
|
||||
return nil, structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Get the member as a server
|
||||
var member serf.Member
|
||||
srv := s.agent.Server()
|
||||
|
@ -99,6 +109,17 @@ func (s *HTTPServer) AgentMembersRequest(resp http.ResponseWriter, req *http.Req
|
|||
if req.Method != "GET" {
|
||||
return nil, CodedError(405, ErrInvalidMethod)
|
||||
}
|
||||
|
||||
var secret string
|
||||
s.parseToken(req, &secret)
|
||||
|
||||
// Check node read permissions
|
||||
if aclObj, err := s.agent.Client().ResolveToken(secret); err != nil {
|
||||
return nil, err
|
||||
} else if aclObj != nil && !aclObj.AllowNodeRead() {
|
||||
return nil, structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
args := &structs.GenericRequest{}
|
||||
var out structs.ServerMembersResponse
|
||||
if err := s.agent.RPC("Status.Members", args, &out); err != nil {
|
||||
|
@ -117,6 +138,16 @@ func (s *HTTPServer) AgentForceLeaveRequest(resp http.ResponseWriter, req *http.
|
|||
return nil, CodedError(501, ErrInvalidMethod)
|
||||
}
|
||||
|
||||
var secret string
|
||||
s.parseToken(req, &secret)
|
||||
|
||||
// Check agent write permissions
|
||||
if aclObj, err := s.agent.Client().ResolveToken(secret); err != nil {
|
||||
return nil, err
|
||||
} else if aclObj != nil && !aclObj.AllowAgentWrite() {
|
||||
return nil, structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Get the node to eject
|
||||
node := req.URL.Query().Get("node")
|
||||
if node == "" {
|
||||
|
@ -148,6 +179,16 @@ func (s *HTTPServer) listServers(resp http.ResponseWriter, req *http.Request) (i
|
|||
return nil, CodedError(501, ErrInvalidMethod)
|
||||
}
|
||||
|
||||
var secret string
|
||||
s.parseToken(req, &secret)
|
||||
|
||||
// Check agent read permissions
|
||||
if aclObj, err := s.agent.Client().ResolveToken(secret); err != nil {
|
||||
return nil, err
|
||||
} else if aclObj != nil && !aclObj.AllowAgentRead() {
|
||||
return nil, structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
peers := s.agent.client.GetServers()
|
||||
sort.Strings(peers)
|
||||
return peers, nil
|
||||
|
@ -165,6 +206,16 @@ func (s *HTTPServer) updateServers(resp http.ResponseWriter, req *http.Request)
|
|||
return nil, CodedError(400, "missing server address")
|
||||
}
|
||||
|
||||
var secret string
|
||||
s.parseToken(req, &secret)
|
||||
|
||||
// Check agent write permissions
|
||||
if aclObj, err := s.agent.Client().ResolveToken(secret); err != nil {
|
||||
return nil, err
|
||||
} else if aclObj != nil && !aclObj.AllowAgentWrite() {
|
||||
return nil, structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Set the servers list into the client
|
||||
s.agent.logger.Printf("[TRACE] Adding servers %+q to the client's primary server list", servers)
|
||||
if err := client.SetServers(servers); err != nil {
|
||||
|
@ -182,6 +233,16 @@ func (s *HTTPServer) KeyringOperationRequest(resp http.ResponseWriter, req *http
|
|||
return nil, CodedError(501, ErrInvalidMethod)
|
||||
}
|
||||
|
||||
var secret string
|
||||
s.parseToken(req, &secret)
|
||||
|
||||
// Check agent write permissions
|
||||
if aclObj, err := s.agent.Client().ResolveToken(secret); err != nil {
|
||||
return nil, err
|
||||
} else if aclObj != nil && !aclObj.AllowAgentWrite() {
|
||||
return nil, structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
kmgr := srv.KeyManager()
|
||||
var sresp *serf.KeyResponse
|
||||
var err error
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/acl"
|
||||
"github.com/hashicorp/nomad/nomad/mock"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -56,6 +58,62 @@ func TestHTTP_AgentSelf(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestHTTP_AgentSelf_ACL(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
|
||||
httpACLTest(t, nil, func(s *TestAgent) {
|
||||
state := s.Agent.server.State()
|
||||
|
||||
// Make the HTTP request
|
||||
req, err := http.NewRequest("GET", "/v1/agent/self", nil)
|
||||
assert.Nil(err)
|
||||
|
||||
// Try request without a token and expect failure
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
_, err := s.Server.AgentSelfRequest(respW, req)
|
||||
assert.NotNil(err)
|
||||
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
}
|
||||
|
||||
// Try request with an invalid token and expect failure
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
token := mock.CreatePolicyAndToken(t, state, 1005, "invalid", mock.NodePolicy(acl.PolicyWrite))
|
||||
setToken(req, token)
|
||||
_, err := s.Server.AgentSelfRequest(respW, req)
|
||||
assert.NotNil(err)
|
||||
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
}
|
||||
|
||||
// Try request with a valid token
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
token := mock.CreatePolicyAndToken(t, state, 1007, "valid", mock.AgentPolicy(acl.PolicyWrite))
|
||||
setToken(req, token)
|
||||
obj, err := s.Server.AgentSelfRequest(respW, req)
|
||||
assert.Nil(err)
|
||||
|
||||
self := obj.(agentSelf)
|
||||
assert.NotNil(self.Config)
|
||||
assert.NotNil(self.Stats)
|
||||
}
|
||||
|
||||
// Try request with a root token
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
setToken(req, s.RootToken)
|
||||
obj, err := s.Server.AgentSelfRequest(respW, req)
|
||||
assert.Nil(err)
|
||||
|
||||
self := obj.(agentSelf)
|
||||
assert.NotNil(self.Config)
|
||||
assert.NotNil(self.Stats)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestHTTP_AgentJoin(t *testing.T) {
|
||||
// TODO(alexdadgar)
|
||||
// t.Parallel()
|
||||
|
@ -113,6 +171,60 @@ func TestHTTP_AgentMembers(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestHTTP_AgentMembers_ACL(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
|
||||
httpACLTest(t, nil, func(s *TestAgent) {
|
||||
state := s.Agent.server.State()
|
||||
|
||||
// Make the HTTP request
|
||||
req, err := http.NewRequest("GET", "/v1/agent/members", nil)
|
||||
assert.Nil(err)
|
||||
|
||||
// Try request without a token and expect failure
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
_, err := s.Server.AgentMembersRequest(respW, req)
|
||||
assert.NotNil(err)
|
||||
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
}
|
||||
|
||||
// Try request with an invalid token and expect failure
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
token := mock.CreatePolicyAndToken(t, state, 1005, "invalid", mock.AgentPolicy(acl.PolicyWrite))
|
||||
setToken(req, token)
|
||||
_, err := s.Server.AgentMembersRequest(respW, req)
|
||||
assert.NotNil(err)
|
||||
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
}
|
||||
|
||||
// Try request with a valid token
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
token := mock.CreatePolicyAndToken(t, state, 1007, "valid", mock.NodePolicy(acl.PolicyRead))
|
||||
setToken(req, token)
|
||||
obj, err := s.Server.AgentMembersRequest(respW, req)
|
||||
assert.Nil(err)
|
||||
|
||||
members := obj.(structs.ServerMembersResponse)
|
||||
assert.Len(members.Members, 1)
|
||||
}
|
||||
|
||||
// Try request with a root token
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
setToken(req, s.RootToken)
|
||||
obj, err := s.Server.AgentMembersRequest(respW, req)
|
||||
assert.Nil(err)
|
||||
|
||||
members := obj.(structs.ServerMembersResponse)
|
||||
assert.Len(members.Members, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestHTTP_AgentForceLeave(t *testing.T) {
|
||||
t.Parallel()
|
||||
httpTest(t, nil, func(s *TestAgent) {
|
||||
|
@ -131,6 +243,56 @@ func TestHTTP_AgentForceLeave(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestHTTP_AgentForceLeave_ACL(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
|
||||
httpACLTest(t, nil, func(s *TestAgent) {
|
||||
state := s.Agent.server.State()
|
||||
|
||||
// Make the HTTP request
|
||||
req, err := http.NewRequest("PUT", "/v1/agent/force-leave?node=foo", nil)
|
||||
assert.Nil(err)
|
||||
|
||||
// Try request without a token and expect failure
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
_, err := s.Server.AgentForceLeaveRequest(respW, req)
|
||||
assert.NotNil(err)
|
||||
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
}
|
||||
|
||||
// Try request with an invalid token and expect failure
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
token := mock.CreatePolicyAndToken(t, state, 1005, "invalid", mock.NodePolicy(acl.PolicyRead))
|
||||
setToken(req, token)
|
||||
_, err := s.Server.AgentForceLeaveRequest(respW, req)
|
||||
assert.NotNil(err)
|
||||
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
}
|
||||
|
||||
// Try request with a valid token
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
token := mock.CreatePolicyAndToken(t, state, 1007, "valid", mock.AgentPolicy(acl.PolicyWrite))
|
||||
setToken(req, token)
|
||||
_, err := s.Server.AgentForceLeaveRequest(respW, req)
|
||||
assert.Nil(err)
|
||||
assert.Equal(http.StatusOK, respW.Code)
|
||||
}
|
||||
|
||||
// Try request with a root token
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
setToken(req, s.RootToken)
|
||||
_, err := s.Server.AgentForceLeaveRequest(respW, req)
|
||||
assert.Nil(err)
|
||||
assert.Equal(http.StatusOK, respW.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestHTTP_AgentSetServers(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
|
@ -173,6 +335,128 @@ func TestHTTP_AgentSetServers(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestHTTP_AgentSetServers_ACL(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
|
||||
httpACLTest(t, nil, func(s *TestAgent) {
|
||||
state := s.Agent.server.State()
|
||||
|
||||
// Make the HTTP request
|
||||
req, err := http.NewRequest("PUT", "/v1/agent/servers?address=127.0.0.1%3A4647&address=127.0.0.2%3A4647&address=127.0.0.3%3A4647", nil)
|
||||
assert.Nil(err)
|
||||
|
||||
// Try request without a token and expect failure
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
_, err := s.Server.AgentServersRequest(respW, req)
|
||||
assert.NotNil(err)
|
||||
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
}
|
||||
|
||||
// Try request with an invalid token and expect failure
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
token := mock.CreatePolicyAndToken(t, state, 1005, "invalid", mock.NodePolicy(acl.PolicyRead))
|
||||
setToken(req, token)
|
||||
_, err := s.Server.AgentServersRequest(respW, req)
|
||||
assert.NotNil(err)
|
||||
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
}
|
||||
|
||||
// Try request with a valid token
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
token := mock.CreatePolicyAndToken(t, state, 1007, "valid", mock.AgentPolicy(acl.PolicyWrite))
|
||||
setToken(req, token)
|
||||
_, err := s.Server.AgentServersRequest(respW, req)
|
||||
assert.Nil(err)
|
||||
assert.Equal(http.StatusOK, respW.Code)
|
||||
}
|
||||
|
||||
// Try request with a root token
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
setToken(req, s.RootToken)
|
||||
_, err := s.Server.AgentServersRequest(respW, req)
|
||||
assert.Nil(err)
|
||||
assert.Equal(http.StatusOK, respW.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestHTTP_AgentListServers_ACL(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
|
||||
httpACLTest(t, nil, func(s *TestAgent) {
|
||||
state := s.Agent.server.State()
|
||||
|
||||
// Set some servers
|
||||
{
|
||||
req, err := http.NewRequest("PUT", "/v1/agent/servers?address=127.0.0.1%3A4647&address=127.0.0.2%3A4647&address=127.0.0.3%3A4647", nil)
|
||||
assert.Nil(err)
|
||||
|
||||
respW := httptest.NewRecorder()
|
||||
setToken(req, s.RootToken)
|
||||
_, err = s.Server.AgentServersRequest(respW, req)
|
||||
assert.Nil(err)
|
||||
assert.Equal(http.StatusOK, respW.Code)
|
||||
}
|
||||
|
||||
// Create list request
|
||||
req, err := http.NewRequest("GET", "/v1/agent/servers", nil)
|
||||
assert.Nil(err)
|
||||
|
||||
expected := []string{
|
||||
"127.0.0.1:4647",
|
||||
"127.0.0.2:4647",
|
||||
"127.0.0.3:4647",
|
||||
}
|
||||
|
||||
// Try request without a token and expect failure
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
_, err := s.Server.AgentServersRequest(respW, req)
|
||||
assert.NotNil(err)
|
||||
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
}
|
||||
|
||||
// Try request with an invalid token and expect failure
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
token := mock.CreatePolicyAndToken(t, state, 1005, "invalid", mock.NodePolicy(acl.PolicyRead))
|
||||
setToken(req, token)
|
||||
_, err := s.Server.AgentServersRequest(respW, req)
|
||||
assert.NotNil(err)
|
||||
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
}
|
||||
|
||||
// Try request with a valid token
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
token := mock.CreatePolicyAndToken(t, state, 1007, "valid", mock.AgentPolicy(acl.PolicyRead))
|
||||
setToken(req, token)
|
||||
out, err := s.Server.AgentServersRequest(respW, req)
|
||||
assert.Nil(err)
|
||||
servers := out.([]string)
|
||||
assert.Len(servers, len(expected))
|
||||
assert.Equal(expected, servers)
|
||||
}
|
||||
|
||||
// Try request with a root token
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
setToken(req, s.RootToken)
|
||||
out, err := s.Server.AgentServersRequest(respW, req)
|
||||
assert.Nil(err)
|
||||
servers := out.([]string)
|
||||
assert.Len(servers, len(expected))
|
||||
assert.Equal(expected, servers)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestHTTP_AgentListKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -198,6 +482,66 @@ func TestHTTP_AgentListKeys(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestHTTP_AgentListKeys_ACL(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
|
||||
key1 := "HS5lJ+XuTlYKWaeGYyG+/A=="
|
||||
|
||||
cb := func(c *Config) {
|
||||
c.Server.EncryptKey = key1
|
||||
}
|
||||
|
||||
httpACLTest(t, cb, func(s *TestAgent) {
|
||||
state := s.Agent.server.State()
|
||||
|
||||
// Make the HTTP request
|
||||
req, err := http.NewRequest("GET", "/v1/agent/keyring/list", nil)
|
||||
assert.Nil(err)
|
||||
|
||||
// Try request without a token and expect failure
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
_, err := s.Server.KeyringOperationRequest(respW, req)
|
||||
assert.NotNil(err)
|
||||
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
}
|
||||
|
||||
// Try request with an invalid token and expect failure
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
token := mock.CreatePolicyAndToken(t, state, 1005, "invalid", mock.AgentPolicy(acl.PolicyRead))
|
||||
setToken(req, token)
|
||||
_, err := s.Server.KeyringOperationRequest(respW, req)
|
||||
assert.NotNil(err)
|
||||
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
}
|
||||
|
||||
// Try request with a valid token
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
token := mock.CreatePolicyAndToken(t, state, 1007, "valid", mock.AgentPolicy(acl.PolicyWrite))
|
||||
setToken(req, token)
|
||||
out, err := s.Server.KeyringOperationRequest(respW, req)
|
||||
assert.Nil(err)
|
||||
kresp := out.(structs.KeyringResponse)
|
||||
assert.Len(kresp.Keys, 1)
|
||||
assert.Contains(kresp.Keys, key1)
|
||||
}
|
||||
|
||||
// Try request with a root token
|
||||
{
|
||||
respW := httptest.NewRecorder()
|
||||
setToken(req, s.RootToken)
|
||||
out, err := s.Server.KeyringOperationRequest(respW, req)
|
||||
assert.Nil(err)
|
||||
kresp := out.(structs.KeyringResponse)
|
||||
assert.Len(kresp.Keys, 1)
|
||||
assert.Contains(kresp.Keys, key1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestHTTP_AgentInstallKey(t *testing.T) {
|
||||
// TODO(alexdadgar)
|
||||
// t.Parallel()
|
||||
|
|
|
@ -29,6 +29,11 @@ func NamespacePolicy(namespace string, policy string, capabilities []string) str
|
|||
return policyHCL
|
||||
}
|
||||
|
||||
// AgentPolicy is a helper for generating the hcl for a given agent policy.
|
||||
func AgentPolicy(policy string) string {
|
||||
return fmt.Sprintf("agent {\n\tpolicy = %q\n}\n", policy)
|
||||
}
|
||||
|
||||
// NodePolicy is a helper for generating the hcl for a given node policy.
|
||||
func NodePolicy(policy string) string {
|
||||
return fmt.Sprintf("node {\n\tpolicy = %q\n}\n", policy)
|
||||
|
|
|
@ -27,7 +27,7 @@ The table below shows this endpoint's support for
|
|||
|
||||
| Blocking Queries | ACL Required |
|
||||
| ---------------- | ------------ |
|
||||
| `NO` | `none` |
|
||||
| `NO` | `node:read` |
|
||||
|
||||
### Sample Request
|
||||
|
||||
|
@ -88,7 +88,7 @@ The table below shows this endpoint's support for
|
|||
|
||||
| Blocking Queries | ACL Required |
|
||||
| ---------------- | ------------ |
|
||||
| `NO` | `none` |
|
||||
| `NO` | `agent:read` |
|
||||
|
||||
### Sample Request
|
||||
|
||||
|
@ -118,9 +118,9 @@ The table below shows this endpoint's support for
|
|||
[blocking queries](/api/index.html#blocking-queries) and
|
||||
[required ACLs](/api/index.html#acls).
|
||||
|
||||
| Blocking Queries | ACL Required |
|
||||
| ---------------- | ------------ |
|
||||
| `NO` | `none` |
|
||||
| Blocking Queries | ACL Required |
|
||||
| ---------------- | ------------- |
|
||||
| `NO` | `agent:write` |
|
||||
|
||||
### Parameters
|
||||
|
||||
|
@ -147,9 +147,9 @@ The table below shows this endpoint's support for
|
|||
[blocking queries](/api/index.html#blocking-queries) and
|
||||
[required ACLs](/api/index.html#acls).
|
||||
|
||||
| Blocking Queries | Consistency Modes | ACL Required |
|
||||
| ---------------- | ----------------- | ------------ |
|
||||
| `NO` | `none` | `none` |
|
||||
| Blocking Queries | ACL Required |
|
||||
| ---------------- | ------------ |
|
||||
| `NO` | `agent:read` |
|
||||
|
||||
### Sample Request
|
||||
|
||||
|
@ -440,9 +440,9 @@ The table below shows this endpoint's support for
|
|||
[blocking queries](/api/index.html#blocking-queries) and
|
||||
[required ACLs](/api/index.html#acls).
|
||||
|
||||
| Blocking Queries | ACL Required |
|
||||
| ---------------- | ------------ |
|
||||
| `NO` | `none` |
|
||||
| Blocking Queries | ACL Required |
|
||||
| ---------------- | ------------- |
|
||||
| `NO` | `agent:write` |
|
||||
|
||||
### Parameters
|
||||
|
||||
|
|
Loading…
Reference in a new issue