open-nomad/acl/policy.go

194 lines
5.3 KiB
Go
Raw Normal View History

2017-08-04 00:41:33 +00:00
package acl
import (
"fmt"
"regexp"
2017-08-04 00:41:33 +00:00
"github.com/hashicorp/hcl"
)
const (
2017-08-08 04:09:13 +00:00
// The following levels are the only valid values for the `policy = "read"` stanza.
// When policies are merged together, the most privilege is granted, except for deny
// which always takes precedence and supercedes.
2017-08-04 00:41:33 +00:00
PolicyDeny = "deny"
PolicyRead = "read"
PolicyWrite = "write"
)
const (
2017-08-08 04:09:13 +00:00
// The following are the fine-grained capabilities that can be granted within a namespace.
// The Policy stanza is a short hand for granting several of these. When capabilities are
// combined we take the union of all capabilities. If the deny capability is present, it
// takes precedence and overwrites all other capabilities.
2017-09-19 14:47:10 +00:00
NamespaceCapabilityDeny = "deny"
NamespaceCapabilityListJobs = "list-jobs"
NamespaceCapabilityReadJob = "read-job"
NamespaceCapabilitySubmitJob = "submit-job"
NamespaceCapabilityDispatchJob = "dispatch-job"
2017-09-19 14:47:10 +00:00
NamespaceCapabilityReadLogs = "read-logs"
NamespaceCapabilityReadFS = "read-fs"
NamespaceCapabilityAllocLifecycle = "alloc-lifecycle"
2017-09-19 14:47:10 +00:00
NamespaceCapabilitySentinelOverride = "sentinel-override"
2017-08-04 00:41:33 +00:00
)
var (
validNamespace = regexp.MustCompile("^[a-zA-Z0-9-*]{1,128}$")
)
2017-08-04 00:41:33 +00:00
// Policy represents a parsed HCL or JSON policy.
type Policy struct {
Namespaces []*NamespacePolicy `hcl:"namespace,expand"`
Agent *AgentPolicy `hcl:"agent"`
Node *NodePolicy `hcl:"node"`
Operator *OperatorPolicy `hcl:"operator"`
2017-10-13 21:36:02 +00:00
Quota *QuotaPolicy `hcl:"quota"`
2017-08-04 00:41:33 +00:00
Raw string `hcl:"-"`
}
2017-10-17 21:15:11 +00:00
// IsEmpty checks to make sure that at least one policy has been set and is not
// comprised of only a raw policy.
func (p *Policy) IsEmpty() bool {
return len(p.Namespaces) == 0 &&
p.Agent == nil &&
p.Node == nil &&
p.Operator == nil &&
p.Quota == nil
}
2017-08-04 00:41:33 +00:00
// NamespacePolicy is the policy for a specific namespace
type NamespacePolicy struct {
Name string `hcl:",key"`
Policy string
Capabilities []string
}
type AgentPolicy struct {
Policy string
}
type NodePolicy struct {
Policy string
}
type OperatorPolicy struct {
Policy string
}
2017-10-13 21:36:02 +00:00
type QuotaPolicy struct {
Policy string
}
2017-08-04 00:41:33 +00:00
// isPolicyValid makes sure the given string matches one of the valid policies.
func isPolicyValid(policy string) bool {
switch policy {
2017-08-08 04:09:13 +00:00
case PolicyDeny, PolicyRead, PolicyWrite:
2017-08-04 00:41:33 +00:00
return true
default:
return false
}
}
// isNamespaceCapabilityValid ensures the given capability is valid for a namespace policy
func isNamespaceCapabilityValid(cap string) bool {
switch cap {
2017-08-08 04:09:13 +00:00
case NamespaceCapabilityDeny, NamespaceCapabilityListJobs, NamespaceCapabilityReadJob,
NamespaceCapabilitySubmitJob, NamespaceCapabilityDispatchJob, NamespaceCapabilityReadLogs,
NamespaceCapabilityReadFS, NamespaceCapabilityAllocLifecycle:
2017-08-04 00:41:33 +00:00
return true
2017-09-26 22:26:33 +00:00
// Separate the enterprise-only capabilities
2017-09-19 14:47:10 +00:00
case NamespaceCapabilitySentinelOverride:
return true
2017-08-04 00:41:33 +00:00
default:
return false
}
}
// expandNamespacePolicy provides the equivalent set of capabilities for
// a namespace policy
func expandNamespacePolicy(policy string) []string {
switch policy {
case PolicyDeny:
return []string{NamespaceCapabilityDeny}
case PolicyRead:
return []string{
NamespaceCapabilityListJobs,
NamespaceCapabilityReadJob,
}
case PolicyWrite:
return []string{
NamespaceCapabilityListJobs,
NamespaceCapabilityReadJob,
NamespaceCapabilitySubmitJob,
NamespaceCapabilityDispatchJob,
2017-08-04 00:41:33 +00:00
NamespaceCapabilityReadLogs,
NamespaceCapabilityReadFS,
NamespaceCapabilityAllocLifecycle,
2017-08-04 00:41:33 +00:00
}
default:
return nil
}
}
// Parse is used to parse the specified ACL rules into an
// intermediary set of policies, before being compiled into
// the ACL
func Parse(rules string) (*Policy, error) {
// Decode the rules
p := &Policy{Raw: rules}
if rules == "" {
// Hot path for empty rules
return p, nil
}
// Attempt to parse
if err := hcl.Decode(p, rules); err != nil {
return nil, fmt.Errorf("Failed to parse ACL Policy: %v", err)
}
// At least one valid policy must be specified, we don't want to store only
// raw data
2017-10-17 21:15:11 +00:00
if p.IsEmpty() {
return nil, fmt.Errorf("Invalid policy: %s", p.Raw)
}
2017-08-04 00:41:33 +00:00
// Validate the policy
for _, ns := range p.Namespaces {
if !validNamespace.MatchString(ns.Name) {
return nil, fmt.Errorf("Invalid namespace name: %#v", ns)
}
2017-08-04 00:41:33 +00:00
if ns.Policy != "" && !isPolicyValid(ns.Policy) {
return nil, fmt.Errorf("Invalid namespace policy: %#v", ns)
}
for _, cap := range ns.Capabilities {
if !isNamespaceCapabilityValid(cap) {
2017-08-08 04:09:13 +00:00
return nil, fmt.Errorf("Invalid namespace capability '%s': %#v", cap, ns)
2017-08-04 00:41:33 +00:00
}
}
// Expand the short hand policy to the capabilities and
// add to any existing capabilities
if ns.Policy != "" {
extraCap := expandNamespacePolicy(ns.Policy)
ns.Capabilities = append(ns.Capabilities, extraCap...)
}
}
if p.Agent != nil && !isPolicyValid(p.Agent.Policy) {
return nil, fmt.Errorf("Invalid agent policy: %#v", p.Agent)
}
if p.Node != nil && !isPolicyValid(p.Node.Policy) {
return nil, fmt.Errorf("Invalid node policy: %#v", p.Node)
}
if p.Operator != nil && !isPolicyValid(p.Operator.Policy) {
return nil, fmt.Errorf("Invalid operator policy: %#v", p.Operator)
}
2017-10-13 21:36:02 +00:00
if p.Quota != nil && !isPolicyValid(p.Quota.Policy) {
return nil, fmt.Errorf("Invalid quota policy: %#v", p.Quota)
}
2017-08-04 00:41:33 +00:00
return p, nil
}