mirror of
https://github.com/kemko/nomad.git
synced 2026-01-06 10:25:42 +03:00
FS HTTP API ACL enforcement
ACL enforcement for the filesystem HTTP APIs on clients.
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,44 @@ 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)
|
||||
|
||||
requireReadFS := func(f func(http.ResponseWriter, *http.Request) (interface{}, error)) (interface{}, error) {
|
||||
if aclObj, err := s.agent.Client().ResolveToken(secret); err != nil {
|
||||
return nil, err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(namespace, acl.NamespaceCapabilityReadFS) {
|
||||
return nil, structs.ErrPermissionDenied
|
||||
}
|
||||
return f(resp, req)
|
||||
}
|
||||
|
||||
path := strings.TrimPrefix(req.URL.Path, "/v1/client/fs/")
|
||||
switch {
|
||||
case strings.HasPrefix(path, "ls/"):
|
||||
return s.DirectoryListRequest(resp, req)
|
||||
return requireReadFS(s.DirectoryListRequest)
|
||||
case strings.HasPrefix(path, "stat/"):
|
||||
return s.FileStatRequest(resp, req)
|
||||
return requireReadFS(s.FileStatRequest)
|
||||
case strings.HasPrefix(path, "readat/"):
|
||||
return s.FileReadAtRequest(resp, req)
|
||||
return requireReadFS(s.FileReadAtRequest)
|
||||
case strings.HasPrefix(path, "cat/"):
|
||||
return s.FileCatRequest(resp, req)
|
||||
return requireReadFS(s.FileCatRequest)
|
||||
case strings.HasPrefix(path, "stream/"):
|
||||
return s.Stream(resp, req)
|
||||
return requireReadFS(s.Stream)
|
||||
case strings.HasPrefix(path, "logs/"):
|
||||
// Logs can be accessed with ReadFS or ReadLogs caps
|
||||
if aclObj, err := s.agent.Client().ResolveToken(secret); err != nil {
|
||||
return nil, err
|
||||
} else 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,124 @@ func TestAllocDirFS_ReadAt_MissingParams(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAllocDirFS_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/ls/", 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
|
||||
|
||||
Reference in New Issue
Block a user