Commit a4026229 authored by Michael Schurter's avatar Michael Schurter
Browse files

/v1/client/allocation/./{stats,gc} ACL enforcement

Showing with 139 additions and 3 deletions
+139 -3
......@@ -6,6 +6,7 @@ import (
"strings"
"github.com/golang/snappy"
"github.com/hashicorp/nomad/acl"
"github.com/hashicorp/nomad/nomad/structs"
)
......@@ -118,6 +119,18 @@ func (s *HTTPServer) ClientGCRequest(resp http.ResponseWriter, req *http.Request
}
func (s *HTTPServer) allocGC(allocID string, resp http.ResponseWriter, req *http.Request) (interface{}, error) {
var secret string
s.parseToken(req, &secret)
var namespace string
parseNamespace(req, &namespace)
// Check namespace submit-job permissions
if aclObj, err := s.agent.Client().ResolveToken(secret); err != nil {
return nil, err
} else if aclObj != nil && !aclObj.AllowNsOp(namespace, acl.NamespaceCapabilitySubmitJob) {
return nil, structs.ErrPermissionDenied
}
return nil, s.agent.Client().CollectAllocation(allocID)
}
......@@ -133,6 +146,19 @@ func (s *HTTPServer) allocSnapshot(allocID string, resp http.ResponseWriter, req
}
func (s *HTTPServer) allocStats(allocID string, resp http.ResponseWriter, req *http.Request) (interface{}, error) {
var secret string
s.parseToken(req, &secret)
var namespace string
parseNamespace(req, &namespace)
// Check namespace read-job permissions
if aclObj, err := s.agent.Client().ResolveToken(secret); err != nil {
return nil, err
} else if aclObj != nil && !aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadJob) {
return nil, structs.ErrPermissionDenied
}
clientStats := s.agent.client.StatsReporter()
aStats, err := clientStats.GetAllocStats(allocID)
if err != nil {
......
......@@ -243,6 +243,61 @@ func TestHTTP_AllocStats(t *testing.T) {
})
}
func TestHTTP_AllocStats_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/client/allocation/123/stats", nil)
if err != nil {
t.Fatalf("err: %v", err)
}
// Try request without a token and expect failure
{
respW := httptest.NewRecorder()
_, err := s.Server.ClientAllocRequest(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.ClientAllocRequest(respW, req)
assert.NotNil(err)
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
}
// Try request with a valid token
// Still returns an error because the alloc does not exist
{
respW := httptest.NewRecorder()
policy := mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})
token := mock.CreatePolicyAndToken(t, state, 1007, "valid", policy)
setToken(req, token)
_, err := s.Server.ClientAllocRequest(respW, req)
assert.NotNil(err)
assert.Contains(err.Error(), "unknown allocation ID")
}
// Try request with a management token
// Still returns an error because the alloc does not exist
{
respW := httptest.NewRecorder()
setToken(req, s.RootToken)
_, err := s.Server.ClientAllocRequest(respW, req)
assert.NotNil(err)
assert.Contains(err.Error(), "unknown allocation ID")
}
})
}
func TestHTTP_AllocSnapshot(t *testing.T) {
t.Parallel()
httpTest(t, nil, func(s *TestAgent) {
......@@ -279,6 +334,61 @@ func TestHTTP_AllocGC(t *testing.T) {
})
}
func TestHTTP_AllocGC_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/client/allocation/123/gc", nil)
if err != nil {
t.Fatalf("err: %v", err)
}
// Try request without a token and expect failure
{
respW := httptest.NewRecorder()
_, err := s.Server.ClientAllocRequest(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.ClientAllocRequest(respW, req)
assert.NotNil(err)
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
}
// Try request with a valid token
// Still returns an error because the alloc does not exist
{
respW := httptest.NewRecorder()
policy := mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilitySubmitJob})
token := mock.CreatePolicyAndToken(t, state, 1007, "valid", policy)
setToken(req, token)
_, err := s.Server.ClientAllocRequest(respW, req)
assert.NotNil(err)
assert.Contains(err.Error(), "not present")
}
// Try request with a management token
// Still returns an error because the alloc does not exist
{
respW := httptest.NewRecorder()
setToken(req, s.RootToken)
_, err := s.Server.ClientAllocRequest(respW, req)
assert.NotNil(err)
assert.Contains(err.Error(), "not present")
}
})
}
func TestHTTP_AllocAllGC(t *testing.T) {
t.Parallel()
httpTest(t, nil, func(s *TestAgent) {
......
......@@ -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 | ACL Required |
| ---------------- | ------------ |
| `NO` | `none` |
| Blocking Queries | ACL Required |
| ---------------- | -------------------- |
| `NO` | `namespace:read-job` |
### Parameters
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment