mirror of
https://github.com/kemko/nomad.git
synced 2026-01-09 11:55:42 +03:00
Merge pull request #3328 from hashicorp/f-acl-client-fs
FS HTTP API ACL enforcement
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user