Merge pull request #6442 from hashicorp/b-alloc-namespace-check
acl: check ACL against object namespace
This commit is contained in:
commit
e8942312bb
22
acl/acl.go
22
acl/acl.go
|
@ -481,3 +481,25 @@ func (a *ACL) AllowQuotaWrite() bool {
|
|||
func (a *ACL) IsManagement() bool {
|
||||
return a.management
|
||||
}
|
||||
|
||||
// NamespaceValidator returns a func that wraps ACL.AllowNamespaceOperation in
|
||||
// a list of operations. Returns true (allowed) if acls are disabled or if
|
||||
// *any* capabilities match.
|
||||
func NamespaceValidator(ops ...string) func(*ACL, string) bool {
|
||||
return func(acl *ACL, ns string) bool {
|
||||
// Always allow if ACLs are disabled.
|
||||
if acl == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, op := range ops {
|
||||
if acl.AllowNamespaceOperation(ns, op) {
|
||||
// An operation is allowed, return true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// No operations are allowed by this ACL, return false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,10 +49,15 @@ func (a *Allocations) GarbageCollectAll(args *nstructs.NodeSpecificRequest, repl
|
|||
func (a *Allocations) GarbageCollect(args *nstructs.AllocSpecificRequest, reply *nstructs.GenericResponse) error {
|
||||
defer metrics.MeasureSince([]string{"client", "allocations", "garbage_collect"}, time.Now())
|
||||
|
||||
// Check submit job permissions
|
||||
alloc, err := a.c.GetAlloc(args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check namespace submit job permission.
|
||||
if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilitySubmitJob) {
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilitySubmitJob) {
|
||||
return nstructs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
|
@ -68,10 +73,15 @@ func (a *Allocations) GarbageCollect(args *nstructs.AllocSpecificRequest, reply
|
|||
func (a *Allocations) Signal(args *nstructs.AllocSignalRequest, reply *nstructs.GenericResponse) error {
|
||||
defer metrics.MeasureSince([]string{"client", "allocations", "signal"}, time.Now())
|
||||
|
||||
// Check alloc-lifecycle permissions
|
||||
alloc, err := a.c.GetAlloc(args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check namespace alloc-lifecycle permission.
|
||||
if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityAllocLifecycle) {
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocLifecycle) {
|
||||
return nstructs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
|
@ -82,9 +92,15 @@ func (a *Allocations) Signal(args *nstructs.AllocSignalRequest, reply *nstructs.
|
|||
func (a *Allocations) Restart(args *nstructs.AllocRestartRequest, reply *nstructs.GenericResponse) error {
|
||||
defer metrics.MeasureSince([]string{"client", "allocations", "restart"}, time.Now())
|
||||
|
||||
alloc, err := a.c.GetAlloc(args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check namespace alloc-lifecycle permission.
|
||||
if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityAllocLifecycle) {
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocLifecycle) {
|
||||
return nstructs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
|
@ -95,10 +111,15 @@ func (a *Allocations) Restart(args *nstructs.AllocRestartRequest, reply *nstruct
|
|||
func (a *Allocations) Stats(args *cstructs.AllocStatsRequest, reply *cstructs.AllocStatsResponse) error {
|
||||
defer metrics.MeasureSince([]string{"client", "allocations", "stats"}, time.Now())
|
||||
|
||||
// Check read job permissions
|
||||
alloc, err := a.c.GetAlloc(args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check read-job permission.
|
||||
if aclObj, err := a.c.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityReadJob) {
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadJob) {
|
||||
return nstructs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
|
@ -148,6 +169,20 @@ func (a *Allocations) execImpl(encoder *codec.Encoder, decoder *codec.Decoder, e
|
|||
return nil, structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
if req.AllocID == "" {
|
||||
return helper.Int64ToPtr(400), allocIDNotPresentErr
|
||||
}
|
||||
ar, err := a.c.getAllocRunner(req.AllocID)
|
||||
if err != nil {
|
||||
code := helper.Int64ToPtr(500)
|
||||
if structs.IsErrUnknownAllocation(err) {
|
||||
code = helper.Int64ToPtr(404)
|
||||
}
|
||||
|
||||
return code, err
|
||||
}
|
||||
alloc := ar.Alloc()
|
||||
|
||||
aclObj, token, err := a.c.resolveTokenAndACL(req.QueryOptions.AuthToken)
|
||||
{
|
||||
// log access
|
||||
|
@ -167,20 +202,14 @@ func (a *Allocations) execImpl(encoder *codec.Encoder, decoder *codec.Decoder, e
|
|||
)
|
||||
}
|
||||
|
||||
// Check read permissions
|
||||
// Check alloc-exec permission.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if aclObj != nil {
|
||||
exec := aclObj.AllowNsOp(req.QueryOptions.Namespace, acl.NamespaceCapabilityAllocExec)
|
||||
if !exec {
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocExec) {
|
||||
return nil, structs.ErrPermissionDenied
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the arguments
|
||||
if req.AllocID == "" {
|
||||
return helper.Int64ToPtr(400), allocIDNotPresentErr
|
||||
}
|
||||
if req.Task == "" {
|
||||
return helper.Int64ToPtr(400), taskNotPresentErr
|
||||
}
|
||||
|
@ -188,16 +217,6 @@ func (a *Allocations) execImpl(encoder *codec.Encoder, decoder *codec.Decoder, e
|
|||
return helper.Int64ToPtr(400), errors.New("command is not present")
|
||||
}
|
||||
|
||||
ar, err := a.c.getAllocRunner(req.AllocID)
|
||||
if err != nil {
|
||||
code := helper.Int64ToPtr(500)
|
||||
if structs.IsErrUnknownAllocation(err) {
|
||||
code = helper.Int64ToPtr(404)
|
||||
}
|
||||
|
||||
return code, err
|
||||
}
|
||||
|
||||
capabilities, err := ar.GetTaskDriverCapabilities(req.Task)
|
||||
if err != nil {
|
||||
code := helper.Int64ToPtr(500)
|
||||
|
@ -210,7 +229,7 @@ func (a *Allocations) execImpl(encoder *codec.Encoder, decoder *codec.Decoder, e
|
|||
|
||||
// check node access
|
||||
if aclObj != nil && capabilities.FSIsolation == drivers.FSIsolationNone {
|
||||
exec := aclObj.AllowNsOp(req.QueryOptions.Namespace, acl.NamespaceCapabilityAllocNodeExec)
|
||||
exec := aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocNodeExec)
|
||||
if !exec {
|
||||
return nil, structs.ErrPermissionDenied
|
||||
}
|
||||
|
|
|
@ -78,9 +78,19 @@ func TestAllocations_Restart_ACL(t *testing.T) {
|
|||
})
|
||||
defer cleanup()
|
||||
|
||||
job := mock.BatchJob()
|
||||
job.TaskGroups[0].Count = 1
|
||||
job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
|
||||
"run_for": "20s",
|
||||
}
|
||||
|
||||
// Wait for client to be running job
|
||||
alloc := testutil.WaitForRunningWithToken(t, server.RPC, job, root.SecretID)[0]
|
||||
|
||||
// Try request without a token and expect failure
|
||||
{
|
||||
req := &nstructs.AllocRestartRequest{}
|
||||
req.AllocID = alloc.ID
|
||||
var resp nstructs.GenericResponse
|
||||
err := client.ClientRPC("Allocations.Restart", &req, &resp)
|
||||
require.NotNil(err)
|
||||
|
@ -91,6 +101,7 @@ func TestAllocations_Restart_ACL(t *testing.T) {
|
|||
{
|
||||
token := mock.CreatePolicyAndToken(t, server.State(), 1005, "invalid", mock.NamespacePolicy(nstructs.DefaultNamespace, "", []string{}))
|
||||
req := &nstructs.AllocRestartRequest{}
|
||||
req.AllocID = alloc.ID
|
||||
req.AuthToken = token.SecretID
|
||||
|
||||
var resp nstructs.GenericResponse
|
||||
|
@ -106,20 +117,27 @@ func TestAllocations_Restart_ACL(t *testing.T) {
|
|||
token := mock.CreatePolicyAndToken(t, server.State(), 1007, "valid", policyHCL)
|
||||
require.NotNil(token)
|
||||
req := &nstructs.AllocRestartRequest{}
|
||||
req.AllocID = alloc.ID
|
||||
req.AuthToken = token.SecretID
|
||||
req.Namespace = nstructs.DefaultNamespace
|
||||
var resp nstructs.GenericResponse
|
||||
err := client.ClientRPC("Allocations.Restart", &req, &resp)
|
||||
require.True(nstructs.IsErrUnknownAllocation(err), "Expected unknown alloc, found: %v", err)
|
||||
require.NoError(err)
|
||||
//require.True(nstructs.IsErrUnknownAllocation(err), "Expected unknown alloc, found: %v", err)
|
||||
}
|
||||
|
||||
// Try request with a management token
|
||||
{
|
||||
req := &nstructs.AllocRestartRequest{}
|
||||
req.AllocID = alloc.ID
|
||||
req.AuthToken = root.SecretID
|
||||
var resp nstructs.GenericResponse
|
||||
err := client.ClientRPC("Allocations.Restart", &req, &resp)
|
||||
require.True(nstructs.IsErrUnknownAllocation(err), "Expected unknown alloc, found: %v", err)
|
||||
// Depending on how quickly the alloc restarts there may be no
|
||||
// error *or* a task not running error; either is fine.
|
||||
if err != nil {
|
||||
require.Contains(err.Error(), "Task not running", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,9 +257,19 @@ func TestAllocations_GarbageCollect_ACL(t *testing.T) {
|
|||
})
|
||||
defer cleanup()
|
||||
|
||||
job := mock.BatchJob()
|
||||
job.TaskGroups[0].Count = 1
|
||||
job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
|
||||
"run_for": "20s",
|
||||
}
|
||||
|
||||
// Wait for client to be running job
|
||||
alloc := testutil.WaitForRunningWithToken(t, server.RPC, job, root.SecretID)[0]
|
||||
|
||||
// Try request without a token and expect failure
|
||||
{
|
||||
req := &nstructs.AllocSpecificRequest{}
|
||||
req.AllocID = alloc.ID
|
||||
var resp nstructs.GenericResponse
|
||||
err := client.ClientRPC("Allocations.GarbageCollect", &req, &resp)
|
||||
require.NotNil(err)
|
||||
|
@ -252,6 +280,7 @@ func TestAllocations_GarbageCollect_ACL(t *testing.T) {
|
|||
{
|
||||
token := mock.CreatePolicyAndToken(t, server.State(), 1005, "invalid", mock.NodePolicy(acl.PolicyDeny))
|
||||
req := &nstructs.AllocSpecificRequest{}
|
||||
req.AllocID = alloc.ID
|
||||
req.AuthToken = token.SecretID
|
||||
|
||||
var resp nstructs.GenericResponse
|
||||
|
@ -266,6 +295,7 @@ func TestAllocations_GarbageCollect_ACL(t *testing.T) {
|
|||
token := mock.CreatePolicyAndToken(t, server.State(), 1005, "test-valid",
|
||||
mock.NamespacePolicy(nstructs.DefaultNamespace, "", []string{acl.NamespaceCapabilitySubmitJob}))
|
||||
req := &nstructs.AllocSpecificRequest{}
|
||||
req.AllocID = alloc.ID
|
||||
req.AuthToken = token.SecretID
|
||||
req.Namespace = nstructs.DefaultNamespace
|
||||
|
||||
|
@ -323,9 +353,19 @@ func TestAllocations_Signal_ACL(t *testing.T) {
|
|||
})
|
||||
defer cleanup()
|
||||
|
||||
job := mock.BatchJob()
|
||||
job.TaskGroups[0].Count = 1
|
||||
job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
|
||||
"run_for": "20s",
|
||||
}
|
||||
|
||||
// Wait for client to be running job
|
||||
alloc := testutil.WaitForRunningWithToken(t, server.RPC, job, root.SecretID)[0]
|
||||
|
||||
// Try request without a token and expect failure
|
||||
{
|
||||
req := &nstructs.AllocSignalRequest{}
|
||||
req.AllocID = alloc.ID
|
||||
var resp nstructs.GenericResponse
|
||||
err := client.ClientRPC("Allocations.Signal", &req, &resp)
|
||||
require.NotNil(err)
|
||||
|
@ -336,6 +376,7 @@ func TestAllocations_Signal_ACL(t *testing.T) {
|
|||
{
|
||||
token := mock.CreatePolicyAndToken(t, server.State(), 1005, "invalid", mock.NodePolicy(acl.PolicyDeny))
|
||||
req := &nstructs.AllocSignalRequest{}
|
||||
req.AllocID = alloc.ID
|
||||
req.AuthToken = token.SecretID
|
||||
|
||||
var resp nstructs.GenericResponse
|
||||
|
@ -350,22 +391,24 @@ func TestAllocations_Signal_ACL(t *testing.T) {
|
|||
token := mock.CreatePolicyAndToken(t, server.State(), 1005, "test-valid",
|
||||
mock.NamespacePolicy(nstructs.DefaultNamespace, "", []string{acl.NamespaceCapabilityAllocLifecycle}))
|
||||
req := &nstructs.AllocSignalRequest{}
|
||||
req.AllocID = alloc.ID
|
||||
req.AuthToken = token.SecretID
|
||||
req.Namespace = nstructs.DefaultNamespace
|
||||
|
||||
var resp nstructs.GenericResponse
|
||||
err := client.ClientRPC("Allocations.Signal", &req, &resp)
|
||||
require.True(nstructs.IsErrUnknownAllocation(err))
|
||||
require.NoError(err)
|
||||
}
|
||||
|
||||
// Try request with a management token
|
||||
{
|
||||
req := &nstructs.AllocSignalRequest{}
|
||||
req.AllocID = alloc.ID
|
||||
req.AuthToken = root.SecretID
|
||||
|
||||
var resp nstructs.GenericResponse
|
||||
err := client.ClientRPC("Allocations.Signal", &req, &resp)
|
||||
require.True(nstructs.IsErrUnknownAllocation(err))
|
||||
require.NoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -414,9 +457,19 @@ func TestAllocations_Stats_ACL(t *testing.T) {
|
|||
})
|
||||
defer cleanup()
|
||||
|
||||
job := mock.BatchJob()
|
||||
job.TaskGroups[0].Count = 1
|
||||
job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
|
||||
"run_for": "20s",
|
||||
}
|
||||
|
||||
// Wait for client to be running job
|
||||
alloc := testutil.WaitForRunningWithToken(t, server.RPC, job, root.SecretID)[0]
|
||||
|
||||
// Try request without a token and expect failure
|
||||
{
|
||||
req := &cstructs.AllocStatsRequest{}
|
||||
req.AllocID = alloc.ID
|
||||
var resp cstructs.AllocStatsResponse
|
||||
err := client.ClientRPC("Allocations.Stats", &req, &resp)
|
||||
require.NotNil(err)
|
||||
|
@ -427,6 +480,7 @@ func TestAllocations_Stats_ACL(t *testing.T) {
|
|||
{
|
||||
token := mock.CreatePolicyAndToken(t, server.State(), 1005, "invalid", mock.NodePolicy(acl.PolicyDeny))
|
||||
req := &cstructs.AllocStatsRequest{}
|
||||
req.AllocID = alloc.ID
|
||||
req.AuthToken = token.SecretID
|
||||
|
||||
var resp cstructs.AllocStatsResponse
|
||||
|
@ -441,22 +495,24 @@ func TestAllocations_Stats_ACL(t *testing.T) {
|
|||
token := mock.CreatePolicyAndToken(t, server.State(), 1005, "test-valid",
|
||||
mock.NamespacePolicy(nstructs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
|
||||
req := &cstructs.AllocStatsRequest{}
|
||||
req.AllocID = alloc.ID
|
||||
req.AuthToken = token.SecretID
|
||||
req.Namespace = nstructs.DefaultNamespace
|
||||
|
||||
var resp cstructs.AllocStatsResponse
|
||||
err := client.ClientRPC("Allocations.Stats", &req, &resp)
|
||||
require.True(nstructs.IsErrUnknownAllocation(err))
|
||||
require.NoError(err)
|
||||
}
|
||||
|
||||
// Try request with a management token
|
||||
{
|
||||
req := &cstructs.AllocStatsRequest{}
|
||||
req.AllocID = alloc.ID
|
||||
req.AuthToken = root.SecretID
|
||||
|
||||
var resp cstructs.AllocStatsResponse
|
||||
err := client.ClientRPC("Allocations.Stats", &req, &resp)
|
||||
require.True(nstructs.IsErrUnknownAllocation(err))
|
||||
require.NoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -677,7 +733,6 @@ func TestAlloc_ExecStreaming_DisableRemoteExec(t *testing.T) {
|
|||
|
||||
func TestAlloc_ExecStreaming_ACL_Basic(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
// Start a server and client
|
||||
s, root := nomad.TestACLServer(t, nil)
|
||||
|
@ -698,6 +753,15 @@ func TestAlloc_ExecStreaming_ACL_Basic(t *testing.T) {
|
|||
[]string{acl.NamespaceCapabilityAllocExec, acl.NamespaceCapabilityReadFS})
|
||||
tokenGood := mock.CreatePolicyAndToken(t, s.State(), 1009, "valid2", policyGood)
|
||||
|
||||
job := mock.BatchJob()
|
||||
job.TaskGroups[0].Count = 1
|
||||
job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
|
||||
"run_for": "20s",
|
||||
}
|
||||
|
||||
// Wait for client to be running job
|
||||
alloc := testutil.WaitForRunningWithToken(t, s.RPC, job, root.SecretID)[0]
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
Token string
|
||||
|
@ -711,12 +775,12 @@ func TestAlloc_ExecStreaming_ACL_Basic(t *testing.T) {
|
|||
{
|
||||
Name: "good token",
|
||||
Token: tokenGood.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
ExpectedError: "task not found",
|
||||
},
|
||||
{
|
||||
Name: "root token",
|
||||
Token: root.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
ExpectedError: "task not found",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -725,7 +789,7 @@ func TestAlloc_ExecStreaming_ACL_Basic(t *testing.T) {
|
|||
|
||||
// Make the request
|
||||
req := &cstructs.AllocExecRequest{
|
||||
AllocID: uuid.Generate(),
|
||||
AllocID: alloc.ID,
|
||||
Task: "testtask",
|
||||
Tty: true,
|
||||
Cmd: []string{"placeholder command"},
|
||||
|
@ -738,7 +802,7 @@ func TestAlloc_ExecStreaming_ACL_Basic(t *testing.T) {
|
|||
|
||||
// Get the handler
|
||||
handler, err := client.StreamingRpcHandler("Allocations.Exec")
|
||||
require.Nil(err)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Create a pipe
|
||||
p1, p2 := net.Pipe()
|
||||
|
@ -754,15 +818,15 @@ func TestAlloc_ExecStreaming_ACL_Basic(t *testing.T) {
|
|||
|
||||
// Send the request
|
||||
encoder := codec.NewEncoder(p1, nstructs.MsgpackHandle)
|
||||
require.Nil(encoder.Encode(req))
|
||||
require.Nil(t, encoder.Encode(req))
|
||||
|
||||
select {
|
||||
case <-time.After(3 * time.Second):
|
||||
require.FailNow("timed out")
|
||||
require.FailNow(t, "timed out")
|
||||
case err := <-errCh:
|
||||
require.Contains(err.Error(), c.ExpectedError)
|
||||
require.Contains(t, err.Error(), c.ExpectedError)
|
||||
case f := <-frames:
|
||||
require.Fail("received unexpected frame", "frame: %#v", f)
|
||||
require.Fail(t, "received unexpected frame", "frame: %#v", f)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -731,6 +731,16 @@ func (c *Client) Stats() map[string]map[string]string {
|
|||
return stats
|
||||
}
|
||||
|
||||
// GetAlloc returns an allocation or an error.
|
||||
func (c *Client) GetAlloc(allocID string) (*structs.Allocation, error) {
|
||||
ar, err := c.getAllocRunner(allocID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ar.Alloc(), nil
|
||||
}
|
||||
|
||||
// SignalAllocation sends a signal to the tasks within an allocation.
|
||||
// If the provided task is empty, then every allocation will be signalled.
|
||||
// If a task is provided, then only an exactly matching task will be signalled.
|
||||
|
@ -778,6 +788,8 @@ func (c *Client) Node() *structs.Node {
|
|||
return c.configCopy.Node
|
||||
}
|
||||
|
||||
// getAllocRunner returns an AllocRunner or an UnknownAllocation error if the
|
||||
// client has no runner for the given alloc ID.
|
||||
func (c *Client) getAllocRunner(allocID string) (AllocRunner, error) {
|
||||
c.allocLock.RLock()
|
||||
defer c.allocLock.RUnlock()
|
||||
|
@ -882,7 +894,6 @@ func (c *Client) GetAllocFS(allocID string) (allocdir.AllocDirFS, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ar.GetAllocDir(), nil
|
||||
}
|
||||
|
||||
|
@ -1878,6 +1889,7 @@ func (c *Client) watchAllocations(updates chan *allocUpdates) {
|
|||
QueryOptions: structs.QueryOptions{
|
||||
Region: c.Region(),
|
||||
AllowStale: true,
|
||||
AuthToken: c.secretNodeID(),
|
||||
},
|
||||
}
|
||||
var allocsResp structs.AllocsGetResponse
|
||||
|
|
|
@ -99,10 +99,15 @@ func handleStreamResultError(err error, code *int64, encoder *codec.Encoder) {
|
|||
func (f *FileSystem) List(args *cstructs.FsListRequest, reply *cstructs.FsListResponse) error {
|
||||
defer metrics.MeasureSince([]string{"client", "file_system", "list"}, time.Now())
|
||||
|
||||
// Check read permissions
|
||||
alloc, err := f.c.GetAlloc(args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check namespace read-fs permission.
|
||||
if aclObj, err := f.c.ResolveToken(args.QueryOptions.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityReadFS) {
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadFS) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
|
@ -123,10 +128,15 @@ func (f *FileSystem) List(args *cstructs.FsListRequest, reply *cstructs.FsListRe
|
|||
func (f *FileSystem) Stat(args *cstructs.FsStatRequest, reply *cstructs.FsStatResponse) error {
|
||||
defer metrics.MeasureSince([]string{"client", "file_system", "stat"}, time.Now())
|
||||
|
||||
// Check read permissions
|
||||
alloc, err := f.c.GetAlloc(args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check namespace read-fs permission.
|
||||
if aclObj, err := f.c.ResolveToken(args.QueryOptions.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityReadFS) {
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadFS) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
|
@ -159,20 +169,26 @@ func (f *FileSystem) stream(conn io.ReadWriteCloser) {
|
|||
return
|
||||
}
|
||||
|
||||
if req.AllocID == "" {
|
||||
handleStreamResultError(allocIDNotPresentErr, helper.Int64ToPtr(400), encoder)
|
||||
return
|
||||
}
|
||||
alloc, err := f.c.GetAlloc(req.AllocID)
|
||||
if err != nil {
|
||||
handleStreamResultError(structs.NewErrUnknownAllocation(req.AllocID), helper.Int64ToPtr(404), encoder)
|
||||
return
|
||||
}
|
||||
|
||||
// Check read permissions
|
||||
if aclObj, err := f.c.ResolveToken(req.QueryOptions.AuthToken); err != nil {
|
||||
handleStreamResultError(err, helper.Int64ToPtr(403), encoder)
|
||||
return
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(req.Namespace, acl.NamespaceCapabilityReadFS) {
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadFS) {
|
||||
handleStreamResultError(structs.ErrPermissionDenied, helper.Int64ToPtr(403), encoder)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate the arguments
|
||||
if req.AllocID == "" {
|
||||
handleStreamResultError(allocIDNotPresentErr, helper.Int64ToPtr(400), encoder)
|
||||
return
|
||||
}
|
||||
if req.Path == "" {
|
||||
handleStreamResultError(pathNotPresentErr, helper.Int64ToPtr(400), encoder)
|
||||
return
|
||||
|
@ -332,13 +348,23 @@ func (f *FileSystem) logs(conn io.ReadWriteCloser) {
|
|||
return
|
||||
}
|
||||
|
||||
if req.AllocID == "" {
|
||||
handleStreamResultError(allocIDNotPresentErr, helper.Int64ToPtr(400), encoder)
|
||||
return
|
||||
}
|
||||
alloc, err := f.c.GetAlloc(req.AllocID)
|
||||
if err != nil {
|
||||
handleStreamResultError(structs.NewErrUnknownAllocation(req.AllocID), helper.Int64ToPtr(404), encoder)
|
||||
return
|
||||
}
|
||||
|
||||
// Check read permissions
|
||||
if aclObj, err := f.c.ResolveToken(req.QueryOptions.AuthToken); err != nil {
|
||||
handleStreamResultError(err, nil, encoder)
|
||||
return
|
||||
} else if aclObj != nil {
|
||||
readfs := aclObj.AllowNsOp(req.QueryOptions.Namespace, acl.NamespaceCapabilityReadFS)
|
||||
logs := aclObj.AllowNsOp(req.QueryOptions.Namespace, acl.NamespaceCapabilityReadLogs)
|
||||
readfs := aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadFS)
|
||||
logs := aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadLogs)
|
||||
if !readfs && !logs {
|
||||
handleStreamResultError(structs.ErrPermissionDenied, nil, encoder)
|
||||
return
|
||||
|
@ -346,10 +372,6 @@ func (f *FileSystem) logs(conn io.ReadWriteCloser) {
|
|||
}
|
||||
|
||||
// Validate the arguments
|
||||
if req.AllocID == "" {
|
||||
handleStreamResultError(allocIDNotPresentErr, helper.Int64ToPtr(400), encoder)
|
||||
return
|
||||
}
|
||||
if req.Task == "" {
|
||||
handleStreamResultError(taskNotPresentErr, helper.Int64ToPtr(400), encoder)
|
||||
return
|
||||
|
|
|
@ -114,7 +114,6 @@ func TestFS_Stat(t *testing.T) {
|
|||
|
||||
func TestFS_Stat_ACL(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
// Start a server
|
||||
s, root := nomad.TestACLServer(t, nil)
|
||||
|
@ -135,6 +134,15 @@ func TestFS_Stat_ACL(t *testing.T) {
|
|||
[]string{acl.NamespaceCapabilityReadLogs, acl.NamespaceCapabilityReadFS})
|
||||
tokenGood := mock.CreatePolicyAndToken(t, s.State(), 1009, "valid2", policyGood)
|
||||
|
||||
job := mock.BatchJob()
|
||||
job.TaskGroups[0].Count = 1
|
||||
job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
|
||||
"run_for": "20s",
|
||||
}
|
||||
|
||||
// Wait for client to be running job
|
||||
alloc := testutil.WaitForRunningWithToken(t, s.RPC, job, root.SecretID)[0]
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
Token string
|
||||
|
@ -148,20 +156,17 @@ func TestFS_Stat_ACL(t *testing.T) {
|
|||
{
|
||||
Name: "good token",
|
||||
Token: tokenGood.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
},
|
||||
{
|
||||
Name: "root token",
|
||||
Token: root.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
// Make the request with bad allocation id
|
||||
req := &cstructs.FsStatRequest{
|
||||
AllocID: uuid.Generate(),
|
||||
AllocID: alloc.ID,
|
||||
Path: "/",
|
||||
QueryOptions: structs.QueryOptions{
|
||||
Region: "global",
|
||||
|
@ -172,8 +177,12 @@ func TestFS_Stat_ACL(t *testing.T) {
|
|||
|
||||
var resp cstructs.FsStatResponse
|
||||
err := client.ClientRPC("FileSystem.Stat", req, &resp)
|
||||
require.NotNil(err)
|
||||
require.Contains(err.Error(), c.ExpectedError)
|
||||
if c.ExpectedError == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.NotNil(t, err)
|
||||
require.Contains(t, err.Error(), c.ExpectedError)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -238,7 +247,6 @@ func TestFS_List(t *testing.T) {
|
|||
|
||||
func TestFS_List_ACL(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
// Start a server
|
||||
s, root := nomad.TestACLServer(t, nil)
|
||||
|
@ -259,6 +267,15 @@ func TestFS_List_ACL(t *testing.T) {
|
|||
[]string{acl.NamespaceCapabilityReadLogs, acl.NamespaceCapabilityReadFS})
|
||||
tokenGood := mock.CreatePolicyAndToken(t, s.State(), 1009, "valid2", policyGood)
|
||||
|
||||
job := mock.BatchJob()
|
||||
job.TaskGroups[0].Count = 1
|
||||
job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
|
||||
"run_for": "20s",
|
||||
}
|
||||
|
||||
// Wait for client to be running job
|
||||
alloc := testutil.WaitForRunningWithToken(t, s.RPC, job, root.SecretID)[0]
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
Token string
|
||||
|
@ -272,12 +289,10 @@ func TestFS_List_ACL(t *testing.T) {
|
|||
{
|
||||
Name: "good token",
|
||||
Token: tokenGood.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
},
|
||||
{
|
||||
Name: "root token",
|
||||
Token: root.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -285,7 +300,7 @@ func TestFS_List_ACL(t *testing.T) {
|
|||
t.Run(c.Name, func(t *testing.T) {
|
||||
// Make the request with bad allocation id
|
||||
req := &cstructs.FsListRequest{
|
||||
AllocID: uuid.Generate(),
|
||||
AllocID: alloc.ID,
|
||||
Path: "/",
|
||||
QueryOptions: structs.QueryOptions{
|
||||
Region: "global",
|
||||
|
@ -296,8 +311,11 @@ func TestFS_List_ACL(t *testing.T) {
|
|||
|
||||
var resp cstructs.FsListResponse
|
||||
err := client.ClientRPC("FileSystem.List", req, &resp)
|
||||
require.NotNil(err)
|
||||
require.Contains(err.Error(), c.ExpectedError)
|
||||
if c.ExpectedError == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.EqualError(t, err, c.ExpectedError)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -379,7 +397,6 @@ OUTER:
|
|||
|
||||
func TestFS_Stream_ACL(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
// Start a server
|
||||
s, root := nomad.TestACLServer(t, nil)
|
||||
|
@ -400,6 +417,15 @@ func TestFS_Stream_ACL(t *testing.T) {
|
|||
[]string{acl.NamespaceCapabilityReadLogs, acl.NamespaceCapabilityReadFS})
|
||||
tokenGood := mock.CreatePolicyAndToken(t, s.State(), 1009, "valid2", policyGood)
|
||||
|
||||
job := mock.BatchJob()
|
||||
job.TaskGroups[0].Count = 1
|
||||
job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
|
||||
"run_for": "20s",
|
||||
}
|
||||
|
||||
// Wait for client to be running job
|
||||
alloc := testutil.WaitForRunningWithToken(t, s.RPC, job, root.SecretID)[0]
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
Token string
|
||||
|
@ -413,12 +439,10 @@ func TestFS_Stream_ACL(t *testing.T) {
|
|||
{
|
||||
Name: "good token",
|
||||
Token: tokenGood.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
},
|
||||
{
|
||||
Name: "root token",
|
||||
Token: root.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -426,7 +450,7 @@ func TestFS_Stream_ACL(t *testing.T) {
|
|||
t.Run(c.Name, func(t *testing.T) {
|
||||
// Make the request with bad allocation id
|
||||
req := &cstructs.FsStreamRequest{
|
||||
AllocID: uuid.Generate(),
|
||||
AllocID: alloc.ID,
|
||||
Path: "foo",
|
||||
Origin: "start",
|
||||
QueryOptions: structs.QueryOptions{
|
||||
|
@ -438,7 +462,7 @@ func TestFS_Stream_ACL(t *testing.T) {
|
|||
|
||||
// Get the handler
|
||||
handler, err := client.StreamingRpcHandler("FileSystem.Stream")
|
||||
require.Nil(err)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Create a pipe
|
||||
p1, p2 := net.Pipe()
|
||||
|
@ -457,11 +481,9 @@ func TestFS_Stream_ACL(t *testing.T) {
|
|||
for {
|
||||
var msg cstructs.StreamErrWrapper
|
||||
if err := decoder.Decode(&msg); err != nil {
|
||||
if err == io.EOF || strings.Contains(err.Error(), "closed") {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
errCh <- fmt.Errorf("error decoding: %v", err)
|
||||
}
|
||||
|
||||
streamMsg <- &msg
|
||||
}
|
||||
|
@ -469,7 +491,7 @@ func TestFS_Stream_ACL(t *testing.T) {
|
|||
|
||||
// Send the request
|
||||
encoder := codec.NewEncoder(p1, structs.MsgpackHandle)
|
||||
require.Nil(encoder.Encode(req))
|
||||
require.NoError(t, encoder.Encode(req))
|
||||
|
||||
timeout := time.After(5 * time.Second)
|
||||
|
||||
|
@ -479,6 +501,11 @@ func TestFS_Stream_ACL(t *testing.T) {
|
|||
case <-timeout:
|
||||
t.Fatal("timeout")
|
||||
case err := <-errCh:
|
||||
eof := err == io.EOF || strings.Contains(err.Error(), "closed")
|
||||
if c.ExpectedError == "" && eof {
|
||||
// No error was expected!
|
||||
return
|
||||
}
|
||||
t.Fatal(err)
|
||||
case msg := <-streamMsg:
|
||||
if msg.Error == nil {
|
||||
|
@ -1019,6 +1046,15 @@ func TestFS_Logs_ACL(t *testing.T) {
|
|||
[]string{acl.NamespaceCapabilityReadLogs, acl.NamespaceCapabilityReadFS})
|
||||
tokenGood := mock.CreatePolicyAndToken(t, s.State(), 1009, "valid2", policyGood)
|
||||
|
||||
job := mock.BatchJob()
|
||||
job.TaskGroups[0].Count = 1
|
||||
job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
|
||||
"run_for": "20s",
|
||||
}
|
||||
|
||||
// Wait for client to be running job
|
||||
alloc := testutil.WaitForRunningWithToken(t, s.RPC, job, root.SecretID)[0]
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
Token string
|
||||
|
@ -1032,12 +1068,10 @@ func TestFS_Logs_ACL(t *testing.T) {
|
|||
{
|
||||
Name: "good token",
|
||||
Token: tokenGood.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
},
|
||||
{
|
||||
Name: "root token",
|
||||
Token: root.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1045,8 +1079,8 @@ func TestFS_Logs_ACL(t *testing.T) {
|
|||
t.Run(c.Name, func(t *testing.T) {
|
||||
// Make the request with bad allocation id
|
||||
req := &cstructs.FsLogsRequest{
|
||||
AllocID: uuid.Generate(),
|
||||
Task: "foo",
|
||||
AllocID: alloc.ID,
|
||||
Task: job.TaskGroups[0].Tasks[0].Name,
|
||||
LogType: "stdout",
|
||||
Origin: "start",
|
||||
QueryOptions: structs.QueryOptions{
|
||||
|
@ -1077,11 +1111,9 @@ func TestFS_Logs_ACL(t *testing.T) {
|
|||
for {
|
||||
var msg cstructs.StreamErrWrapper
|
||||
if err := decoder.Decode(&msg); err != nil {
|
||||
if err == io.EOF || strings.Contains(err.Error(), "closed") {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
errCh <- fmt.Errorf("error decoding: %v", err)
|
||||
}
|
||||
|
||||
streamMsg <- &msg
|
||||
}
|
||||
|
@ -1099,6 +1131,11 @@ func TestFS_Logs_ACL(t *testing.T) {
|
|||
case <-timeout:
|
||||
t.Fatal("timeout")
|
||||
case err := <-errCh:
|
||||
eof := err == io.EOF || strings.Contains(err.Error(), "closed")
|
||||
if c.ExpectedError == "" && eof {
|
||||
// No error was expected!
|
||||
return
|
||||
}
|
||||
t.Fatal(err)
|
||||
case msg := <-streamMsg:
|
||||
if msg.Error == nil {
|
||||
|
@ -1106,6 +1143,7 @@ func TestFS_Logs_ACL(t *testing.T) {
|
|||
}
|
||||
|
||||
if strings.Contains(msg.Error.Error(), c.ExpectedError) {
|
||||
// Ok! Error matched expectation.
|
||||
break OUTER
|
||||
} else {
|
||||
t.Fatalf("Bad error: %v", msg.Error)
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
package testutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cstructs "github.com/hashicorp/nomad/client/structs"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ugorji/go/codec"
|
||||
)
|
||||
|
||||
// StreamingRPC may be satisfied by client.Client or server.Server.
|
||||
type StreamingRPC interface {
|
||||
StreamingRpcHandler(method string) (structs.StreamingRpcHandler, error)
|
||||
}
|
||||
|
||||
// StreamingRPCErrorTestCase is a test case to be passed to the
|
||||
// assertStreamingRPCError func.
|
||||
type StreamingRPCErrorTestCase struct {
|
||||
Name string
|
||||
RPC string
|
||||
Req interface{}
|
||||
Assert func(error) bool
|
||||
}
|
||||
|
||||
// AssertStreamingRPCError asserts a streaming RPC's error matches the given
|
||||
// assertion in the test case.
|
||||
func AssertStreamingRPCError(t *testing.T, s StreamingRPC, tc StreamingRPCErrorTestCase) {
|
||||
handler, err := s.StreamingRpcHandler(tc.RPC)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a pipe
|
||||
p1, p2 := net.Pipe()
|
||||
defer p1.Close()
|
||||
defer p2.Close()
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
streamMsg := make(chan *cstructs.StreamErrWrapper, 1)
|
||||
|
||||
// Start the handler
|
||||
go handler(p2)
|
||||
|
||||
// Start the decoder
|
||||
go func() {
|
||||
decoder := codec.NewDecoder(p1, structs.MsgpackHandle)
|
||||
for {
|
||||
var msg cstructs.StreamErrWrapper
|
||||
if err := decoder.Decode(&msg); err != nil {
|
||||
if err == io.EOF || strings.Contains(err.Error(), "closed") {
|
||||
return
|
||||
}
|
||||
errCh <- fmt.Errorf("error decoding: %v", err)
|
||||
}
|
||||
|
||||
streamMsg <- &msg
|
||||
}
|
||||
}()
|
||||
|
||||
// Send the request
|
||||
encoder := codec.NewEncoder(p1, structs.MsgpackHandle)
|
||||
require.NoError(t, encoder.Encode(tc.Req))
|
||||
|
||||
timeout := time.After(5 * time.Second)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Fatal("timeout")
|
||||
case err := <-errCh:
|
||||
require.NoError(t, err)
|
||||
case msg := <-streamMsg:
|
||||
// Convert RpcError to error
|
||||
var err error
|
||||
if msg.Error != nil {
|
||||
err = msg.Error
|
||||
}
|
||||
require.True(t, tc.Assert(err), "(%T) %s", msg.Error, msg.Error)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
|
@ -346,7 +346,7 @@ func TestHTTP_AllocRestart_ACL(t *testing.T) {
|
|||
respW := httptest.NewRecorder()
|
||||
_, err = s.Server.ClientAllocRequest(respW, req)
|
||||
require.NotNil(err)
|
||||
require.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
require.True(structs.IsErrUnknownAllocation(err), "(%T) %v", err, err)
|
||||
}
|
||||
|
||||
// Try request with an invalid token and expect it to fail
|
||||
|
@ -360,7 +360,7 @@ func TestHTTP_AllocRestart_ACL(t *testing.T) {
|
|||
setToken(req, token)
|
||||
_, err = s.Server.ClientAllocRequest(respW, req)
|
||||
require.NotNil(err)
|
||||
require.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
require.True(structs.IsErrUnknownAllocation(err), "(%T) %v", err, err)
|
||||
}
|
||||
|
||||
// Try request with a valid token
|
||||
|
@ -376,7 +376,7 @@ func TestHTTP_AllocRestart_ACL(t *testing.T) {
|
|||
setToken(req, token)
|
||||
_, err = s.Server.ClientAllocRequest(respW, req)
|
||||
require.NotNil(err)
|
||||
require.True(structs.IsErrUnknownAllocation(err))
|
||||
require.True(structs.IsErrUnknownAllocation(err), "(%T) %v", err, err)
|
||||
}
|
||||
|
||||
// Try request with a management token
|
||||
|
@ -523,7 +523,7 @@ func TestHTTP_AllocStats_ACL(t *testing.T) {
|
|||
respW := httptest.NewRecorder()
|
||||
_, err := s.Server.ClientAllocRequest(respW, req)
|
||||
require.NotNil(err)
|
||||
require.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
require.True(structs.IsErrUnknownAllocation(err), "(%T) %v", err, err)
|
||||
}
|
||||
|
||||
// Try request with an invalid token and expect failure
|
||||
|
@ -533,7 +533,7 @@ func TestHTTP_AllocStats_ACL(t *testing.T) {
|
|||
setToken(req, token)
|
||||
_, err := s.Server.ClientAllocRequest(respW, req)
|
||||
require.NotNil(err)
|
||||
require.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
require.True(structs.IsErrUnknownAllocation(err), "(%T) %v", err, err)
|
||||
}
|
||||
|
||||
// Try request with a valid token
|
||||
|
@ -545,7 +545,7 @@ func TestHTTP_AllocStats_ACL(t *testing.T) {
|
|||
setToken(req, token)
|
||||
_, err := s.Server.ClientAllocRequest(respW, req)
|
||||
require.NotNil(err)
|
||||
require.True(structs.IsErrUnknownAllocation(err))
|
||||
require.True(structs.IsErrUnknownAllocation(err), "(%T) %v", err, err)
|
||||
}
|
||||
|
||||
// Try request with a management token
|
||||
|
@ -812,7 +812,7 @@ func TestHTTP_AllocGC_ACL(t *testing.T) {
|
|||
respW := httptest.NewRecorder()
|
||||
_, err := s.Server.ClientAllocRequest(respW, req)
|
||||
require.NotNil(err)
|
||||
require.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
require.True(structs.IsErrUnknownAllocation(err), "(%T) %v", err, err)
|
||||
}
|
||||
|
||||
// Try request with an invalid token and expect failure
|
||||
|
@ -822,7 +822,7 @@ func TestHTTP_AllocGC_ACL(t *testing.T) {
|
|||
setToken(req, token)
|
||||
_, err := s.Server.ClientAllocRequest(respW, req)
|
||||
require.NotNil(err)
|
||||
require.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
require.True(structs.IsErrUnknownAllocation(err), "(%T) %v", err, err)
|
||||
}
|
||||
|
||||
// Try request with a valid token
|
||||
|
@ -834,7 +834,7 @@ func TestHTTP_AllocGC_ACL(t *testing.T) {
|
|||
setToken(req, token)
|
||||
_, err := s.Server.ClientAllocRequest(respW, req)
|
||||
require.NotNil(err)
|
||||
require.True(structs.IsErrUnknownAllocation(err))
|
||||
require.True(structs.IsErrUnknownAllocation(err), "(%T) %v", err, err)
|
||||
}
|
||||
|
||||
// Try request with a management token
|
||||
|
|
|
@ -86,8 +86,10 @@ func (a *Alloc) GetAlloc(args *structs.AllocSpecificRequest,
|
|||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "alloc", "get_alloc"}, time.Now())
|
||||
|
||||
// Check namespace read-job permissions
|
||||
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
// Check namespace read-job permissions before performing blocking query.
|
||||
allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob)
|
||||
aclObj, err := a.srv.ResolveToken(args.AuthToken)
|
||||
if err != nil {
|
||||
// If ResolveToken had an unexpected error return that
|
||||
if err != structs.ErrTokenNotFound {
|
||||
return err
|
||||
|
@ -107,8 +109,6 @@ func (a *Alloc) GetAlloc(args *structs.AllocSpecificRequest,
|
|||
if node == nil {
|
||||
return structs.ErrTokenNotFound
|
||||
}
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Setup the blocking query
|
||||
|
@ -125,6 +125,11 @@ func (a *Alloc) GetAlloc(args *structs.AllocSpecificRequest,
|
|||
// Setup the output
|
||||
reply.Alloc = out
|
||||
if out != nil {
|
||||
// Re-check namespace in case it differs from request.
|
||||
if !allowNsOp(aclObj, out.Namespace) {
|
||||
return structs.NewErrUnknownAllocation(args.AllocID)
|
||||
}
|
||||
|
||||
reply.Index = out.ModifyIndex
|
||||
} else {
|
||||
// Use the last index that affected the allocs table
|
||||
|
@ -214,25 +219,18 @@ func (a *Alloc) Stop(args *structs.AllocStopRequest, reply *structs.AllocStopRes
|
|||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "alloc", "stop"}, time.Now())
|
||||
|
||||
// Check that it is a management token.
|
||||
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityAllocLifecycle) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
if args.AllocID == "" {
|
||||
return fmt.Errorf("must provide an alloc id")
|
||||
}
|
||||
|
||||
ws := memdb.NewWatchSet()
|
||||
alloc, err := a.srv.State().AllocByID(ws, args.AllocID)
|
||||
alloc, err := getAlloc(a.srv.State(), args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if alloc == nil {
|
||||
return fmt.Errorf(structs.ErrUnknownAllocationPrefix)
|
||||
// Check for namespace alloc-lifecycle permissions.
|
||||
allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityAllocLifecycle)
|
||||
aclObj, err := a.srv.ResolveToken(args.AuthToken)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !allowNsOp(aclObj, alloc.Namespace) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
now := time.Now().UTC().UnixNano()
|
||||
|
|
|
@ -276,54 +276,90 @@ func TestAllocEndpoint_GetAlloc_ACL(t *testing.T) {
|
|||
invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
|
||||
mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
|
||||
|
||||
get := &structs.AllocSpecificRequest{
|
||||
getReq := func() *structs.AllocSpecificRequest {
|
||||
return &structs.AllocSpecificRequest{
|
||||
AllocID: alloc.ID,
|
||||
QueryOptions: structs.QueryOptions{Region: "global"},
|
||||
QueryOptions: structs.QueryOptions{
|
||||
Region: "global",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
F func(t *testing.T)
|
||||
}{
|
||||
// Lookup the alloc without a token and expect failure
|
||||
{
|
||||
Name: "no-token",
|
||||
F: func(t *testing.T) {
|
||||
var resp structs.SingleAllocResponse
|
||||
err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp)
|
||||
assert.Equal(structs.ErrPermissionDenied.Error(), err.Error())
|
||||
}
|
||||
err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", getReq(), &resp)
|
||||
require.True(t, structs.IsErrUnknownAllocation(err), "expected unknown alloc but found: %v", err)
|
||||
},
|
||||
},
|
||||
|
||||
// Try with a valid ACL token
|
||||
{
|
||||
Name: "valid-token",
|
||||
F: func(t *testing.T) {
|
||||
get := getReq()
|
||||
get.AuthToken = validToken.SecretID
|
||||
get.AllocID = alloc.ID
|
||||
var resp structs.SingleAllocResponse
|
||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC")
|
||||
assert.EqualValues(resp.Index, 1000, "resp.Index")
|
||||
assert.Equal(alloc, resp.Alloc, "Returned alloc not equal")
|
||||
}
|
||||
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC")
|
||||
require.EqualValues(t, resp.Index, 1000, "resp.Index")
|
||||
require.Equal(t, alloc, resp.Alloc, "Returned alloc not equal")
|
||||
},
|
||||
},
|
||||
|
||||
// Try with a valid Node.SecretID
|
||||
{
|
||||
Name: "valid-node-secret",
|
||||
F: func(t *testing.T) {
|
||||
node := mock.Node()
|
||||
assert.Nil(state.UpsertNode(1005, node))
|
||||
get := getReq()
|
||||
get.AuthToken = node.SecretID
|
||||
get.AllocID = alloc.ID
|
||||
var resp structs.SingleAllocResponse
|
||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC")
|
||||
assert.EqualValues(resp.Index, 1000, "resp.Index")
|
||||
assert.Equal(alloc, resp.Alloc, "Returned alloc not equal")
|
||||
}
|
||||
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC")
|
||||
require.EqualValues(t, resp.Index, 1000, "resp.Index")
|
||||
require.Equal(t, alloc, resp.Alloc, "Returned alloc not equal")
|
||||
},
|
||||
},
|
||||
|
||||
// Try with a invalid token
|
||||
{
|
||||
Name: "invalid-token",
|
||||
F: func(t *testing.T) {
|
||||
get := getReq()
|
||||
get.AuthToken = invalidToken.SecretID
|
||||
get.AllocID = alloc.ID
|
||||
var resp structs.SingleAllocResponse
|
||||
err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp)
|
||||
assert.NotNil(err, "RPC")
|
||||
assert.Equal(err.Error(), structs.ErrPermissionDenied.Error())
|
||||
}
|
||||
require.NotNil(t, err, "RPC")
|
||||
require.True(t, structs.IsErrUnknownAllocation(err), "expected unknown alloc but found: %v", err)
|
||||
},
|
||||
},
|
||||
|
||||
// Try with a root token
|
||||
{
|
||||
Name: "root-token",
|
||||
F: func(t *testing.T) {
|
||||
get := getReq()
|
||||
get.AuthToken = root.SecretID
|
||||
get.AllocID = alloc.ID
|
||||
var resp structs.SingleAllocResponse
|
||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC")
|
||||
assert.EqualValues(resp.Index, 1000, "resp.Index")
|
||||
assert.Equal(alloc, resp.Alloc, "Returned alloc not equal")
|
||||
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC")
|
||||
require.EqualValues(t, resp.Index, 1000, "resp.Index")
|
||||
require.Equal(t, alloc, resp.Alloc, "Returned alloc not equal")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.Name, tc.F)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -87,13 +87,6 @@ func (a *ClientAllocations) Signal(args *structs.AllocSignalRequest, reply *stru
|
|||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "client_allocations", "signal"}, time.Now())
|
||||
|
||||
// Check node read permissions
|
||||
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityAllocLifecycle) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Verify the arguments.
|
||||
if args.AllocID == "" {
|
||||
return errors.New("missing AllocID")
|
||||
|
@ -105,13 +98,16 @@ func (a *ClientAllocations) Signal(args *structs.AllocSignalRequest, reply *stru
|
|||
return err
|
||||
}
|
||||
|
||||
alloc, err := snap.AllocByID(nil, args.AllocID)
|
||||
alloc, err := getAlloc(snap, args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if alloc == nil {
|
||||
return structs.NewErrUnknownAllocation(args.AllocID)
|
||||
// Check namespace alloc-lifecycle permission.
|
||||
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocLifecycle) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Make sure Node is valid and new enough to support RPC
|
||||
|
@ -143,13 +139,6 @@ func (a *ClientAllocations) GarbageCollect(args *structs.AllocSpecificRequest, r
|
|||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "client_allocations", "garbage_collect"}, time.Now())
|
||||
|
||||
// Check node read permissions
|
||||
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilitySubmitJob) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Verify the arguments.
|
||||
if args.AllocID == "" {
|
||||
return errors.New("missing AllocID")
|
||||
|
@ -161,13 +150,16 @@ func (a *ClientAllocations) GarbageCollect(args *structs.AllocSpecificRequest, r
|
|||
return err
|
||||
}
|
||||
|
||||
alloc, err := snap.AllocByID(nil, args.AllocID)
|
||||
alloc, err := getAlloc(snap, args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if alloc == nil {
|
||||
return structs.NewErrUnknownAllocation(args.AllocID)
|
||||
// Check namespace submit-job permission.
|
||||
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilitySubmitJob) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Make sure Node is valid and new enough to support RPC
|
||||
|
@ -199,30 +191,22 @@ func (a *ClientAllocations) Restart(args *structs.AllocRestartRequest, reply *st
|
|||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "client_allocations", "restart"}, time.Now())
|
||||
|
||||
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityAllocLifecycle) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Verify the arguments.
|
||||
if args.AllocID == "" {
|
||||
return errors.New("missing AllocID")
|
||||
}
|
||||
|
||||
// Find the allocation
|
||||
snap, err := a.srv.State().Snapshot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
alloc, err := snap.AllocByID(nil, args.AllocID)
|
||||
alloc, err := getAlloc(snap, args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if alloc == nil {
|
||||
return structs.NewErrUnknownAllocation(args.AllocID)
|
||||
// Check for namespace alloc-lifecycle permissions.
|
||||
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocLifecycle) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Make sure Node is valid and new enough to support RPC
|
||||
|
@ -254,31 +238,22 @@ func (a *ClientAllocations) Stats(args *cstructs.AllocStatsRequest, reply *cstru
|
|||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "client_allocations", "stats"}, time.Now())
|
||||
|
||||
// Check node read permissions
|
||||
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityReadJob) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Verify the arguments.
|
||||
if args.AllocID == "" {
|
||||
return errors.New("missing AllocID")
|
||||
}
|
||||
|
||||
// Find the allocation
|
||||
snap, err := a.srv.State().Snapshot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
alloc, err := snap.AllocByID(nil, args.AllocID)
|
||||
alloc, err := getAlloc(snap, args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if alloc == nil {
|
||||
return structs.NewErrUnknownAllocation(args.AllocID)
|
||||
// Check for namespace read-job permissions.
|
||||
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadJob) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Make sure Node is valid and new enough to support RPC
|
||||
|
@ -319,19 +294,6 @@ func (a *ClientAllocations) exec(conn io.ReadWriteCloser) {
|
|||
return
|
||||
}
|
||||
|
||||
// Check node read permissions
|
||||
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
handleStreamResultError(err, nil, encoder)
|
||||
return
|
||||
} else if aclObj != nil {
|
||||
// client ultimately checks if AllocNodeExec is required
|
||||
exec := aclObj.AllowNsOp(args.QueryOptions.Namespace, acl.NamespaceCapabilityAllocExec)
|
||||
if !exec {
|
||||
handleStreamResultError(structs.ErrPermissionDenied, nil, encoder)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the arguments.
|
||||
if args.AllocID == "" {
|
||||
handleStreamResultError(errors.New("missing AllocID"), helper.Int64ToPtr(400), encoder)
|
||||
|
@ -345,15 +307,26 @@ func (a *ClientAllocations) exec(conn io.ReadWriteCloser) {
|
|||
return
|
||||
}
|
||||
|
||||
alloc, err := snap.AllocByID(nil, args.AllocID)
|
||||
alloc, err := getAlloc(snap, args.AllocID)
|
||||
if structs.IsErrUnknownAllocation(err) {
|
||||
handleStreamResultError(err, helper.Int64ToPtr(404), encoder)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
handleStreamResultError(err, nil, encoder)
|
||||
return
|
||||
}
|
||||
if alloc == nil {
|
||||
handleStreamResultError(structs.NewErrUnknownAllocation(args.AllocID), helper.Int64ToPtr(404), encoder)
|
||||
|
||||
// Check node read permissions
|
||||
if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
handleStreamResultError(err, nil, encoder)
|
||||
return
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityAllocExec) {
|
||||
// client ultimately checks if AllocNodeExec is required
|
||||
handleStreamResultError(structs.ErrPermissionDenied, nil, encoder)
|
||||
return
|
||||
}
|
||||
|
||||
nodeID := alloc.NodeID
|
||||
|
||||
// Make sure Node is valid and new enough to support RPC
|
||||
|
|
|
@ -363,7 +363,6 @@ func TestClientAllocations_GarbageCollect_Local(t *testing.T) {
|
|||
|
||||
func TestClientAllocations_GarbageCollect_Local_ACL(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
// Start a server
|
||||
s, root := TestACLServer(t, nil)
|
||||
|
@ -378,6 +377,12 @@ func TestClientAllocations_GarbageCollect_Local_ACL(t *testing.T) {
|
|||
policyGood := mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilitySubmitJob})
|
||||
tokenGood := mock.CreatePolicyAndToken(t, s.State(), 1009, "valid2", policyGood)
|
||||
|
||||
// Upsert the allocation
|
||||
state := s.State()
|
||||
alloc := mock.Alloc()
|
||||
require.NoError(t, state.UpsertJob(1010, alloc.Job))
|
||||
require.NoError(t, state.UpsertAllocs(1011, []*structs.Allocation{alloc}))
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
Token string
|
||||
|
@ -391,12 +396,12 @@ func TestClientAllocations_GarbageCollect_Local_ACL(t *testing.T) {
|
|||
{
|
||||
Name: "good token",
|
||||
Token: tokenGood.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
ExpectedError: structs.ErrUnknownNodePrefix,
|
||||
},
|
||||
{
|
||||
Name: "root token",
|
||||
Token: root.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
ExpectedError: structs.ErrUnknownNodePrefix,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -405,7 +410,7 @@ func TestClientAllocations_GarbageCollect_Local_ACL(t *testing.T) {
|
|||
|
||||
// Make the request without having a node-id
|
||||
req := &structs.AllocSpecificRequest{
|
||||
AllocID: uuid.Generate(),
|
||||
AllocID: alloc.ID,
|
||||
QueryOptions: structs.QueryOptions{
|
||||
AuthToken: c.Token,
|
||||
Region: "global",
|
||||
|
@ -416,8 +421,8 @@ func TestClientAllocations_GarbageCollect_Local_ACL(t *testing.T) {
|
|||
// Fetch the response
|
||||
var resp structs.GenericResponse
|
||||
err := msgpackrpc.CallWithCodec(codec, "ClientAllocations.GarbageCollect", req, &resp)
|
||||
require.NotNil(err)
|
||||
require.Contains(err.Error(), c.ExpectedError)
|
||||
require.NotNil(t, err)
|
||||
require.Contains(t, err.Error(), c.ExpectedError)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -634,7 +639,7 @@ func TestClientAllocations_Stats_Local(t *testing.T) {
|
|||
var resp cstructs.AllocStatsResponse
|
||||
err := msgpackrpc.CallWithCodec(codec, "ClientAllocations.Stats", req, &resp)
|
||||
require.NotNil(err)
|
||||
require.Contains(err.Error(), "missing")
|
||||
require.EqualError(err, structs.ErrMissingAllocID.Error(), "(%T) %v")
|
||||
|
||||
// Fetch the response setting the node id
|
||||
req.AllocID = a.ID
|
||||
|
@ -646,7 +651,6 @@ func TestClientAllocations_Stats_Local(t *testing.T) {
|
|||
|
||||
func TestClientAllocations_Stats_Local_ACL(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
// Start a server
|
||||
s, root := TestACLServer(t, nil)
|
||||
|
@ -661,6 +665,12 @@ func TestClientAllocations_Stats_Local_ACL(t *testing.T) {
|
|||
policyGood := mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})
|
||||
tokenGood := mock.CreatePolicyAndToken(t, s.State(), 1009, "valid2", policyGood)
|
||||
|
||||
// Upsert the allocation
|
||||
state := s.State()
|
||||
alloc := mock.Alloc()
|
||||
require.NoError(t, state.UpsertJob(1010, alloc.Job))
|
||||
require.NoError(t, state.UpsertAllocs(1011, []*structs.Allocation{alloc}))
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
Token string
|
||||
|
@ -674,12 +684,12 @@ func TestClientAllocations_Stats_Local_ACL(t *testing.T) {
|
|||
{
|
||||
Name: "good token",
|
||||
Token: tokenGood.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
ExpectedError: structs.ErrUnknownNodePrefix,
|
||||
},
|
||||
{
|
||||
Name: "root token",
|
||||
Token: root.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
ExpectedError: structs.ErrUnknownNodePrefix,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -688,7 +698,7 @@ func TestClientAllocations_Stats_Local_ACL(t *testing.T) {
|
|||
|
||||
// Make the request without having a node-id
|
||||
req := &structs.AllocSpecificRequest{
|
||||
AllocID: uuid.Generate(),
|
||||
AllocID: alloc.ID,
|
||||
QueryOptions: structs.QueryOptions{
|
||||
AuthToken: c.Token,
|
||||
Region: "global",
|
||||
|
@ -699,8 +709,8 @@ func TestClientAllocations_Stats_Local_ACL(t *testing.T) {
|
|||
// Fetch the response
|
||||
var resp cstructs.AllocStatsResponse
|
||||
err := msgpackrpc.CallWithCodec(codec, "ClientAllocations.Stats", req, &resp)
|
||||
require.NotNil(err)
|
||||
require.Contains(err.Error(), c.ExpectedError)
|
||||
require.NotNil(t, err)
|
||||
require.Contains(t, err.Error(), c.ExpectedError)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -867,7 +877,7 @@ func TestClientAllocations_Restart_Local(t *testing.T) {
|
|||
var resp structs.GenericResponse
|
||||
err := msgpackrpc.CallWithCodec(codec, "ClientAllocations.Restart", req, &resp)
|
||||
require.NotNil(err)
|
||||
require.Contains(err.Error(), "missing")
|
||||
require.EqualError(err, structs.ErrMissingAllocID.Error(), "(%T) %v")
|
||||
|
||||
// Fetch the response setting the alloc id - This should not error because the
|
||||
// alloc is running.
|
||||
|
@ -981,14 +991,14 @@ func TestClientAllocations_Restart_Remote(t *testing.T) {
|
|||
var resp structs.GenericResponse
|
||||
err := msgpackrpc.CallWithCodec(codec, "ClientAllocations.Restart", req, &resp)
|
||||
require.NotNil(err)
|
||||
require.Contains(err.Error(), "missing")
|
||||
require.EqualError(err, structs.ErrMissingAllocID.Error(), "(%T) %v")
|
||||
|
||||
// Fetch the response setting the alloc id - This should succeed because the
|
||||
// alloc is running
|
||||
req.AllocID = a.ID
|
||||
var resp2 structs.GenericResponse
|
||||
err = msgpackrpc.CallWithCodec(codec, "ClientAllocations.Restart", req, &resp2)
|
||||
require.Nil(err)
|
||||
require.NoError(err)
|
||||
}
|
||||
|
||||
func TestClientAllocations_Restart_ACL(t *testing.T) {
|
||||
|
@ -1005,6 +1015,12 @@ func TestClientAllocations_Restart_ACL(t *testing.T) {
|
|||
policyGood := mock.NamespacePolicy(structs.DefaultNamespace, acl.PolicyWrite, nil)
|
||||
tokenGood := mock.CreatePolicyAndToken(t, s.State(), 1009, "valid2", policyGood)
|
||||
|
||||
// Upsert the allocation
|
||||
state := s.State()
|
||||
alloc := mock.Alloc()
|
||||
require.NoError(t, state.UpsertJob(1010, alloc.Job))
|
||||
require.NoError(t, state.UpsertAllocs(1011, []*structs.Allocation{alloc}))
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
Token string
|
||||
|
@ -1018,12 +1034,12 @@ func TestClientAllocations_Restart_ACL(t *testing.T) {
|
|||
{
|
||||
Name: "good token",
|
||||
Token: tokenGood.SecretID,
|
||||
ExpectedError: "Unknown alloc",
|
||||
ExpectedError: "Unknown node",
|
||||
},
|
||||
{
|
||||
Name: "root token",
|
||||
Token: root.SecretID,
|
||||
ExpectedError: "Unknown alloc",
|
||||
ExpectedError: "Unknown node",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1032,7 +1048,7 @@ func TestClientAllocations_Restart_ACL(t *testing.T) {
|
|||
|
||||
// Make the request without having a node-id
|
||||
req := &structs.AllocRestartRequest{
|
||||
AllocID: uuid.Generate(),
|
||||
AllocID: alloc.ID,
|
||||
QueryOptions: structs.QueryOptions{
|
||||
Namespace: structs.DefaultNamespace,
|
||||
AuthToken: c.Token,
|
||||
|
|
|
@ -108,13 +108,6 @@ func (f *FileSystem) List(args *cstructs.FsListRequest, reply *cstructs.FsListRe
|
|||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "file_system", "list"}, time.Now())
|
||||
|
||||
// Check filesystem read permissions
|
||||
if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityReadFS) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Verify the arguments.
|
||||
if args.AllocID == "" {
|
||||
return errors.New("missing allocation ID")
|
||||
|
@ -126,12 +119,18 @@ func (f *FileSystem) List(args *cstructs.FsListRequest, reply *cstructs.FsListRe
|
|||
return err
|
||||
}
|
||||
|
||||
alloc, err := snap.AllocByID(nil, args.AllocID)
|
||||
alloc, err := getAlloc(snap, args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if alloc == nil {
|
||||
return structs.NewErrUnknownAllocation(args.AllocID)
|
||||
|
||||
// Check namespace filesystem read permissions
|
||||
allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadFS)
|
||||
aclObj, err := f.srv.ResolveToken(args.AuthToken)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !allowNsOp(aclObj, alloc.Namespace) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Make sure Node is valid and new enough to support RPC
|
||||
|
@ -163,13 +162,6 @@ func (f *FileSystem) Stat(args *cstructs.FsStatRequest, reply *cstructs.FsStatRe
|
|||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "file_system", "stat"}, time.Now())
|
||||
|
||||
// Check filesystem read permissions
|
||||
if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityReadFS) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Verify the arguments.
|
||||
if args.AllocID == "" {
|
||||
return errors.New("missing allocation ID")
|
||||
|
@ -181,12 +173,16 @@ func (f *FileSystem) Stat(args *cstructs.FsStatRequest, reply *cstructs.FsStatRe
|
|||
return err
|
||||
}
|
||||
|
||||
alloc, err := snap.AllocByID(nil, args.AllocID)
|
||||
alloc, err := getAlloc(snap, args.AllocID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if alloc == nil {
|
||||
return structs.NewErrUnknownAllocation(args.AllocID)
|
||||
|
||||
// Check filesystem read permissions
|
||||
if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadFS) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Make sure Node is valid and new enough to support RPC
|
||||
|
@ -228,15 +224,6 @@ func (f *FileSystem) stream(conn io.ReadWriteCloser) {
|
|||
return
|
||||
}
|
||||
|
||||
// Check node read permissions
|
||||
if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
handleStreamResultError(err, nil, encoder)
|
||||
return
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.Namespace, acl.NamespaceCapabilityReadFS) {
|
||||
handleStreamResultError(structs.ErrPermissionDenied, nil, encoder)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the arguments.
|
||||
if args.AllocID == "" {
|
||||
handleStreamResultError(errors.New("missing AllocID"), helper.Int64ToPtr(400), encoder)
|
||||
|
@ -250,15 +237,25 @@ func (f *FileSystem) stream(conn io.ReadWriteCloser) {
|
|||
return
|
||||
}
|
||||
|
||||
alloc, err := snap.AllocByID(nil, args.AllocID)
|
||||
alloc, err := getAlloc(snap, args.AllocID)
|
||||
if structs.IsErrUnknownAllocation(err) {
|
||||
handleStreamResultError(structs.NewErrUnknownAllocation(args.AllocID), helper.Int64ToPtr(404), encoder)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
handleStreamResultError(err, nil, encoder)
|
||||
return
|
||||
}
|
||||
if alloc == nil {
|
||||
handleStreamResultError(structs.NewErrUnknownAllocation(args.AllocID), helper.Int64ToPtr(404), encoder)
|
||||
|
||||
// Check namespace read-fs permissions.
|
||||
if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
handleStreamResultError(err, nil, encoder)
|
||||
return
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(alloc.Namespace, acl.NamespaceCapabilityReadFS) {
|
||||
handleStreamResultError(structs.ErrPermissionDenied, nil, encoder)
|
||||
return
|
||||
}
|
||||
|
||||
nodeID := alloc.NodeID
|
||||
|
||||
// Make sure Node is valid and new enough to support RPC
|
||||
|
@ -346,22 +343,9 @@ func (f *FileSystem) logs(conn io.ReadWriteCloser) {
|
|||
return
|
||||
}
|
||||
|
||||
// Check node read permissions
|
||||
if aclObj, err := f.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
handleStreamResultError(err, nil, encoder)
|
||||
return
|
||||
} else if aclObj != nil {
|
||||
readfs := aclObj.AllowNsOp(args.QueryOptions.Namespace, acl.NamespaceCapabilityReadFS)
|
||||
logs := aclObj.AllowNsOp(args.QueryOptions.Namespace, acl.NamespaceCapabilityReadLogs)
|
||||
if !readfs && !logs {
|
||||
handleStreamResultError(structs.ErrPermissionDenied, nil, encoder)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the arguments.
|
||||
if args.AllocID == "" {
|
||||
handleStreamResultError(errors.New("missing AllocID"), helper.Int64ToPtr(400), encoder)
|
||||
handleStreamResultError(structs.ErrMissingAllocID, helper.Int64ToPtr(400), encoder)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -372,15 +356,28 @@ func (f *FileSystem) logs(conn io.ReadWriteCloser) {
|
|||
return
|
||||
}
|
||||
|
||||
alloc, err := snap.AllocByID(nil, args.AllocID)
|
||||
alloc, err := getAlloc(snap, args.AllocID)
|
||||
if structs.IsErrUnknownAllocation(err) {
|
||||
handleStreamResultError(structs.NewErrUnknownAllocation(args.AllocID), helper.Int64ToPtr(404), encoder)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
handleStreamResultError(err, nil, encoder)
|
||||
return
|
||||
}
|
||||
if alloc == nil {
|
||||
handleStreamResultError(structs.NewErrUnknownAllocation(args.AllocID), helper.Int64ToPtr(404), encoder)
|
||||
|
||||
// Check namespace read-logs *or* read-fs permissions.
|
||||
allowNsOp := acl.NamespaceValidator(
|
||||
acl.NamespaceCapabilityReadFS, acl.NamespaceCapabilityReadLogs)
|
||||
aclObj, err := f.srv.ResolveToken(args.AuthToken)
|
||||
if err != nil {
|
||||
handleStreamResultError(err, nil, encoder)
|
||||
return
|
||||
} else if !allowNsOp(aclObj, alloc.Namespace) {
|
||||
handleStreamResultError(structs.ErrPermissionDenied, nil, encoder)
|
||||
return
|
||||
}
|
||||
|
||||
nodeID := alloc.NodeID
|
||||
|
||||
// Make sure Node is valid and new enough to support RPC
|
||||
|
|
|
@ -107,7 +107,6 @@ func TestClientFS_List_Local(t *testing.T) {
|
|||
|
||||
func TestClientFS_List_ACL(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
// Start a server
|
||||
s, root := TestACLServer(t, nil)
|
||||
|
@ -122,6 +121,12 @@ func TestClientFS_List_ACL(t *testing.T) {
|
|||
policyGood := mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadFS})
|
||||
tokenGood := mock.CreatePolicyAndToken(t, s.State(), 1009, "valid2", policyGood)
|
||||
|
||||
// Upsert the allocation
|
||||
state := s.State()
|
||||
alloc := mock.Alloc()
|
||||
require.NoError(t, state.UpsertJob(1010, alloc.Job))
|
||||
require.NoError(t, state.UpsertAllocs(1011, []*structs.Allocation{alloc}))
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
Token string
|
||||
|
@ -135,12 +140,12 @@ func TestClientFS_List_ACL(t *testing.T) {
|
|||
{
|
||||
Name: "good token",
|
||||
Token: tokenGood.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
ExpectedError: structs.ErrUnknownNodePrefix,
|
||||
},
|
||||
{
|
||||
Name: "root token",
|
||||
Token: root.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
ExpectedError: structs.ErrUnknownNodePrefix,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -149,7 +154,7 @@ func TestClientFS_List_ACL(t *testing.T) {
|
|||
|
||||
// Make the request
|
||||
req := &cstructs.FsListRequest{
|
||||
AllocID: uuid.Generate(),
|
||||
AllocID: alloc.ID,
|
||||
Path: "/",
|
||||
QueryOptions: structs.QueryOptions{
|
||||
Region: "global",
|
||||
|
@ -161,8 +166,8 @@ func TestClientFS_List_ACL(t *testing.T) {
|
|||
// Fetch the response
|
||||
var resp cstructs.FsListResponse
|
||||
err := msgpackrpc.CallWithCodec(codec, "FileSystem.List", req, &resp)
|
||||
require.NotNil(err)
|
||||
require.Contains(err.Error(), c.ExpectedError)
|
||||
require.NotNil(t, err)
|
||||
require.Contains(t, err.Error(), c.ExpectedError)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -376,7 +381,6 @@ func TestClientFS_Stat_Local(t *testing.T) {
|
|||
|
||||
func TestClientFS_Stat_ACL(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
// Start a server
|
||||
s, root := TestACLServer(t, nil)
|
||||
|
@ -391,6 +395,12 @@ func TestClientFS_Stat_ACL(t *testing.T) {
|
|||
policyGood := mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadFS})
|
||||
tokenGood := mock.CreatePolicyAndToken(t, s.State(), 1009, "valid2", policyGood)
|
||||
|
||||
// Upsert the allocation
|
||||
state := s.State()
|
||||
alloc := mock.Alloc()
|
||||
require.NoError(t, state.UpsertJob(1010, alloc.Job))
|
||||
require.NoError(t, state.UpsertAllocs(1011, []*structs.Allocation{alloc}))
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
Token string
|
||||
|
@ -404,12 +414,12 @@ func TestClientFS_Stat_ACL(t *testing.T) {
|
|||
{
|
||||
Name: "good token",
|
||||
Token: tokenGood.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
ExpectedError: structs.ErrUnknownNodePrefix,
|
||||
},
|
||||
{
|
||||
Name: "root token",
|
||||
Token: root.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
ExpectedError: structs.ErrUnknownNodePrefix,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -418,7 +428,7 @@ func TestClientFS_Stat_ACL(t *testing.T) {
|
|||
|
||||
// Make the request
|
||||
req := &cstructs.FsStatRequest{
|
||||
AllocID: uuid.Generate(),
|
||||
AllocID: alloc.ID,
|
||||
Path: "/",
|
||||
QueryOptions: structs.QueryOptions{
|
||||
Region: "global",
|
||||
|
@ -430,8 +440,8 @@ func TestClientFS_Stat_ACL(t *testing.T) {
|
|||
// Fetch the response
|
||||
var resp cstructs.FsStatResponse
|
||||
err := msgpackrpc.CallWithCodec(codec, "FileSystem.Stat", req, &resp)
|
||||
require.NotNil(err)
|
||||
require.Contains(err.Error(), c.ExpectedError)
|
||||
require.NotNil(t, err)
|
||||
require.Contains(t, err.Error(), c.ExpectedError)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -601,7 +611,6 @@ OUTER:
|
|||
|
||||
func TestClientFS_Streaming_ACL(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
// Start a server
|
||||
s, root := TestACLServer(t, nil)
|
||||
|
@ -616,6 +625,12 @@ func TestClientFS_Streaming_ACL(t *testing.T) {
|
|||
[]string{acl.NamespaceCapabilityReadLogs, acl.NamespaceCapabilityReadFS})
|
||||
tokenGood := mock.CreatePolicyAndToken(t, s.State(), 1009, "valid2", policyGood)
|
||||
|
||||
// Upsert the allocation
|
||||
state := s.State()
|
||||
alloc := mock.Alloc()
|
||||
require.NoError(t, state.UpsertJob(1010, alloc.Job))
|
||||
require.NoError(t, state.UpsertAllocs(1011, []*structs.Allocation{alloc}))
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
Token string
|
||||
|
@ -629,12 +644,12 @@ func TestClientFS_Streaming_ACL(t *testing.T) {
|
|||
{
|
||||
Name: "good token",
|
||||
Token: tokenGood.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
ExpectedError: structs.ErrUnknownNodePrefix,
|
||||
},
|
||||
{
|
||||
Name: "root token",
|
||||
Token: root.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
ExpectedError: structs.ErrUnknownNodePrefix,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -642,7 +657,7 @@ func TestClientFS_Streaming_ACL(t *testing.T) {
|
|||
t.Run(c.Name, func(t *testing.T) {
|
||||
// Make the request with bad allocation id
|
||||
req := &cstructs.FsStreamRequest{
|
||||
AllocID: uuid.Generate(),
|
||||
AllocID: alloc.ID,
|
||||
QueryOptions: structs.QueryOptions{
|
||||
Namespace: structs.DefaultNamespace,
|
||||
Region: "global",
|
||||
|
@ -652,7 +667,7 @@ func TestClientFS_Streaming_ACL(t *testing.T) {
|
|||
|
||||
// Get the handler
|
||||
handler, err := s.StreamingRpcHandler("FileSystem.Stream")
|
||||
require.Nil(err)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a pipe
|
||||
p1, p2 := net.Pipe()
|
||||
|
@ -683,7 +698,7 @@ func TestClientFS_Streaming_ACL(t *testing.T) {
|
|||
|
||||
// Send the request
|
||||
encoder := codec.NewEncoder(p1, structs.MsgpackHandle)
|
||||
require.Nil(encoder.Encode(req))
|
||||
require.NoError(t, encoder.Encode(req))
|
||||
|
||||
timeout := time.After(5 * time.Second)
|
||||
|
||||
|
@ -1423,7 +1438,6 @@ OUTER:
|
|||
|
||||
func TestClientFS_Logs_ACL(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
// Start a server
|
||||
s, root := TestACLServer(t, nil)
|
||||
|
@ -1438,6 +1452,12 @@ func TestClientFS_Logs_ACL(t *testing.T) {
|
|||
[]string{acl.NamespaceCapabilityReadLogs, acl.NamespaceCapabilityReadFS})
|
||||
tokenGood := mock.CreatePolicyAndToken(t, s.State(), 1009, "valid2", policyGood)
|
||||
|
||||
// Upsert the allocation
|
||||
state := s.State()
|
||||
alloc := mock.Alloc()
|
||||
require.NoError(t, state.UpsertJob(1010, alloc.Job))
|
||||
require.NoError(t, state.UpsertAllocs(1011, []*structs.Allocation{alloc}))
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
Token string
|
||||
|
@ -1451,12 +1471,12 @@ func TestClientFS_Logs_ACL(t *testing.T) {
|
|||
{
|
||||
Name: "good token",
|
||||
Token: tokenGood.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
ExpectedError: structs.ErrUnknownNodePrefix,
|
||||
},
|
||||
{
|
||||
Name: "root token",
|
||||
Token: root.SecretID,
|
||||
ExpectedError: structs.ErrUnknownAllocationPrefix,
|
||||
ExpectedError: structs.ErrUnknownNodePrefix,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1464,7 +1484,7 @@ func TestClientFS_Logs_ACL(t *testing.T) {
|
|||
t.Run(c.Name, func(t *testing.T) {
|
||||
// Make the request with bad allocation id
|
||||
req := &cstructs.FsLogsRequest{
|
||||
AllocID: uuid.Generate(),
|
||||
AllocID: alloc.ID,
|
||||
QueryOptions: structs.QueryOptions{
|
||||
Namespace: structs.DefaultNamespace,
|
||||
Region: "global",
|
||||
|
@ -1474,7 +1494,7 @@ func TestClientFS_Logs_ACL(t *testing.T) {
|
|||
|
||||
// Get the handler
|
||||
handler, err := s.StreamingRpcHandler("FileSystem.Logs")
|
||||
require.Nil(err)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create a pipe
|
||||
p1, p2 := net.Pipe()
|
||||
|
@ -1505,7 +1525,7 @@ func TestClientFS_Logs_ACL(t *testing.T) {
|
|||
|
||||
// Send the request
|
||||
encoder := codec.NewEncoder(p1, structs.MsgpackHandle)
|
||||
require.Nil(encoder.Encode(req))
|
||||
require.NoError(t, encoder.Encode(req))
|
||||
|
||||
timeout := time.After(5 * time.Second)
|
||||
|
||||
|
|
|
@ -28,9 +28,11 @@ func (d *Deployment) GetDeployment(args *structs.DeploymentSpecificRequest,
|
|||
defer metrics.MeasureSince([]string{"nomad", "deployment", "get_deployment"}, time.Now())
|
||||
|
||||
// Check namespace read-job permissions
|
||||
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob)
|
||||
aclObj, err := d.srv.ResolveToken(args.AuthToken)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
|
||||
} else if !allowNsOp(aclObj, args.RequestNamespace()) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
|
@ -53,6 +55,11 @@ func (d *Deployment) GetDeployment(args *structs.DeploymentSpecificRequest,
|
|||
// Setup the output
|
||||
reply.Deployment = out
|
||||
if out != nil {
|
||||
// Re-check namespace in case it differs from request.
|
||||
if !allowNsOp(aclObj, out.Namespace) {
|
||||
return structs.NewErrUnknownAllocation(args.DeploymentID)
|
||||
}
|
||||
|
||||
reply.Index = out.ModifyIndex
|
||||
} else {
|
||||
// Use the last index that affected the deployments table
|
||||
|
@ -77,13 +84,6 @@ func (d *Deployment) Fail(args *structs.DeploymentFailRequest, reply *structs.De
|
|||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "deployment", "fail"}, time.Now())
|
||||
|
||||
// Check namespace submit-job permissions
|
||||
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Validate the arguments
|
||||
if args.DeploymentID == "" {
|
||||
return fmt.Errorf("missing deployment ID")
|
||||
|
@ -104,6 +104,13 @@ func (d *Deployment) Fail(args *structs.DeploymentFailRequest, reply *structs.De
|
|||
return fmt.Errorf("deployment not found")
|
||||
}
|
||||
|
||||
// Check namespace submit-job permissions
|
||||
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
if !deploy.Active() {
|
||||
return fmt.Errorf("can't fail terminal deployment")
|
||||
}
|
||||
|
@ -119,13 +126,6 @@ func (d *Deployment) Pause(args *structs.DeploymentPauseRequest, reply *structs.
|
|||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "deployment", "pause"}, time.Now())
|
||||
|
||||
// Check namespace submit-job permissions
|
||||
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Validate the arguments
|
||||
if args.DeploymentID == "" {
|
||||
return fmt.Errorf("missing deployment ID")
|
||||
|
@ -146,6 +146,13 @@ func (d *Deployment) Pause(args *structs.DeploymentPauseRequest, reply *structs.
|
|||
return fmt.Errorf("deployment not found")
|
||||
}
|
||||
|
||||
// Check namespace submit-job permissions
|
||||
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
if !deploy.Active() {
|
||||
if args.Pause {
|
||||
return fmt.Errorf("can't pause terminal deployment")
|
||||
|
@ -165,13 +172,6 @@ func (d *Deployment) Promote(args *structs.DeploymentPromoteRequest, reply *stru
|
|||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "deployment", "promote"}, time.Now())
|
||||
|
||||
// Check namespace submit-job permissions
|
||||
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Validate the arguments
|
||||
if args.DeploymentID == "" {
|
||||
return fmt.Errorf("missing deployment ID")
|
||||
|
@ -192,6 +192,13 @@ func (d *Deployment) Promote(args *structs.DeploymentPromoteRequest, reply *stru
|
|||
return fmt.Errorf("deployment not found")
|
||||
}
|
||||
|
||||
// Check namespace submit-job permissions
|
||||
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
if !deploy.Active() {
|
||||
return fmt.Errorf("can't promote terminal deployment")
|
||||
}
|
||||
|
@ -208,13 +215,6 @@ func (d *Deployment) SetAllocHealth(args *structs.DeploymentAllocHealthRequest,
|
|||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "deployment", "set_alloc_health"}, time.Now())
|
||||
|
||||
// Check namespace submit-job permissions
|
||||
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Validate the arguments
|
||||
if args.DeploymentID == "" {
|
||||
return fmt.Errorf("missing deployment ID")
|
||||
|
@ -239,6 +239,13 @@ func (d *Deployment) SetAllocHealth(args *structs.DeploymentAllocHealthRequest,
|
|||
return fmt.Errorf("deployment not found")
|
||||
}
|
||||
|
||||
// Check namespace submit-job permissions
|
||||
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(deploy.Namespace, acl.NamespaceCapabilitySubmitJob) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
if !deploy.Active() {
|
||||
return fmt.Errorf("can't set health of allocations for a terminal deployment")
|
||||
}
|
||||
|
@ -254,7 +261,8 @@ func (d *Deployment) List(args *structs.DeploymentListRequest, reply *structs.De
|
|||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "deployment", "list"}, time.Now())
|
||||
|
||||
// Check namespace read-job permissions
|
||||
// Check namespace read-job permissions against request namespace since
|
||||
// results are filtered by request namespace.
|
||||
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
|
||||
|
@ -310,10 +318,14 @@ func (d *Deployment) Allocations(args *structs.DeploymentSpecificRequest, reply
|
|||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "deployment", "allocations"}, time.Now())
|
||||
|
||||
// Check namespace read-job permissions
|
||||
if aclObj, err := d.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
// Check namespace read-job permissions against the request namespace.
|
||||
// Must re-check against the alloc namespace when they return to ensure
|
||||
// there's no namespace mismatch.
|
||||
allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob)
|
||||
aclObj, err := d.srv.ResolveToken(args.AuthToken)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
|
||||
} else if !allowNsOp(aclObj, args.RequestNamespace()) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
|
@ -328,6 +340,15 @@ func (d *Deployment) Allocations(args *structs.DeploymentSpecificRequest, reply
|
|||
return err
|
||||
}
|
||||
|
||||
// Deployments do not span namespaces so just check the
|
||||
// first allocs namespace.
|
||||
if len(allocs) > 0 {
|
||||
ns := allocs[0].Namespace
|
||||
if ns != args.RequestNamespace() && !allowNsOp(aclObj, ns) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
}
|
||||
|
||||
stubs := make([]*structs.AllocListStub, 0, len(allocs))
|
||||
for _, alloc := range allocs {
|
||||
stubs = append(stubs, alloc.Stub())
|
||||
|
|
|
@ -34,10 +34,12 @@ func (e *Eval) GetEval(args *structs.EvalSpecificRequest,
|
|||
}
|
||||
defer metrics.MeasureSince([]string{"nomad", "eval", "get_eval"}, time.Now())
|
||||
|
||||
// Check for read-job permissions
|
||||
if aclObj, err := e.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
// Check for read-job permissions before performing blocking query.
|
||||
allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob)
|
||||
aclObj, err := e.srv.ResolveToken(args.AuthToken)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
|
||||
} else if !allowNsOp(aclObj, args.RequestNamespace()) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
|
@ -55,6 +57,11 @@ func (e *Eval) GetEval(args *structs.EvalSpecificRequest,
|
|||
// Setup the output
|
||||
reply.Eval = out
|
||||
if out != nil {
|
||||
// Re-check namespace in case it differs from request.
|
||||
if !allowNsOp(aclObj, out.Namespace) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
reply.Index = out.ModifyIndex
|
||||
} else {
|
||||
// Use the last index that affected the nodes table
|
||||
|
@ -389,9 +396,11 @@ func (e *Eval) Allocations(args *structs.EvalSpecificRequest,
|
|||
defer metrics.MeasureSince([]string{"nomad", "eval", "allocations"}, time.Now())
|
||||
|
||||
// Check for read-job permissions
|
||||
if aclObj, err := e.srv.ResolveToken(args.AuthToken); err != nil {
|
||||
allowNsOp := acl.NamespaceValidator(acl.NamespaceCapabilityReadJob)
|
||||
aclObj, err := e.srv.ResolveToken(args.AuthToken)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) {
|
||||
} else if !allowNsOp(aclObj, args.RequestNamespace()) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
|
@ -408,6 +417,13 @@ func (e *Eval) Allocations(args *structs.EvalSpecificRequest,
|
|||
|
||||
// Convert to a stub
|
||||
if len(allocs) > 0 {
|
||||
// Evaluations do not span namespaces so just check the
|
||||
// first allocs namespace.
|
||||
ns := allocs[0].Namespace
|
||||
if ns != args.RequestNamespace() && !allowNsOp(aclObj, ns) {
|
||||
return structs.ErrPermissionDenied
|
||||
}
|
||||
|
||||
reply.Allocations = make([]*structs.AllocListStub, 0, len(allocs))
|
||||
for _, alloc := range allocs {
|
||||
reply.Allocations = append(reply.Allocations, alloc.Stub())
|
||||
|
|
|
@ -16,6 +16,7 @@ const (
|
|||
errUnknownMethod = "Unknown rpc method"
|
||||
errUnknownNomadVersion = "Unable to determine Nomad version"
|
||||
errNodeLacksRpc = "Node does not support RPC; requires 0.8 or later"
|
||||
errMissingAllocID = "Missing allocation ID"
|
||||
|
||||
// Prefix based errors that are used to check if the error is of a given
|
||||
// type. These errors should be created with the associated constructor.
|
||||
|
@ -36,6 +37,7 @@ var (
|
|||
ErrUnknownMethod = errors.New(errUnknownMethod)
|
||||
ErrUnknownNomadVersion = errors.New(errUnknownNomadVersion)
|
||||
ErrNodeLacksRpc = errors.New(errNodeLacksRpc)
|
||||
ErrMissingAllocID = errors.New(errMissingAllocID)
|
||||
)
|
||||
|
||||
// IsErrNoLeader returns whether the error is due to there being no leader.
|
||||
|
|
|
@ -207,6 +207,13 @@ type QueryOptions struct {
|
|||
Region string
|
||||
|
||||
// Namespace is the target namespace for the query.
|
||||
//
|
||||
// Since handlers do not have a default value set they should access
|
||||
// the Namespace via the RequestNamespace method.
|
||||
//
|
||||
// Requests accessing specific namespaced objects must check ACLs
|
||||
// against the namespace of the object, not the namespace in the
|
||||
// request.
|
||||
Namespace string
|
||||
|
||||
// If set, wait until query exceeds given index. Must be provided
|
||||
|
@ -233,6 +240,11 @@ func (q QueryOptions) RequestRegion() string {
|
|||
return q.Region
|
||||
}
|
||||
|
||||
// RequestNamespace returns the request's namespace or the default namespace if
|
||||
// no explicit namespace was sent.
|
||||
//
|
||||
// Requests accessing specific namespaced objects must check ACLs against the
|
||||
// namespace of the object, not the namespace in the request.
|
||||
func (q QueryOptions) RequestNamespace() string {
|
||||
if q.Namespace == "" {
|
||||
return DefaultNamespace
|
||||
|
@ -254,6 +266,13 @@ type WriteRequest struct {
|
|||
Region string
|
||||
|
||||
// Namespace is the target namespace for the write.
|
||||
//
|
||||
// Since RPC handlers do not have a default value set they should
|
||||
// access the Namespace via the RequestNamespace method.
|
||||
//
|
||||
// Requests accessing specific namespaced objects must check ACLs
|
||||
// against the namespace of the object, not the namespace in the
|
||||
// request.
|
||||
Namespace string
|
||||
|
||||
// AuthToken is secret portion of the ACL token used for the request
|
||||
|
@ -267,6 +286,11 @@ func (w WriteRequest) RequestRegion() string {
|
|||
return w.Region
|
||||
}
|
||||
|
||||
// RequestNamespace returns the request's namespace or the default namespace if
|
||||
// no explicit namespace was sent.
|
||||
//
|
||||
// Requests accessing specific namespaced objects must check ACLs against the
|
||||
// namespace of the object, not the namespace in the request.
|
||||
func (w WriteRequest) RequestNamespace() string {
|
||||
if w.Namespace == "" {
|
||||
return DefaultNamespace
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
memdb "github.com/hashicorp/go-memdb"
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/nomad/nomad/state"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
|
@ -275,3 +276,28 @@ func nodeSupportsRpc(node *structs.Node) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllocGetter is an interface for retrieving allocations by ID. It is
|
||||
// satisfied by *state.StateStore and *state.StateSnapshot.
|
||||
type AllocGetter interface {
|
||||
AllocByID(ws memdb.WatchSet, id string) (*structs.Allocation, error)
|
||||
}
|
||||
|
||||
// getAlloc retrieves an allocation by ID and namespace. If the allocation is
|
||||
// nil, an error is returned.
|
||||
func getAlloc(state AllocGetter, allocID string) (*structs.Allocation, error) {
|
||||
if allocID == "" {
|
||||
return nil, structs.ErrMissingAllocID
|
||||
}
|
||||
|
||||
alloc, err := state.AllocByID(nil, allocID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if alloc == nil {
|
||||
return nil, structs.NewErrUnknownAllocation(allocID)
|
||||
}
|
||||
|
||||
return alloc, nil
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
"github.com/kr/pretty"
|
||||
testing "github.com/mitchellh/go-testing-interface"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -125,13 +126,14 @@ func WaitForVotingMembers(t testing.T, rpc rpcFn, nPeers int) {
|
|||
})
|
||||
}
|
||||
|
||||
// RegisterJobWithToken registers a job and uses the job's Region and Namespace.
|
||||
func RegisterJobWithToken(t testing.T, rpc rpcFn, job *structs.Job, token string) {
|
||||
WaitForResult(func() (bool, error) {
|
||||
args := &structs.JobRegisterRequest{}
|
||||
args.Job = job
|
||||
args.WriteRequest.Region = "global"
|
||||
args.WriteRequest.Region = job.Region
|
||||
args.AuthToken = token
|
||||
args.Namespace = structs.DefaultNamespace
|
||||
args.Namespace = job.Namespace
|
||||
var jobResp structs.JobRegisterResponse
|
||||
err := rpc("Job.Register", args, &jobResp)
|
||||
return err == nil, fmt.Errorf("Job.Register error: %v", err)
|
||||
|
@ -154,16 +156,18 @@ func WaitForRunningWithToken(t testing.T, rpc rpcFn, job *structs.Job, token str
|
|||
WaitForResult(func() (bool, error) {
|
||||
args := &structs.JobSpecificRequest{}
|
||||
args.JobID = job.ID
|
||||
args.QueryOptions.Region = "global"
|
||||
args.QueryOptions.Region = job.Region
|
||||
args.AuthToken = token
|
||||
args.Namespace = structs.DefaultNamespace
|
||||
args.Namespace = job.Namespace
|
||||
err := rpc("Job.Allocations", args, &resp)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Job.Allocations error: %v", err)
|
||||
}
|
||||
|
||||
if len(resp.Allocations) == 0 {
|
||||
return false, fmt.Errorf("0 allocations")
|
||||
evals := structs.JobEvaluationsResponse{}
|
||||
require.NoError(t, rpc("Job.Evaluations", args, &evals), "error looking up evals")
|
||||
return false, fmt.Errorf("0 allocations; evals: %s", pretty.Sprint(evals.Evaluations))
|
||||
}
|
||||
|
||||
for _, alloc := range resp.Allocations {
|
||||
|
|
Loading…
Reference in New Issue