Merge pull request #3336 from hashicorp/f-acl-client-agent

/v1/client/agent/* ACL enforcement
This commit is contained in:
Michael Schurter 2017-10-09 12:20:26 -07:00 committed by GitHub
commit 1184df7d8f
4 changed files with 421 additions and 11 deletions

View file

@ -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

View file

@ -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()

View file

@ -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)

View file

@ -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