github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/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 NamespaceCapabilityDeny = "deny" 25 NamespaceCapabilityListJobs = "list-jobs" 26 NamespaceCapabilityReadJob = "read-job" 27 NamespaceCapabilitySubmitJob = "submit-job" 28 NamespaceCapabilityDispatchJob = "dispatch-job" 29 NamespaceCapabilityReadLogs = "read-logs" 30 NamespaceCapabilityReadFS = "read-fs" 31 NamespaceCapabilityAllocExec = "alloc-exec" 32 NamespaceCapabilityAllocNodeExec = "alloc-node-exec" 33 NamespaceCapabilityAllocLifecycle = "alloc-lifecycle" 34 NamespaceCapabilitySentinelOverride = "sentinel-override" 35 ) 36 37 var ( 38 validNamespace = regexp.MustCompile("^[a-zA-Z0-9-*]{1,128}$") 39 ) 40 41 // Policy represents a parsed HCL or JSON policy. 42 type Policy struct { 43 Namespaces []*NamespacePolicy `hcl:"namespace,expand"` 44 Agent *AgentPolicy `hcl:"agent"` 45 Node *NodePolicy `hcl:"node"` 46 Operator *OperatorPolicy `hcl:"operator"` 47 Quota *QuotaPolicy `hcl:"quota"` 48 Raw string `hcl:"-"` 49 } 50 51 // IsEmpty checks to make sure that at least one policy has been set and is not 52 // comprised of only a raw policy. 53 func (p *Policy) IsEmpty() bool { 54 return len(p.Namespaces) == 0 && 55 p.Agent == nil && 56 p.Node == nil && 57 p.Operator == nil && 58 p.Quota == nil 59 } 60 61 // NamespacePolicy is the policy for a specific namespace 62 type NamespacePolicy struct { 63 Name string `hcl:",key"` 64 Policy string 65 Capabilities []string 66 } 67 68 type AgentPolicy struct { 69 Policy string 70 } 71 72 type NodePolicy struct { 73 Policy string 74 } 75 76 type OperatorPolicy struct { 77 Policy string 78 } 79 80 type QuotaPolicy struct { 81 Policy string 82 } 83 84 // isPolicyValid makes sure the given string matches one of the valid policies. 85 func isPolicyValid(policy string) bool { 86 switch policy { 87 case PolicyDeny, PolicyRead, PolicyWrite: 88 return true 89 default: 90 return false 91 } 92 } 93 94 // isNamespaceCapabilityValid ensures the given capability is valid for a namespace policy 95 func isNamespaceCapabilityValid(cap string) bool { 96 switch cap { 97 case NamespaceCapabilityDeny, NamespaceCapabilityListJobs, NamespaceCapabilityReadJob, 98 NamespaceCapabilitySubmitJob, NamespaceCapabilityDispatchJob, NamespaceCapabilityReadLogs, 99 NamespaceCapabilityReadFS, NamespaceCapabilityAllocLifecycle, 100 NamespaceCapabilityAllocExec, NamespaceCapabilityAllocNodeExec: 101 return true 102 // Separate the enterprise-only capabilities 103 case NamespaceCapabilitySentinelOverride: 104 return true 105 default: 106 return false 107 } 108 } 109 110 // expandNamespacePolicy provides the equivalent set of capabilities for 111 // a namespace policy 112 func expandNamespacePolicy(policy string) []string { 113 switch policy { 114 case PolicyDeny: 115 return []string{NamespaceCapabilityDeny} 116 case PolicyRead: 117 return []string{ 118 NamespaceCapabilityListJobs, 119 NamespaceCapabilityReadJob, 120 } 121 case PolicyWrite: 122 return []string{ 123 NamespaceCapabilityListJobs, 124 NamespaceCapabilityReadJob, 125 NamespaceCapabilitySubmitJob, 126 NamespaceCapabilityDispatchJob, 127 NamespaceCapabilityReadLogs, 128 NamespaceCapabilityReadFS, 129 NamespaceCapabilityAllocExec, 130 NamespaceCapabilityAllocLifecycle, 131 } 132 default: 133 return nil 134 } 135 } 136 137 // Parse is used to parse the specified ACL rules into an 138 // intermediary set of policies, before being compiled into 139 // the ACL 140 func Parse(rules string) (*Policy, error) { 141 // Decode the rules 142 p := &Policy{Raw: rules} 143 if rules == "" { 144 // Hot path for empty rules 145 return p, nil 146 } 147 148 // Attempt to parse 149 if err := hcl.Decode(p, rules); err != nil { 150 return nil, fmt.Errorf("Failed to parse ACL Policy: %v", err) 151 } 152 153 // At least one valid policy must be specified, we don't want to store only 154 // raw data 155 if p.IsEmpty() { 156 return nil, fmt.Errorf("Invalid policy: %s", p.Raw) 157 } 158 159 // Validate the policy 160 for _, ns := range p.Namespaces { 161 if !validNamespace.MatchString(ns.Name) { 162 return nil, fmt.Errorf("Invalid namespace name: %#v", ns) 163 } 164 if ns.Policy != "" && !isPolicyValid(ns.Policy) { 165 return nil, fmt.Errorf("Invalid namespace policy: %#v", ns) 166 } 167 for _, cap := range ns.Capabilities { 168 if !isNamespaceCapabilityValid(cap) { 169 return nil, fmt.Errorf("Invalid namespace capability '%s': %#v", cap, ns) 170 } 171 } 172 173 // Expand the short hand policy to the capabilities and 174 // add to any existing capabilities 175 if ns.Policy != "" { 176 extraCap := expandNamespacePolicy(ns.Policy) 177 ns.Capabilities = append(ns.Capabilities, extraCap...) 178 } 179 } 180 181 if p.Agent != nil && !isPolicyValid(p.Agent.Policy) { 182 return nil, fmt.Errorf("Invalid agent policy: %#v", p.Agent) 183 } 184 185 if p.Node != nil && !isPolicyValid(p.Node.Policy) { 186 return nil, fmt.Errorf("Invalid node policy: %#v", p.Node) 187 } 188 189 if p.Operator != nil && !isPolicyValid(p.Operator.Policy) { 190 return nil, fmt.Errorf("Invalid operator policy: %#v", p.Operator) 191 } 192 193 if p.Quota != nil && !isPolicyValid(p.Quota.Policy) { 194 return nil, fmt.Errorf("Invalid quota policy: %#v", p.Quota) 195 } 196 return p, nil 197 }