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