Unverified Commit 99c9ff79 authored by Danielle Lancashire's avatar Danielle Lancashire
Browse files

csi_endpoint: Support No ACLs and restrict Nodes

This commit refactors the ACL code for the CSI endpoint to support
environments that run without acls enabled (e.g developer environments)
and also provides an easy way to restrict which endpoints may be
accessed with a client's SecretID to limit the blast radius of a
malicious client on the state of the environment.
parent 92430f9d
Showing with 80 additions and 25 deletions
+80 -25
package nomad
import (
"fmt"
"time"
metrics "github.com/armon/go-metrics"
......@@ -21,20 +20,24 @@ type CSIVolume struct {
// QueryACLObj looks up the ACL token in the request and returns the acl.ACL object
// - fallback to node secret ids
func (srv *Server) QueryACLObj(args *structs.QueryOptions) (*acl.ACL, error) {
if args.AuthToken == "" {
return nil, fmt.Errorf("authorization required")
}
func (srv *Server) QueryACLObj(args *structs.QueryOptions, allowNodeAccess bool) (*acl.ACL, error) {
// Lookup the token
aclObj, err := srv.ResolveToken(args.AuthToken)
if err != nil {
// If ResolveToken had an unexpected error return that
return nil, err
}
if !structs.IsErrTokenNotFound(err) {
return nil, err
}
// If we don't allow access to this endpoint from Nodes, then return token
// not found.
if !allowNodeAccess {
return nil, structs.ErrTokenNotFound
}
if aclObj == nil {
ws := memdb.NewWatchSet()
// Attempt to lookup AuthToken as a Node.SecretID since nodes may call
// call this endpoint and don't have an ACL token.
node, stateErr := srv.fsm.State().NodeBySecretID(ws, args.AuthToken)
if stateErr != nil {
// Return the original ResolveToken error with this err
......@@ -43,22 +46,24 @@ func (srv *Server) QueryACLObj(args *structs.QueryOptions) (*acl.ACL, error) {
return nil, merr.ErrorOrNil()
}
// We did not find a Node for this ID, so return Token Not Found.
if node == nil {
return nil, structs.ErrTokenNotFound
}
}
// Return either the users aclObj, or nil if ACLs are disabled.
return aclObj, nil
}
// WriteACLObj calls QueryACLObj for a WriteRequest
func (srv *Server) WriteACLObj(args *structs.WriteRequest) (*acl.ACL, error) {
func (srv *Server) WriteACLObj(args *structs.WriteRequest, allowNodeAccess bool) (*acl.ACL, error) {
opts := &structs.QueryOptions{
Region: args.RequestRegion(),
Namespace: args.RequestNamespace(),
AuthToken: args.AuthToken,
}
return srv.QueryACLObj(opts)
return srv.QueryACLObj(opts, allowNodeAccess)
}
const (
......@@ -87,7 +92,8 @@ func (v *CSIVolume) List(args *structs.CSIVolumeListRequest, reply *structs.CSIV
return err
}
aclObj, err := v.srv.QueryACLObj(&args.QueryOptions)
allowCSIAccess := acl.NamespaceValidator(acl.NamespaceCapabilityCSIAccess)
aclObj, err := v.srv.QueryACLObj(&args.QueryOptions, false)
if err != nil {
return err
}
......@@ -138,7 +144,7 @@ func (v *CSIVolume) List(args *structs.CSIVolumeListRequest, reply *structs.CSIV
// Cache ACL checks QUESTION: are they expensive?
allowed, ok := cache[vol.Namespace]
if !ok {
allowed = aclObj.AllowNsOp(vol.Namespace, acl.NamespaceCapabilityCSIAccess)
allowed = allowCSIAccess(aclObj, vol.Namespace)
cache[vol.Namespace] = allowed
}
......@@ -158,12 +164,13 @@ func (v *CSIVolume) Get(args *structs.CSIVolumeGetRequest, reply *structs.CSIVol
return err
}
aclObj, err := v.srv.QueryACLObj(&args.QueryOptions)
allowCSIAccess := acl.NamespaceValidator(acl.NamespaceCapabilityCSIAccess)
aclObj, err := v.srv.QueryACLObj(&args.QueryOptions, true)
if err != nil {
return err
}
if !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityCSIAccess) {
if !allowCSIAccess(aclObj, args.RequestNamespace()) {
return structs.ErrPermissionDenied
}
......@@ -198,7 +205,8 @@ func (v *CSIVolume) Register(args *structs.CSIVolumeRegisterRequest, reply *stru
return err
}
aclObj, err := v.srv.WriteACLObj(&args.WriteRequest)
allowCSIVolumeManagement := acl.NamespaceValidator(acl.NamespaceCapabilityCSICreateVolume)
aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, false)
if err != nil {
return err
}
......@@ -206,7 +214,7 @@ func (v *CSIVolume) Register(args *structs.CSIVolumeRegisterRequest, reply *stru
metricsStart := time.Now()
defer metrics.MeasureSince([]string{"nomad", "volume", "register"}, metricsStart)
if !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityCSICreateVolume) {
if !allowCSIVolumeManagement(aclObj, args.RequestNamespace()) {
return structs.ErrPermissionDenied
}
......@@ -238,7 +246,8 @@ func (v *CSIVolume) Deregister(args *structs.CSIVolumeDeregisterRequest, reply *
return err
}
aclObj, err := v.srv.WriteACLObj(&args.WriteRequest)
allowCSIVolumeManagement := acl.NamespaceValidator(acl.NamespaceCapabilityCSICreateVolume)
aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, false)
if err != nil {
return err
}
......@@ -247,7 +256,7 @@ func (v *CSIVolume) Deregister(args *structs.CSIVolumeDeregisterRequest, reply *
defer metrics.MeasureSince([]string{"nomad", "volume", "deregister"}, metricsStart)
ns := args.RequestNamespace()
if !aclObj.AllowNsOp(ns, acl.NamespaceCapabilityCSICreateVolume) {
if !allowCSIVolumeManagement(aclObj, ns) {
return structs.ErrPermissionDenied
}
......@@ -271,7 +280,8 @@ func (v *CSIVolume) Claim(args *structs.CSIVolumeClaimRequest, reply *structs.CS
return err
}
aclObj, err := v.srv.WriteACLObj(&args.WriteRequest)
allowCSIAccess := acl.NamespaceValidator(acl.NamespaceCapabilityCSIAccess)
aclObj, err := v.srv.WriteACLObj(&args.WriteRequest, true)
if err != nil {
return err
}
......@@ -279,7 +289,7 @@ func (v *CSIVolume) Claim(args *structs.CSIVolumeClaimRequest, reply *structs.CS
metricsStart := time.Now()
defer metrics.MeasureSince([]string{"nomad", "volume", "claim"}, metricsStart)
if !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityCSIAccess) {
if !allowCSIAccess(aclObj, args.RequestNamespace()) {
return structs.ErrPermissionDenied
}
......@@ -309,12 +319,13 @@ func (v *CSIPlugin) List(args *structs.CSIPluginListRequest, reply *structs.CSIP
return err
}
aclObj, err := v.srv.QueryACLObj(&args.QueryOptions)
allowCSIAccess := acl.NamespaceValidator(acl.NamespaceCapabilityCSIAccess)
aclObj, err := v.srv.QueryACLObj(&args.QueryOptions, false)
if err != nil {
return err
}
if !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityCSIAccess) {
if !allowCSIAccess(aclObj, args.RequestNamespace()) {
return structs.ErrPermissionDenied
}
......@@ -358,12 +369,13 @@ func (v *CSIPlugin) Get(args *structs.CSIPluginGetRequest, reply *structs.CSIPlu
return err
}
aclObj, err := v.srv.QueryACLObj(&args.QueryOptions)
allowCSIAccess := acl.NamespaceValidator(acl.NamespaceCapabilityCSIAccess)
aclObj, err := v.srv.QueryACLObj(&args.QueryOptions, false)
if err != nil {
return err
}
if !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityCSIAccess) {
if !allowCSIAccess(aclObj, args.RequestNamespace()) {
return structs.ErrPermissionDenied
}
......
......@@ -23,6 +23,49 @@ func TestCSIVolumeEndpoint_Get(t *testing.T) {
ns := structs.DefaultNamespace
state := srv.fsm.State()
codec := rpcClient(t, srv)
id0 := uuid.Generate()
// Create the volume
vols := []*structs.CSIVolume{{
ID: id0,
Namespace: ns,
AccessMode: structs.CSIVolumeAccessModeMultiNodeSingleWriter,
AttachmentMode: structs.CSIVolumeAttachmentModeFilesystem,
PluginID: "minnie",
}}
err := state.CSIVolumeRegister(999, vols)
require.NoError(t, err)
// Create the register request
req := &structs.CSIVolumeGetRequest{
ID: id0,
QueryOptions: structs.QueryOptions{
Region: "global",
Namespace: ns,
},
}
var resp structs.CSIVolumeGetResponse
err = msgpackrpc.CallWithCodec(codec, "CSIVolume.Get", req, &resp)
require.NoError(t, err)
require.Equal(t, uint64(999), resp.Index)
require.Equal(t, vols[0].ID, resp.Volume.ID)
}
func TestCSIVolumeEndpoint_Get_ACL(t *testing.T) {
t.Parallel()
srv, shutdown := TestServer(t, func(c *Config) {
c.NumSchedulers = 0 // Prevent automatic dequeue
})
defer shutdown()
testutil.WaitForLeader(t, srv.RPC)
ns := structs.DefaultNamespace
state := srv.fsm.State()
state.BootstrapACLTokens(1, 0, mock.ACLManagementToken())
srv.config.ACLEnabled = true
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment