github.com/ilhicas/nomad@v0.11.8/acl/policy.go (about) 1 package acl 2 3 import ( 4 "fmt" 5 "regexp" 6 7 "github.com/hashicorp/hcl" 8 ) 9 10 const ( 11 // The following levels are the only valid values for the `policy = "read"` stanza. 12 // When policies are merged together, the most privilege is granted, except for deny 13 // which always takes precedence and supersedes. 14 PolicyDeny = "deny" 15 PolicyRead = "read" 16 PolicyList = "list" 17 PolicyWrite = "write" 18 PolicyScale = "scale" 19 ) 20 21 const ( 22 // The following are the fine-grained capabilities that can be granted within a namespace. 23 // The Policy stanza is a short hand for granting several of these. When capabilities are 24 // combined we take the union of all capabilities. If the deny capability is present, it 25 // takes precedence and overwrites all other capabilities. 26 27 NamespaceCapabilityDeny = "deny" 28 NamespaceCapabilityListJobs = "list-jobs" 29 NamespaceCapabilityReadJob = "read-job" 30 NamespaceCapabilitySubmitJob = "submit-job" 31 NamespaceCapabilityDispatchJob = "dispatch-job" 32 NamespaceCapabilityReadLogs = "read-logs" 33 NamespaceCapabilityReadFS = "read-fs" 34 NamespaceCapabilityAllocExec = "alloc-exec" 35 NamespaceCapabilityAllocNodeExec = "alloc-node-exec" 36 NamespaceCapabilityAllocLifecycle = "alloc-lifecycle" 37 NamespaceCapabilitySentinelOverride = "sentinel-override" 38 NamespaceCapabilityCSIRegisterPlugin = "csi-register-plugin" 39 NamespaceCapabilityCSIWriteVolume = "csi-write-volume" 40 NamespaceCapabilityCSIReadVolume = "csi-read-volume" 41 NamespaceCapabilityCSIListVolume = "csi-list-volume" 42 NamespaceCapabilityCSIMountVolume = "csi-mount-volume" 43 NamespaceCapabilityListScalingPolicies = "list-scaling-policies" 44 NamespaceCapabilityReadScalingPolicy = "read-scaling-policy" 45 NamespaceCapabilityReadJobScaling = "read-job-scaling" 46 NamespaceCapabilityScaleJob = "scale-job" 47 ) 48 49 var ( 50 validNamespace = regexp.MustCompile("^[a-zA-Z0-9-*]{1,128}$") 51 ) 52 53 const ( 54 // The following are the fine-grained capabilities that can be granted for a volume set. 55 // The Policy stanza is a short hand for granting several of these. When capabilities are 56 // combined we take the union of all capabilities. If the deny capability is present, it 57 // takes precedence and overwrites all other capabilities. 58 59 HostVolumeCapabilityDeny = "deny" 60 HostVolumeCapabilityMountReadOnly = "mount-readonly" 61 HostVolumeCapabilityMountReadWrite = "mount-readwrite" 62 ) 63 64 var ( 65 validVolume = regexp.MustCompile("^[a-zA-Z0-9-*]{1,128}$") 66 ) 67 68 // Policy represents a parsed HCL or JSON policy. 69 type Policy struct { 70 Namespaces []*NamespacePolicy `hcl:"namespace,expand"` 71 HostVolumes []*HostVolumePolicy `hcl:"host_volume,expand"` 72 Agent *AgentPolicy `hcl:"agent"` 73 Node *NodePolicy `hcl:"node"` 74 Operator *OperatorPolicy `hcl:"operator"` 75 Quota *QuotaPolicy `hcl:"quota"` 76 Plugin *PluginPolicy `hcl:"plugin"` 77 Raw string `hcl:"-"` 78 } 79 80 // IsEmpty checks to make sure that at least one policy has been set and is not 81 // comprised of only a raw policy. 82 func (p *Policy) IsEmpty() bool { 83 return len(p.Namespaces) == 0 && 84 len(p.HostVolumes) == 0 && 85 p.Agent == nil && 86 p.Node == nil && 87 p.Operator == nil && 88 p.Quota == nil && 89 p.Plugin == nil 90 } 91 92 // NamespacePolicy is the policy for a specific namespace 93 type NamespacePolicy struct { 94 Name string `hcl:",key"` 95 Policy string 96 Capabilities []string 97 } 98 99 // HostVolumePolicy is the policy for a specific named host volume 100 type HostVolumePolicy struct { 101 Name string `hcl:",key"` 102 Policy string 103 Capabilities []string 104 } 105 106 type AgentPolicy struct { 107 Policy string 108 } 109 110 type NodePolicy struct { 111 Policy string 112 } 113 114 type OperatorPolicy struct { 115 Policy string 116 } 117 118 type QuotaPolicy struct { 119 Policy string 120 } 121 122 type PluginPolicy struct { 123 Policy string 124 } 125 126 // isPolicyValid makes sure the given string matches one of the valid policies. 127 func isPolicyValid(policy string) bool { 128 switch policy { 129 case PolicyDeny, PolicyRead, PolicyWrite, PolicyScale: 130 return true 131 default: 132 return false 133 } 134 } 135 136 func (p *PluginPolicy) isValid() bool { 137 switch p.Policy { 138 case PolicyDeny, PolicyRead, PolicyList: 139 return true 140 default: 141 return false 142 } 143 } 144 145 // isNamespaceCapabilityValid ensures the given capability is valid for a namespace policy 146 func isNamespaceCapabilityValid(cap string) bool { 147 switch cap { 148 case NamespaceCapabilityDeny, NamespaceCapabilityListJobs, NamespaceCapabilityReadJob, 149 NamespaceCapabilitySubmitJob, NamespaceCapabilityDispatchJob, NamespaceCapabilityReadLogs, 150 NamespaceCapabilityReadFS, NamespaceCapabilityAllocLifecycle, 151 NamespaceCapabilityAllocExec, NamespaceCapabilityAllocNodeExec, 152 NamespaceCapabilityCSIReadVolume, NamespaceCapabilityCSIWriteVolume, NamespaceCapabilityCSIListVolume, NamespaceCapabilityCSIMountVolume, NamespaceCapabilityCSIRegisterPlugin, 153 NamespaceCapabilityListScalingPolicies, NamespaceCapabilityReadScalingPolicy, NamespaceCapabilityReadJobScaling, NamespaceCapabilityScaleJob: 154 return true 155 // Separate the enterprise-only capabilities 156 case NamespaceCapabilitySentinelOverride: 157 return true 158 default: 159 return false 160 } 161 } 162 163 // expandNamespacePolicy provides the equivalent set of capabilities for 164 // a namespace policy 165 func expandNamespacePolicy(policy string) []string { 166 read := []string{ 167 NamespaceCapabilityListJobs, 168 NamespaceCapabilityReadJob, 169 NamespaceCapabilityCSIListVolume, 170 NamespaceCapabilityCSIReadVolume, 171 NamespaceCapabilityReadJobScaling, 172 NamespaceCapabilityListScalingPolicies, 173 NamespaceCapabilityReadScalingPolicy, 174 } 175 176 write := append(read, []string{ 177 NamespaceCapabilityScaleJob, 178 NamespaceCapabilitySubmitJob, 179 NamespaceCapabilityDispatchJob, 180 NamespaceCapabilityReadLogs, 181 NamespaceCapabilityReadFS, 182 NamespaceCapabilityAllocExec, 183 NamespaceCapabilityAllocLifecycle, 184 NamespaceCapabilityCSIMountVolume, 185 NamespaceCapabilityCSIWriteVolume, 186 }...) 187 188 switch policy { 189 case PolicyDeny: 190 return []string{NamespaceCapabilityDeny} 191 case PolicyRead: 192 return read 193 case PolicyWrite: 194 return write 195 case PolicyScale: 196 return []string{ 197 NamespaceCapabilityListScalingPolicies, 198 NamespaceCapabilityReadScalingPolicy, 199 NamespaceCapabilityReadJobScaling, 200 NamespaceCapabilityScaleJob, 201 } 202 default: 203 return nil 204 } 205 } 206 207 func isHostVolumeCapabilityValid(cap string) bool { 208 switch cap { 209 case HostVolumeCapabilityDeny, HostVolumeCapabilityMountReadOnly, HostVolumeCapabilityMountReadWrite: 210 return true 211 default: 212 return false 213 } 214 } 215 216 func expandHostVolumePolicy(policy string) []string { 217 switch policy { 218 case PolicyDeny: 219 return []string{HostVolumeCapabilityDeny} 220 case PolicyRead: 221 return []string{HostVolumeCapabilityMountReadOnly} 222 case PolicyWrite: 223 return []string{HostVolumeCapabilityMountReadOnly, HostVolumeCapabilityMountReadWrite} 224 default: 225 return nil 226 } 227 } 228 229 // Parse is used to parse the specified ACL rules into an 230 // intermediary set of policies, before being compiled into 231 // the ACL 232 func Parse(rules string) (*Policy, error) { 233 // Decode the rules 234 p := &Policy{Raw: rules} 235 if rules == "" { 236 // Hot path for empty rules 237 return p, nil 238 } 239 240 // Attempt to parse 241 if err := hcl.Decode(p, rules); err != nil { 242 return nil, fmt.Errorf("Failed to parse ACL Policy: %v", err) 243 } 244 245 // At least one valid policy must be specified, we don't want to store only 246 // raw data 247 if p.IsEmpty() { 248 return nil, fmt.Errorf("Invalid policy: %s", p.Raw) 249 } 250 251 // Validate the policy 252 for _, ns := range p.Namespaces { 253 if !validNamespace.MatchString(ns.Name) { 254 return nil, fmt.Errorf("Invalid namespace name: %#v", ns) 255 } 256 if ns.Policy != "" && !isPolicyValid(ns.Policy) { 257 return nil, fmt.Errorf("Invalid namespace policy: %#v", ns) 258 } 259 for _, cap := range ns.Capabilities { 260 if !isNamespaceCapabilityValid(cap) { 261 return nil, fmt.Errorf("Invalid namespace capability '%s': %#v", cap, ns) 262 } 263 } 264 265 // Expand the short hand policy to the capabilities and 266 // add to any existing capabilities 267 if ns.Policy != "" { 268 extraCap := expandNamespacePolicy(ns.Policy) 269 ns.Capabilities = append(ns.Capabilities, extraCap...) 270 } 271 } 272 273 for _, hv := range p.HostVolumes { 274 if !validVolume.MatchString(hv.Name) { 275 return nil, fmt.Errorf("Invalid host volume name: %#v", hv) 276 } 277 if hv.Policy != "" && !isPolicyValid(hv.Policy) { 278 return nil, fmt.Errorf("Invalid host volume policy: %#v", hv) 279 } 280 for _, cap := range hv.Capabilities { 281 if !isHostVolumeCapabilityValid(cap) { 282 return nil, fmt.Errorf("Invalid host volume capability '%s': %#v", cap, hv) 283 } 284 } 285 286 // Expand the short hand policy to the capabilities and 287 // add to any existing capabilities 288 if hv.Policy != "" { 289 extraCap := expandHostVolumePolicy(hv.Policy) 290 hv.Capabilities = append(hv.Capabilities, extraCap...) 291 } 292 } 293 294 if p.Agent != nil && !isPolicyValid(p.Agent.Policy) { 295 return nil, fmt.Errorf("Invalid agent policy: %#v", p.Agent) 296 } 297 298 if p.Node != nil && !isPolicyValid(p.Node.Policy) { 299 return nil, fmt.Errorf("Invalid node policy: %#v", p.Node) 300 } 301 302 if p.Operator != nil && !isPolicyValid(p.Operator.Policy) { 303 return nil, fmt.Errorf("Invalid operator policy: %#v", p.Operator) 304 } 305 306 if p.Quota != nil && !isPolicyValid(p.Quota.Policy) { 307 return nil, fmt.Errorf("Invalid quota policy: %#v", p.Quota) 308 } 309 310 if p.Plugin != nil && !p.Plugin.isValid() { 311 return nil, fmt.Errorf("Invalid plugin policy: %#v", p.Plugin) 312 } 313 return p, nil 314 }