open-consul/acl/policy.go

350 lines
9.4 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package acl
import (
"fmt"
"strings"
)
const (
PolicyDeny = "deny"
PolicyRead = "read"
PolicyList = "list"
PolicyWrite = "write"
)
type AccessLevel int
const (
AccessUnknown AccessLevel = iota
AccessDeny
AccessRead
AccessList
AccessWrite
)
func (l AccessLevel) String() string {
switch l {
case AccessDeny:
return PolicyDeny
case AccessRead:
return PolicyRead
case AccessList:
return PolicyList
case AccessWrite:
return PolicyWrite
default:
return "unknown"
}
}
func AccessLevelFromString(level string) (AccessLevel, error) {
switch strings.ToLower(level) {
case PolicyDeny:
return AccessDeny, nil
case PolicyRead:
return AccessRead, nil
case PolicyList:
return AccessList, nil
case PolicyWrite:
return AccessWrite, nil
default:
return AccessUnknown, fmt.Errorf("%q is not a valid access level", level)
}
}
type PolicyRules struct {
ACL string `hcl:"acl,expand"`
Agents []*AgentRule `hcl:"agent,expand"`
AgentPrefixes []*AgentRule `hcl:"agent_prefix,expand"`
Keys []*KeyRule `hcl:"key,expand"`
KeyPrefixes []*KeyRule `hcl:"key_prefix,expand"`
Nodes []*NodeRule `hcl:"node,expand"`
NodePrefixes []*NodeRule `hcl:"node_prefix,expand"`
Services []*ServiceRule `hcl:"service,expand"`
ServicePrefixes []*ServiceRule `hcl:"service_prefix,expand"`
Sessions []*SessionRule `hcl:"session,expand"`
SessionPrefixes []*SessionRule `hcl:"session_prefix,expand"`
Events []*EventRule `hcl:"event,expand"`
EventPrefixes []*EventRule `hcl:"event_prefix,expand"`
PreparedQueries []*PreparedQueryRule `hcl:"query,expand"`
PreparedQueryPrefixes []*PreparedQueryRule `hcl:"query_prefix,expand"`
Keyring string `hcl:"keyring"`
Operator string `hcl:"operator"`
Mesh string `hcl:"mesh"`
Peering string `hcl:"peering"`
}
// Policy is used to represent the policy specified by an ACL configuration.
type Policy struct {
PolicyRules `hcl:",squash"`
EnterprisePolicyRules `hcl:",squash"`
}
// AgentRule represents a rule for working with agent endpoints on nodes
// with specific name prefixes.
type AgentRule struct {
Node string `hcl:",key"`
Policy string
}
// KeyRule represents a rule for a key
type KeyRule struct {
Prefix string `hcl:",key"`
Policy string
EnterpriseRule `hcl:",squash"`
}
// NodeRule represents a rule for a node
type NodeRule struct {
Name string `hcl:",key"`
Policy string
EnterpriseRule `hcl:",squash"`
}
// ServiceRule represents a policy for a service
type ServiceRule struct {
Name string `hcl:",key"`
Policy string
// Intentions is the policy for intentions where this service is the
// destination. This may be empty, in which case the Policy determines
// the intentions policy.
Intentions string
EnterpriseRule `hcl:",squash"`
}
// SessionRule represents a rule for making sessions tied to specific node
// name prefixes.
type SessionRule struct {
Node string `hcl:",key"`
Policy string
}
// EventRule represents a user event rule.
type EventRule struct {
Event string `hcl:",key"`
Policy string
}
// PreparedQueryRule represents a prepared query rule.
type PreparedQueryRule struct {
Prefix string `hcl:",key"`
Policy string
}
// isPolicyValid makes sure the given string matches one of the valid policies.
func isPolicyValid(policy string, allowList bool) bool {
access, err := AccessLevelFromString(policy)
if err != nil {
return false
}
if access == AccessList && !allowList {
return false
}
return true
}
func (pr *PolicyRules) Validate(conf *Config) error {
// Validate the acl policy - this one is allowed to be empty
if pr.ACL != "" && !isPolicyValid(pr.ACL, false) {
return fmt.Errorf("Invalid acl policy: %#v", pr.ACL)
}
// Validate the agent policy
for _, ap := range pr.Agents {
if !isPolicyValid(ap.Policy, false) {
return fmt.Errorf("Invalid agent policy: %#v", ap)
}
}
for _, ap := range pr.AgentPrefixes {
if !isPolicyValid(ap.Policy, false) {
return fmt.Errorf("Invalid agent_prefix policy: %#v", ap)
}
}
// Validate the key policy
for _, kp := range pr.Keys {
if !isPolicyValid(kp.Policy, true) {
return fmt.Errorf("Invalid key policy: %#v", kp)
}
if err := kp.EnterpriseRule.Validate(kp.Policy, conf); err != nil {
return fmt.Errorf("Invalid key enterprise policy: %#v, got error: %v", kp, err)
}
}
for _, kp := range pr.KeyPrefixes {
if !isPolicyValid(kp.Policy, true) {
return fmt.Errorf("Invalid key_prefix policy: %#v", kp)
}
if err := kp.EnterpriseRule.Validate(kp.Policy, conf); err != nil {
return fmt.Errorf("Invalid key_prefix enterprise policy: %#v, got error: %v", kp, err)
}
}
// Validate the node policies
for _, np := range pr.Nodes {
if !isPolicyValid(np.Policy, false) {
return fmt.Errorf("Invalid node policy: %#v", np)
}
if err := np.EnterpriseRule.Validate(np.Policy, conf); err != nil {
return fmt.Errorf("Invalid node enterprise policy: %#v, got error: %v", np, err)
}
}
for _, np := range pr.NodePrefixes {
if !isPolicyValid(np.Policy, false) {
return fmt.Errorf("Invalid node_prefix policy: %#v", np)
}
if err := np.EnterpriseRule.Validate(np.Policy, conf); err != nil {
return fmt.Errorf("Invalid node_prefix enterprise policy: %#v, got error: %v", np, err)
}
}
// Validate the service policies
for _, sp := range pr.Services {
if !isPolicyValid(sp.Policy, false) {
return fmt.Errorf("Invalid service policy: %#v", sp)
}
if sp.Intentions != "" && !isPolicyValid(sp.Intentions, false) {
return fmt.Errorf("Invalid service intentions policy: %#v", sp)
}
if err := sp.EnterpriseRule.Validate(sp.Policy, conf); err != nil {
return fmt.Errorf("Invalid service enterprise policy: %#v, got error: %v", sp, err)
}
}
for _, sp := range pr.ServicePrefixes {
if !isPolicyValid(sp.Policy, false) {
return fmt.Errorf("Invalid service_prefix policy: %#v", sp)
}
if sp.Intentions != "" && !isPolicyValid(sp.Intentions, false) {
return fmt.Errorf("Invalid service_prefix intentions policy: %#v", sp)
}
if err := sp.EnterpriseRule.Validate(sp.Policy, conf); err != nil {
return fmt.Errorf("Invalid service_prefix enterprise policy: %#v, got error: %v", sp, err)
}
}
// Validate the session policies
for _, sp := range pr.Sessions {
if !isPolicyValid(sp.Policy, false) {
return fmt.Errorf("Invalid session policy: %#v", sp)
}
}
for _, sp := range pr.SessionPrefixes {
if !isPolicyValid(sp.Policy, false) {
return fmt.Errorf("Invalid session_prefix policy: %#v", sp)
}
}
// Validate the user event policies
for _, ep := range pr.Events {
if !isPolicyValid(ep.Policy, false) {
return fmt.Errorf("Invalid event policy: %#v", ep)
}
}
for _, ep := range pr.EventPrefixes {
if !isPolicyValid(ep.Policy, false) {
return fmt.Errorf("Invalid event_prefix policy: %#v", ep)
}
}
// Validate the prepared query policies
for _, pq := range pr.PreparedQueries {
if !isPolicyValid(pq.Policy, false) {
return fmt.Errorf("Invalid query policy: %#v", pq)
}
}
for _, pq := range pr.PreparedQueryPrefixes {
if !isPolicyValid(pq.Policy, false) {
return fmt.Errorf("Invalid query_prefix policy: %#v", pq)
}
}
// Validate the keyring policy - this one is allowed to be empty
if pr.Keyring != "" && !isPolicyValid(pr.Keyring, false) {
return fmt.Errorf("Invalid keyring policy: %#v", pr.Keyring)
}
// Validate the operator policy - this one is allowed to be empty
if pr.Operator != "" && !isPolicyValid(pr.Operator, false) {
return fmt.Errorf("Invalid operator policy: %#v", pr.Operator)
}
// Validate the mesh policy - this one is allowed to be empty
if pr.Mesh != "" && !isPolicyValid(pr.Mesh, false) {
return fmt.Errorf("Invalid mesh policy: %#v", pr.Mesh)
}
// Validate the peering policy - this one is allowed to be empty
if pr.Peering != "" && !isPolicyValid(pr.Peering, false) {
return fmt.Errorf("Invalid peering policy: %#v", pr.Peering)
}
return nil
}
func parse(rules string, conf *Config, meta *EnterprisePolicyMeta) (*Policy, error) {
p, err := decodeRules(rules, conf, meta)
if err != nil {
return nil, err
}
if err := p.PolicyRules.Validate(conf); err != nil {
return nil, err
}
if err := p.EnterprisePolicyRules.Validate(conf); err != nil {
return nil, err
}
return p, nil
}
// NewPolicyFromSource is used to parse the specified ACL rules into an
// intermediary set of policies, before being compiled into
// the ACL
func NewPolicyFromSource(rules string, conf *Config, meta *EnterprisePolicyMeta) (*Policy, error) {
if rules == "" {
// Hot path for empty source
return &Policy{}, nil
}
var policy *Policy
var err error
policy, err = parse(rules, conf, meta)
return policy, err
}
// takesPrecedenceOver returns true when permission a
// should take precedence over permission b
func takesPrecedenceOver(a, b string) bool {
if a == PolicyDeny {
return true
} else if b == PolicyDeny {
return false
}
if a == PolicyWrite {
return true
} else if b == PolicyWrite {
return false
}
if a == PolicyList {
return true
} else if b == PolicyList {
return false
}
if a == PolicyRead {
return true
} else if b == PolicyRead {
return false
}
return false
}