acls: Break mount acl into mount-rw and mount-ro

This commit is contained in:
Danielle Lancashire 2019-08-21 20:13:16 +02:00
parent f69723653f
commit 91bb67f713
No known key found for this signature in database
GPG Key ID: 8D65584EF3DDF91B
4 changed files with 66 additions and 18 deletions

View File

@ -45,8 +45,9 @@ const (
// combined we take the union of all capabilities. If the deny capability is present, it
// takes precedence and overwrites all other capabilities.
HostVolumeCapabilityDeny = "deny"
HostVolumeCapabilityMount = "mount"
HostVolumeCapabilityDeny = "deny"
HostVolumeCapabilityMountReadOnly = "mount-readonly"
HostVolumeCapabilityMountReadWrite = "mount-readwrite"
)
var (
@ -160,7 +161,7 @@ func expandNamespacePolicy(policy string) []string {
func isHostVolumeCapabilityValid(cap string) bool {
switch cap {
case HostVolumeCapabilityDeny, HostVolumeCapabilityMount:
case HostVolumeCapabilityDeny, HostVolumeCapabilityMountReadOnly, HostVolumeCapabilityMountReadWrite:
return true
default:
return false
@ -172,9 +173,9 @@ func expandHostVolumePolicy(policy string) []string {
case PolicyDeny:
return []string{HostVolumeCapabilityDeny}
case PolicyRead:
return []string{HostVolumeCapabilityDeny}
return []string{HostVolumeCapabilityMountReadOnly}
case PolicyWrite:
return []string{HostVolumeCapabilityMount}
return []string{HostVolumeCapabilityMountReadOnly, HostVolumeCapabilityMountReadWrite}
default:
return nil
}

View File

@ -202,7 +202,7 @@ func TestParse(t *testing.T) {
{
`
host_volume "production-tls-*" {
capabilities = ["mount"]
capabilities = ["mount-readonly"]
}
`,
"",
@ -212,7 +212,26 @@ func TestParse(t *testing.T) {
Name: "production-tls-*",
Policy: "",
Capabilities: []string{
HostVolumeCapabilityMount,
HostVolumeCapabilityMountReadOnly,
},
},
},
},
},
{
`
host_volume "production-tls-*" {
capabilities = ["mount-readwrite"]
}
`,
"",
&Policy{
HostVolumes: []*HostVolumePolicy{
{
Name: "production-tls-*",
Policy: "",
Capabilities: []string{
HostVolumeCapabilityMountReadWrite,
},
},
},
@ -221,7 +240,7 @@ func TestParse(t *testing.T) {
{
`
host_volume "volume has a space" {
capabilities = ["mount"]
capabilities = ["mount-readwrite"]
}
`,
"Invalid host volume name",

View File

@ -117,8 +117,18 @@ func (j *Job) Register(args *structs.JobRegisterRequest, reply *structs.JobRegis
return structs.ErrPermissionDenied
}
if !aclObj.AllowHostVolumeOperation(cfg.Source, acl.HostVolumeCapabilityMount) {
return structs.ErrPermissionDenied
// If a volume is readonly, then we allow access if the user has ReadOnly
// or ReadWrite access to the volume. Otherwise we only allow access if
// they have ReadWrite access.
if vol.ReadOnly {
if !aclObj.AllowHostVolumeOperation(cfg.Source, acl.HostVolumeCapabilityMountReadOnly) &&
!aclObj.AllowHostVolumeOperation(cfg.Source, acl.HostVolumeCapabilityMountReadWrite) {
return structs.ErrPermissionDenied
}
} else {
if !aclObj.AllowHostVolumeOperation(cfg.Source, acl.HostVolumeCapabilityMountReadWrite) {
return structs.ErrPermissionDenied
}
}
}
}

View File

@ -185,7 +185,7 @@ func TestJobEndpoint_Register_ACL(t *testing.T) {
defer s1.Shutdown()
testutil.WaitForLeader(t, s1.RPC)
newVolumeJob := func() *structs.Job {
newVolumeJob := func(readonlyVolume bool) *structs.Job {
j := mock.Job()
tg := j.TaskGroups[0]
tg.Volumes = map[string]*structs.VolumeRequest{
@ -194,6 +194,7 @@ func TestJobEndpoint_Register_ACL(t *testing.T) {
Config: map[string]interface{}{
"source": "prod-ca-certs",
},
ReadOnly: readonlyVolume,
},
}
@ -201,7 +202,8 @@ func TestJobEndpoint_Register_ACL(t *testing.T) {
{
Volume: "ca-certs",
Destination: "/etc/ca-certificates",
ReadOnly: true,
// Task readonly does not effect acls
ReadOnly: true,
},
}
@ -212,9 +214,13 @@ func TestJobEndpoint_Register_ACL(t *testing.T) {
submitJobToken := mock.CreatePolicyAndToken(t, s1.State(), 1001, "test-submit-job", submitJobPolicy)
volumesPolicy := mock.HostVolumePolicy("prod-*", "", []string{acl.HostVolumeCapabilityMount})
volumesPolicyReadWrite := mock.HostVolumePolicy("prod-*", "", []string{acl.HostVolumeCapabilityMountReadWrite})
submitJobWithVolumesToken := mock.CreatePolicyAndToken(t, s1.State(), 1002, "test-submit-volumes", submitJobPolicy+"\n"+volumesPolicy)
submitJobWithVolumesReadWriteToken := mock.CreatePolicyAndToken(t, s1.State(), 1002, "test-submit-volumes", submitJobPolicy+"\n"+volumesPolicyReadWrite)
volumesPolicyReadOnly := mock.HostVolumePolicy("prod-*", "", []string{acl.HostVolumeCapabilityMountReadOnly})
submitJobWithVolumesReadOnlyToken := mock.CreatePolicyAndToken(t, s1.State(), 1003, "test-submit-volumes-readonly", submitJobPolicy+"\n"+volumesPolicyReadOnly)
cases := []struct {
Name string
@ -235,15 +241,27 @@ func TestJobEndpoint_Register_ACL(t *testing.T) {
ErrExpected: false,
},
{
Name: "with a token that can submit a job, but not use a required volumes",
Job: newVolumeJob(),
Name: "with a token that can submit a job, but not use a required volume",
Job: newVolumeJob(false),
Token: submitJobToken.SecretID,
ErrExpected: true,
},
{
Name: "with a token that can submit a job, and use all required volumes",
Job: newVolumeJob(),
Token: submitJobWithVolumesToken.SecretID,
Job: newVolumeJob(false),
Token: submitJobWithVolumesReadWriteToken.SecretID,
ErrExpected: false,
},
{
Name: "with a token that can submit a job, but only has readonly access",
Job: newVolumeJob(false),
Token: submitJobWithVolumesReadOnlyToken.SecretID,
ErrExpected: true,
},
{
Name: "with a token that can submit a job, and readonly volume access is enough",
Job: newVolumeJob(true),
Token: submitJobWithVolumesReadOnlyToken.SecretID,
ErrExpected: false,
},
}