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