// 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 }