Merge pull request #3328 from hashicorp/f-acl-client-fs

FS HTTP API ACL enforcement
This commit is contained in:
Michael Schurter
2017-10-09 11:28:26 -07:00
committed by GitHub
3 changed files with 176 additions and 18 deletions

View File

@@ -20,6 +20,7 @@ import (
"gopkg.in/tomb.v1"
"github.com/docker/docker/pkg/ioutils"
"github.com/hashicorp/nomad/acl"
"github.com/hashicorp/nomad/client/allocdir"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hpcloud/tail/watch"
@@ -70,19 +71,53 @@ func (s *HTTPServer) FsRequest(resp http.ResponseWriter, req *http.Request) (int
return nil, clientNotRunning
}
var secret string
s.parseToken(req, &secret)
var namespace string
parseNamespace(req, &namespace)
aclObj, err := s.agent.Client().ResolveToken(secret)
if err != nil {
return nil, err
}
path := strings.TrimPrefix(req.URL.Path, "/v1/client/fs/")
switch {
case strings.HasPrefix(path, "ls/"):
if aclObj != nil && !aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadFS) {
return nil, structs.ErrPermissionDenied
}
return s.DirectoryListRequest(resp, req)
case strings.HasPrefix(path, "stat/"):
if aclObj != nil && !aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadFS) {
return nil, structs.ErrPermissionDenied
}
return s.FileStatRequest(resp, req)
case strings.HasPrefix(path, "readat/"):
if aclObj != nil && !aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadFS) {
return nil, structs.ErrPermissionDenied
}
return s.FileReadAtRequest(resp, req)
case strings.HasPrefix(path, "cat/"):
if aclObj != nil && !aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadFS) {
return nil, structs.ErrPermissionDenied
}
return s.FileCatRequest(resp, req)
case strings.HasPrefix(path, "stream/"):
if aclObj != nil && !aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadFS) {
return nil, structs.ErrPermissionDenied
}
return s.Stream(resp, req)
case strings.HasPrefix(path, "logs/"):
// Logs can be accessed with ReadFS or ReadLogs caps
if aclObj != nil {
readfs := aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadFS)
logs := aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadLogs)
if !readfs && !logs {
return nil, structs.ErrPermissionDenied
}
}
return s.Logs(resp, req)
default:
return nil, CodedError(404, ErrInvalidMethod)

View File

@@ -18,9 +18,12 @@ import (
"testing"
"time"
"github.com/hashicorp/nomad/acl"
"github.com/hashicorp/nomad/client/allocdir"
"github.com/hashicorp/nomad/nomad/mock"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/testutil"
"github.com/stretchr/testify/assert"
"github.com/ugorji/go/codec"
)
@@ -106,6 +109,126 @@ func TestAllocDirFS_ReadAt_MissingParams(t *testing.T) {
})
}
func TestAllocDirFS_ACL(t *testing.T) {
t.Parallel()
assert := assert.New(t)
for _, endpoint := range []string{"ls", "stat", "readat", "cat", "stream"} {
httpACLTest(t, nil, func(s *TestAgent) {
state := s.Agent.server.State()
req, err := http.NewRequest("GET", fmt.Sprintf("/v1/client/fs/%s/", endpoint), nil)
assert.Nil(err)
// Try request without a token and expect failure
{
respW := httptest.NewRecorder()
_, err := s.Server.FsRequest(respW, req)
assert.NotNil(err)
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
}
// Try request with an invalid token and expect failure
{
respW := httptest.NewRecorder()
policy := mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadLogs})
token := mock.CreatePolicyAndToken(t, state, 1005, "invalid", policy)
setToken(req, token)
_, err := s.Server.FsRequest(respW, req)
assert.NotNil(err)
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
}
// Try request with a valid token
// No alloc id set, so expect an error - just not a permissions error
{
respW := httptest.NewRecorder()
policy := mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadFS})
token := mock.CreatePolicyAndToken(t, state, 1007, "valid", policy)
setToken(req, token)
_, err := s.Server.FsRequest(respW, req)
assert.NotNil(err)
assert.Equal(allocIDNotPresentErr, err)
}
// Try request with a management token
// No alloc id set, so expect an error - just not a permissions error
{
respW := httptest.NewRecorder()
setToken(req, s.RootToken)
_, err := s.Server.FsRequest(respW, req)
assert.NotNil(err)
assert.Equal(allocIDNotPresentErr, err)
}
})
}
}
func TestAllocDirFS_Logs_ACL(t *testing.T) {
t.Parallel()
assert := assert.New(t)
httpACLTest(t, nil, func(s *TestAgent) {
state := s.Agent.server.State()
req, err := http.NewRequest("GET", "/v1/client/fs/logs/", nil)
assert.Nil(err)
// Try request without a token and expect failure
{
respW := httptest.NewRecorder()
_, err := s.Server.FsRequest(respW, req)
assert.NotNil(err)
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
}
// Try request with an invalid token and expect failure
{
respW := httptest.NewRecorder()
policy := mock.NamespacePolicy("other", "", []string{acl.NamespaceCapabilityReadFS})
token := mock.CreatePolicyAndToken(t, state, 1005, "invalid", policy)
setToken(req, token)
_, err := s.Server.FsRequest(respW, req)
assert.NotNil(err)
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
}
// Try request with a valid token (ReadFS)
// No alloc id set, so expect an error - just not a permissions error
{
respW := httptest.NewRecorder()
policy := mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadFS})
token := mock.CreatePolicyAndToken(t, state, 1007, "valid1", policy)
setToken(req, token)
_, err := s.Server.FsRequest(respW, req)
assert.NotNil(err)
assert.Equal(allocIDNotPresentErr, err)
}
// Try request with a valid token (ReadLogs)
// No alloc id set, so expect an error - just not a permissions error
{
respW := httptest.NewRecorder()
policy := mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadLogs})
token := mock.CreatePolicyAndToken(t, state, 1009, "valid2", policy)
setToken(req, token)
_, err := s.Server.FsRequest(respW, req)
assert.NotNil(err)
assert.Equal(allocIDNotPresentErr, err)
}
// Try request with a management token
// No alloc id set, so expect an error - just not a permissions error
{
respW := httptest.NewRecorder()
setToken(req, s.RootToken)
_, err := s.Server.FsRequest(respW, req)
assert.NotNil(err)
assert.Equal(allocIDNotPresentErr, err)
}
})
}
type WriteCloseChecker struct {
io.WriteCloser
Closed bool

View File

@@ -248,9 +248,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-fs` |
### Parameters
@@ -293,9 +293,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-fs` |
### Parameters
@@ -337,9 +337,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-fs` |
### Parameters
@@ -403,9 +403,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-logs` or `namespace:read-fs` |
### Parameters
@@ -476,9 +476,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-fs` |
### Parameters
@@ -529,9 +529,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-fs` |
### Parameters