Merge pull request #3488 from hashicorp/sentinel_oss

Introduce Code Policy validation via sentinel into ACLs, with a noop implementation
This commit is contained in:
preetapan 2017-09-25 13:49:20 -05:00 committed by GitHub
commit 70d7f8e71d
20 changed files with 373 additions and 155 deletions

View File

@ -2,6 +2,7 @@ package acl
import (
"github.com/armon/go-radix"
"github.com/hashicorp/consul/sentinel"
)
var (
@ -17,6 +18,10 @@ var (
manageAll ACL
)
// DefaultPolicyEnforcementLevel will be used if the user leaves the level
// blank when configuring an ACL.
const DefaultPolicyEnforcementLevel = "hard-mandatory"
func init() {
// Setup the singletons
allowAll = &StaticACL{
@ -59,7 +64,7 @@ type ACL interface {
KeyRead(string) bool
// KeyWrite checks for permission to write a given key
KeyWrite(string) bool
KeyWrite(string, sentinel.ScopeFn) bool
// KeyWritePrefix checks for permission to write to an
// entire key prefix. This means there must be no sub-policies
@ -78,7 +83,7 @@ type ACL interface {
// NodeWrite checks for permission to create or update (register) a
// given node.
NodeWrite(string) bool
NodeWrite(string, sentinel.ScopeFn) bool
// OperatorRead determines if the read-only Consul operator functions
// can be used.
@ -101,7 +106,7 @@ type ACL interface {
// ServiceWrite checks for permission to create or update a given
// service
ServiceWrite(string) bool
ServiceWrite(string, sentinel.ScopeFn) bool
// SessionRead checks for permission to read sessions for a given node.
SessionRead(string) bool
@ -150,7 +155,7 @@ func (s *StaticACL) KeyRead(string) bool {
return s.defaultAllow
}
func (s *StaticACL) KeyWrite(string) bool {
func (s *StaticACL) KeyWrite(string, sentinel.ScopeFn) bool {
return s.defaultAllow
}
@ -170,7 +175,7 @@ func (s *StaticACL) NodeRead(string) bool {
return s.defaultAllow
}
func (s *StaticACL) NodeWrite(string) bool {
func (s *StaticACL) NodeWrite(string, sentinel.ScopeFn) bool {
return s.defaultAllow
}
@ -194,7 +199,7 @@ func (s *StaticACL) ServiceRead(string) bool {
return s.defaultAllow
}
func (s *StaticACL) ServiceWrite(string) bool {
func (s *StaticACL) ServiceWrite(string, sentinel.ScopeFn) bool {
return s.defaultAllow
}
@ -239,6 +244,16 @@ func RootACL(id string) ACL {
}
}
// PolicyRule binds a regular ACL policy along with an optional piece of
// code to execute.
type PolicyRule struct {
// aclPolicy is used for simple acl rules(allow/deny/manage)
aclPolicy string
// sentinelPolicy has the code part of a policy
sentinelPolicy Sentinel
}
// PolicyACL is used to wrap a set of ACL policies to provide
// the ACL interface.
type PolicyACL struct {
@ -246,6 +261,10 @@ type PolicyACL struct {
// no matching rule.
parent ACL
// sentinel is an interface for validating and executing sentinel code
// policies.
sentinel sentinel.Evaluator
// agentRules contains the agent policies
agentRules *radix.Tree
@ -278,7 +297,7 @@ type PolicyACL struct {
// New is used to construct a policy based ACL from a set of policies
// and a parent policy to resolve missing cases.
func New(parent ACL, policy *Policy) (*PolicyACL, error) {
func New(parent ACL, policy *Policy, sentinel sentinel.Evaluator) (*PolicyACL, error) {
p := &PolicyACL{
parent: parent,
agentRules: radix.New(),
@ -288,6 +307,7 @@ func New(parent ACL, policy *Policy) (*PolicyACL, error) {
sessionRules: radix.New(),
eventRules: radix.New(),
preparedQueryRules: radix.New(),
sentinel: sentinel,
}
// Load the agent policy
@ -297,17 +317,29 @@ func New(parent ACL, policy *Policy) (*PolicyACL, error) {
// Load the key policy
for _, kp := range policy.Keys {
p.keyRules.Insert(kp.Prefix, kp.Policy)
policyRule := PolicyRule{
aclPolicy: kp.Policy,
sentinelPolicy: kp.Sentinel,
}
p.keyRules.Insert(kp.Prefix, policyRule)
}
// Load the node policy
for _, np := range policy.Nodes {
p.nodeRules.Insert(np.Name, np.Policy)
policyRule := PolicyRule{
aclPolicy: np.Policy,
sentinelPolicy: np.Sentinel,
}
p.nodeRules.Insert(np.Name, policyRule)
}
// Load the service policy
for _, sp := range policy.Services {
p.serviceRules.Insert(sp.Name, sp.Policy)
policyRule := PolicyRule{
aclPolicy: sp.Policy,
sentinelPolicy: sp.Sentinel,
}
p.serviceRules.Insert(sp.Name, policyRule)
}
// Load the session policy
@ -421,7 +453,8 @@ func (p *PolicyACL) KeyRead(key string) bool {
// Look for a matching rule
_, rule, ok := p.keyRules.LongestPrefix(key)
if ok {
switch rule.(string) {
pr := rule.(PolicyRule)
switch pr.aclPolicy {
case PolicyRead, PolicyWrite:
return true
default:
@ -434,27 +467,28 @@ func (p *PolicyACL) KeyRead(key string) bool {
}
// KeyWrite returns if a key is allowed to be written
func (p *PolicyACL) KeyWrite(key string) bool {
func (p *PolicyACL) KeyWrite(key string, scope sentinel.ScopeFn) bool {
// Look for a matching rule
_, rule, ok := p.keyRules.LongestPrefix(key)
if ok {
switch rule.(string) {
pr := rule.(PolicyRule)
switch pr.aclPolicy {
case PolicyWrite:
return true
return p.executeCodePolicy(&pr.sentinelPolicy, scope)
default:
return false
}
}
// No matching rule, use the parent.
return p.parent.KeyWrite(key)
return p.parent.KeyWrite(key, scope)
}
// KeyWritePrefix returns if a prefix is allowed to be written
func (p *PolicyACL) KeyWritePrefix(prefix string) bool {
// Look for a matching rule that denies
_, rule, ok := p.keyRules.LongestPrefix(prefix)
if ok && rule.(string) != PolicyWrite {
if ok && rule.(PolicyRule).aclPolicy != PolicyWrite {
return false
}
@ -462,7 +496,7 @@ func (p *PolicyACL) KeyWritePrefix(prefix string) bool {
deny := false
p.keyRules.WalkPrefix(prefix, func(path string, rule interface{}) bool {
// We have a rule to prevent a write in a sub-directory!
if rule.(string) != PolicyWrite {
if rule.(PolicyRule).aclPolicy != PolicyWrite {
deny = true
return true
}
@ -522,7 +556,8 @@ func (p *PolicyACL) NodeRead(name string) bool {
_, rule, ok := p.nodeRules.LongestPrefix(name)
if ok {
switch rule {
pr := rule.(PolicyRule)
switch pr.aclPolicy {
case PolicyRead, PolicyWrite:
return true
default:
@ -535,21 +570,22 @@ func (p *PolicyACL) NodeRead(name string) bool {
}
// NodeWrite checks if writing (registering) a node is allowed
func (p *PolicyACL) NodeWrite(name string) bool {
func (p *PolicyACL) NodeWrite(name string, scope sentinel.ScopeFn) bool {
// Check for an exact rule or catch-all
_, rule, ok := p.nodeRules.LongestPrefix(name)
if ok {
switch rule {
pr := rule.(PolicyRule)
switch pr.aclPolicy {
case PolicyWrite:
return true
return p.executeCodePolicy(&pr.sentinelPolicy, scope)
default:
return false
}
}
// No matching rule, use the parent.
return p.parent.NodeWrite(name)
return p.parent.NodeWrite(name, scope)
}
// OperatorWrite determines if the state-changing operator functions are
@ -603,9 +639,9 @@ func (p *PolicyACL) PreparedQueryWrite(prefix string) bool {
func (p *PolicyACL) ServiceRead(name string) bool {
// Check for an exact rule or catch-all
_, rule, ok := p.serviceRules.LongestPrefix(name)
if ok {
switch rule {
pr := rule.(PolicyRule)
switch pr.aclPolicy {
case PolicyRead, PolicyWrite:
return true
default:
@ -618,21 +654,21 @@ func (p *PolicyACL) ServiceRead(name string) bool {
}
// ServiceWrite checks if writing (registering) a service is allowed
func (p *PolicyACL) ServiceWrite(name string) bool {
func (p *PolicyACL) ServiceWrite(name string, scope sentinel.ScopeFn) bool {
// Check for an exact rule or catch-all
_, rule, ok := p.serviceRules.LongestPrefix(name)
if ok {
switch rule {
pr := rule.(PolicyRule)
switch pr.aclPolicy {
case PolicyWrite:
return true
return p.executeCodePolicy(&pr.sentinelPolicy, scope)
default:
return false
}
}
// No matching rule, use the parent.
return p.parent.ServiceWrite(name)
return p.parent.ServiceWrite(name, scope)
}
// SessionRead checks for permission to read sessions for a given node.
@ -670,3 +706,22 @@ func (p *PolicyACL) SessionWrite(node string) bool {
// No matching rule, use the parent.
return p.parent.SessionWrite(node)
}
// executeCodePolicy will run the associated code policy if code policies are
// enabled.
func (p *PolicyACL) executeCodePolicy(policy *Sentinel, scope sentinel.ScopeFn) bool {
if p.sentinel == nil {
return true
}
if policy.Code == "" || scope == nil {
return true
}
enforcement := policy.EnforcementLevel
if enforcement == "" {
enforcement = DefaultPolicyEnforcementLevel
}
return p.sentinel.Execute(policy.Code, enforcement, scope())
}

View File

@ -56,7 +56,7 @@ func TestStaticACL(t *testing.T) {
if !all.KeyRead("foobar") {
t.Fatalf("should allow")
}
if !all.KeyWrite("foobar") {
if !all.KeyWrite("foobar", nil) {
t.Fatalf("should allow")
}
if !all.KeyringRead() {
@ -68,7 +68,7 @@ func TestStaticACL(t *testing.T) {
if !all.NodeRead("foobar") {
t.Fatalf("should allow")
}
if !all.NodeWrite("foobar") {
if !all.NodeWrite("foobar", nil) {
t.Fatalf("should allow")
}
if !all.OperatorRead() {
@ -86,7 +86,7 @@ func TestStaticACL(t *testing.T) {
if !all.ServiceRead("foobar") {
t.Fatalf("should allow")
}
if !all.ServiceWrite("foobar") {
if !all.ServiceWrite("foobar", nil) {
t.Fatalf("should allow")
}
if !all.SessionRead("foobar") {
@ -126,7 +126,7 @@ func TestStaticACL(t *testing.T) {
if none.KeyRead("foobar") {
t.Fatalf("should not allow")
}
if none.KeyWrite("foobar") {
if none.KeyWrite("foobar", nil) {
t.Fatalf("should not allow")
}
if none.KeyringRead() {
@ -138,7 +138,7 @@ func TestStaticACL(t *testing.T) {
if none.NodeRead("foobar") {
t.Fatalf("should not allow")
}
if none.NodeWrite("foobar") {
if none.NodeWrite("foobar", nil) {
t.Fatalf("should not allow")
}
if none.OperatorRead() {
@ -156,7 +156,7 @@ func TestStaticACL(t *testing.T) {
if none.ServiceRead("foobar") {
t.Fatalf("should not allow")
}
if none.ServiceWrite("foobar") {
if none.ServiceWrite("foobar", nil) {
t.Fatalf("should not allow")
}
if none.SessionRead("foobar") {
@ -190,7 +190,7 @@ func TestStaticACL(t *testing.T) {
if !manage.KeyRead("foobar") {
t.Fatalf("should allow")
}
if !manage.KeyWrite("foobar") {
if !manage.KeyWrite("foobar", nil) {
t.Fatalf("should allow")
}
if !manage.KeyringRead() {
@ -202,7 +202,7 @@ func TestStaticACL(t *testing.T) {
if !manage.NodeRead("foobar") {
t.Fatalf("should allow")
}
if !manage.NodeWrite("foobar") {
if !manage.NodeWrite("foobar", nil) {
t.Fatalf("should allow")
}
if !manage.OperatorRead() {
@ -220,7 +220,7 @@ func TestStaticACL(t *testing.T) {
if !manage.ServiceRead("foobar") {
t.Fatalf("should allow")
}
if !manage.ServiceWrite("foobar") {
if !manage.ServiceWrite("foobar", nil) {
t.Fatalf("should allow")
}
if !manage.SessionRead("foobar") {
@ -306,7 +306,7 @@ func TestPolicyACL(t *testing.T) {
},
},
}
acl, err := New(all, policy)
acl, err := New(all, policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -330,7 +330,7 @@ func TestPolicyACL(t *testing.T) {
if c.read != acl.KeyRead(c.inp) {
t.Fatalf("Read fail: %#v", c)
}
if c.write != acl.KeyWrite(c.inp) {
if c.write != acl.KeyWrite(c.inp, nil) {
t.Fatalf("Write fail: %#v", c)
}
if c.writePrefix != acl.KeyWritePrefix(c.inp) {
@ -357,7 +357,7 @@ func TestPolicyACL(t *testing.T) {
if c.read != acl.ServiceRead(c.inp) {
t.Fatalf("Read fail: %#v", c)
}
if c.write != acl.ServiceWrite(c.inp) {
if c.write != acl.ServiceWrite(c.inp, nil) {
t.Fatalf("Write fail: %#v", c)
}
}
@ -444,7 +444,7 @@ func TestPolicyACL_Parent(t *testing.T) {
},
},
}
root, err := New(deny, policyRoot)
root, err := New(deny, policyRoot, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -477,7 +477,7 @@ func TestPolicyACL_Parent(t *testing.T) {
},
},
}
acl, err := New(root, policy)
acl, err := New(root, policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -499,7 +499,7 @@ func TestPolicyACL_Parent(t *testing.T) {
if c.read != acl.KeyRead(c.inp) {
t.Fatalf("Read fail: %#v", c)
}
if c.write != acl.KeyWrite(c.inp) {
if c.write != acl.KeyWrite(c.inp, nil) {
t.Fatalf("Write fail: %#v", c)
}
if c.writePrefix != acl.KeyWritePrefix(c.inp) {
@ -523,7 +523,7 @@ func TestPolicyACL_Parent(t *testing.T) {
if c.read != acl.ServiceRead(c.inp) {
t.Fatalf("Read fail: %#v", c)
}
if c.write != acl.ServiceWrite(c.inp) {
if c.write != acl.ServiceWrite(c.inp, nil) {
t.Fatalf("Write fail: %#v", c)
}
}
@ -585,7 +585,7 @@ func TestPolicyACL_Agent(t *testing.T) {
},
},
}
root, err := New(deny, policyRoot)
root, err := New(deny, policyRoot, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -610,7 +610,7 @@ func TestPolicyACL_Agent(t *testing.T) {
},
},
}
acl, err := New(root, policy)
acl, err := New(root, policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -659,7 +659,7 @@ func TestPolicyACL_Keyring(t *testing.T) {
{PolicyDeny, false, false},
}
for _, c := range cases {
acl, err := New(DenyAll(), &Policy{Keyring: c.inp})
acl, err := New(DenyAll(), &Policy{Keyring: c.inp}, nil)
if err != nil {
t.Fatalf("bad: %s", err)
}
@ -685,7 +685,7 @@ func TestPolicyACL_Operator(t *testing.T) {
{PolicyDeny, false, false},
}
for _, c := range cases {
acl, err := New(DenyAll(), &Policy{Operator: c.inp})
acl, err := New(DenyAll(), &Policy{Operator: c.inp}, nil)
if err != nil {
t.Fatalf("bad: %s", err)
}
@ -720,7 +720,7 @@ func TestPolicyACL_Node(t *testing.T) {
},
},
}
root, err := New(deny, policyRoot)
root, err := New(deny, policyRoot, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -745,7 +745,7 @@ func TestPolicyACL_Node(t *testing.T) {
},
},
}
acl, err := New(root, policy)
acl, err := New(root, policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -775,7 +775,7 @@ func TestPolicyACL_Node(t *testing.T) {
if c.read != acl.NodeRead(c.inp) {
t.Fatalf("Read fail: %#v", c)
}
if c.write != acl.NodeWrite(c.inp) {
if c.write != acl.NodeWrite(c.inp, nil) {
t.Fatalf("Write fail: %#v", c)
}
}
@ -803,7 +803,7 @@ func TestPolicyACL_Session(t *testing.T) {
},
},
}
root, err := New(deny, policyRoot)
root, err := New(deny, policyRoot, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -828,7 +828,7 @@ func TestPolicyACL_Session(t *testing.T) {
},
},
}
acl, err := New(root, policy)
acl, err := New(root, policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}

View File

@ -4,6 +4,7 @@ import (
"crypto/md5"
"fmt"
"github.com/hashicorp/consul/sentinel"
"github.com/hashicorp/golang-lru"
)
@ -24,10 +25,11 @@ type Cache struct {
aclCache *lru.TwoQueueCache // Cache id -> acl
policyCache *lru.TwoQueueCache // Cache policy -> acl
ruleCache *lru.TwoQueueCache // Cache rules -> policy
sentinel sentinel.Evaluator
}
// NewCache constructs a new policy and ACL cache of a given size
func NewCache(size int, faultfn FaultFunc) (*Cache, error) {
func NewCache(size int, faultfn FaultFunc, sentinel sentinel.Evaluator) (*Cache, error) {
if size <= 0 {
return nil, fmt.Errorf("Must provide positive cache size")
}
@ -52,6 +54,7 @@ func NewCache(size int, faultfn FaultFunc) (*Cache, error) {
aclCache: ac,
policyCache: pc,
ruleCache: rc,
sentinel: sentinel,
}
return c, nil
}
@ -69,7 +72,7 @@ func (c *Cache) getPolicy(id, rules string) (*Policy, error) {
if ok {
return raw.(*Policy), nil
}
policy, err := Parse(rules)
policy, err := Parse(rules, c.sentinel)
if err != nil {
return nil, err
}
@ -149,7 +152,7 @@ func (c *Cache) GetACL(id string) (ACL, error) {
}
// Compile the ACL
acl, err := New(parent, policy)
acl, err := New(parent, policy, c.sentinel)
if err != nil {
return nil, err
}

View File

@ -5,7 +5,7 @@ import (
)
func TestCache_GetPolicy(t *testing.T) {
c, err := NewCache(2, nil)
c, err := NewCache(2, nil, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -62,7 +62,7 @@ func TestCache_GetACL(t *testing.T) {
return "deny", policies[id], nil
}
c, err := NewCache(2, faultfn)
c, err := NewCache(2, faultfn, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -125,7 +125,7 @@ func TestCache_ClearACL(t *testing.T) {
return "deny", policies[id], nil
}
c, err := NewCache(16, faultfn)
c, err := NewCache(16, faultfn, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -160,7 +160,7 @@ func TestCache_Purge(t *testing.T) {
return "deny", policies[id], nil
}
c, err := NewCache(16, faultfn)
c, err := NewCache(16, faultfn, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -192,7 +192,7 @@ func TestCache_GetACLPolicy(t *testing.T) {
faultfn := func(id string) (string, string, error) {
return "deny", policies[id], nil
}
c, err := NewCache(16, faultfn)
c, err := NewCache(16, faultfn, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -245,7 +245,7 @@ func TestCache_GetACL_Parent(t *testing.T) {
return "", "", nil
}
c, err := NewCache(16, faultfn)
c, err := NewCache(16, faultfn, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -276,7 +276,7 @@ func TestCache_GetACL_ParentCache(t *testing.T) {
return "", "", nil
}
c, err := NewCache(16, faultfn)
c, err := NewCache(16, faultfn, nil)
if err != nil {
t.Fatalf("err: %v", err)
}

View File

@ -3,6 +3,7 @@ package acl
import (
"fmt"
"github.com/hashicorp/consul/sentinel"
"github.com/hashicorp/hcl"
)
@ -27,6 +28,12 @@ type Policy struct {
Operator string `hcl:"operator"`
}
// Sentinel defines a snippet of Sentinel code that can be attached to a policy.
type Sentinel struct {
Code string
EnforcementLevel string
}
// AgentPolicy represents a policy for working with agent endpoints on nodes
// with specific name prefixes.
type AgentPolicy struct {
@ -42,6 +49,7 @@ func (a *AgentPolicy) GoString() string {
type KeyPolicy struct {
Prefix string `hcl:",key"`
Policy string
Sentinel Sentinel
}
func (k *KeyPolicy) GoString() string {
@ -52,6 +60,7 @@ func (k *KeyPolicy) GoString() string {
type NodePolicy struct {
Name string `hcl:",key"`
Policy string
Sentinel Sentinel
}
func (n *NodePolicy) GoString() string {
@ -62,6 +71,7 @@ func (n *NodePolicy) GoString() string {
type ServicePolicy struct {
Name string `hcl:",key"`
Policy string
Sentinel Sentinel
}
func (s *ServicePolicy) GoString() string {
@ -113,10 +123,38 @@ func isPolicyValid(policy string) bool {
}
}
// isSentinelValid makes sure the given sentinel block is valid, and will skip
// out if the evaluator is nil.
func isSentinelValid(sentinel sentinel.Evaluator, basicPolicy string, sp Sentinel) error {
// TODO (slackpad) - Need a unit test for this.
// Sentinel not enabled at all, or for this policy.
if sentinel == nil {
return nil
}
if sp.Code == "" {
return nil
}
// We only allow sentinel code on write policies at this time.
if basicPolicy != PolicyWrite {
return fmt.Errorf("code is only allowed for write policies")
}
// Validate the sentinel parts.
switch sp.EnforcementLevel {
case "", "soft-mandatory", "hard-mandatory":
// OK
default:
return fmt.Errorf("unsupported enforcement level %q", sp.EnforcementLevel)
}
return sentinel.Compile(sp.Code)
}
// 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) {
func Parse(rules string, sentinel sentinel.Evaluator) (*Policy, error) {
// Decode the rules
p := &Policy{}
if rules == "" {
@ -140,6 +178,9 @@ func Parse(rules string) (*Policy, error) {
if !isPolicyValid(kp.Policy) {
return nil, fmt.Errorf("Invalid key policy: %#v", kp)
}
if err := isSentinelValid(sentinel, kp.Policy, kp.Sentinel); err != nil {
return nil, fmt.Errorf("Invalid key Sentinel policy: %#v, got error:%v", kp, err)
}
}
// Validate the node policies
@ -147,6 +188,9 @@ func Parse(rules string) (*Policy, error) {
if !isPolicyValid(np.Policy) {
return nil, fmt.Errorf("Invalid node policy: %#v", np)
}
if err := isSentinelValid(sentinel, np.Policy, np.Sentinel); err != nil {
return nil, fmt.Errorf("Invalid node Sentinel policy: %#v, got error:%v", np, err)
}
}
// Validate the service policies
@ -154,6 +198,9 @@ func Parse(rules string) (*Policy, error) {
if !isPolicyValid(sp.Policy) {
return nil, fmt.Errorf("Invalid service policy: %#v", sp)
}
if err := isSentinelValid(sentinel, sp.Policy, sp.Sentinel); err != nil {
return nil, fmt.Errorf("Invalid service Sentinel policy: %#v, got error:%v", sp, err)
}
}
// Validate the session policies

View File

@ -163,7 +163,7 @@ query "bar" {
},
}
out, err := Parse(inp)
out, err := Parse(inp, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -344,7 +344,7 @@ func TestACLPolicy_Parse_JSON(t *testing.T) {
},
}
out, err := Parse(inp)
out, err := Parse(inp, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -362,7 +362,7 @@ keyring = ""
Keyring: "",
}
out, err := Parse(inp)
out, err := Parse(inp, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -380,7 +380,7 @@ operator = ""
Operator: "",
}
out, err := Parse(inp)
out, err := Parse(inp, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -403,7 +403,7 @@ func TestACLPolicy_Bad_Policy(t *testing.T) {
`session "" { policy = "nope" }`,
}
for _, c := range cases {
_, err := Parse(c)
_, err := Parse(c, nil)
if err == nil || !strings.Contains(err.Error(), "Invalid") {
t.Fatalf("expected policy error, got: %#v", err)
}

View File

@ -91,7 +91,7 @@ func newACLManager(config *config.RuntimeConfig) (*aclManager, error) {
},
},
}
master, err := acl.New(acl.DenyAll(), policy)
master, err := acl.New(acl.DenyAll(), policy, nil)
if err != nil {
return nil, err
}
@ -194,7 +194,7 @@ func (m *aclManager) lookupACL(a *Agent, id string) (acl.ACL, error) {
}
}
acl, err := acl.New(parent, reply.Policy)
acl, err := acl.New(parent, reply.Policy, nil)
if err != nil {
return nil, err
}
@ -252,14 +252,14 @@ func (a *Agent) vetServiceRegister(token string, service *structs.NodeService) e
}
// Vet the service itself.
if !rule.ServiceWrite(service.Service) {
if !rule.ServiceWrite(service.Service, nil) {
return acl.ErrPermissionDenied
}
// Vet any service that might be getting overwritten.
services := a.state.Services()
if existing, ok := services[service.ID]; ok {
if !rule.ServiceWrite(existing.Service) {
if !rule.ServiceWrite(existing.Service, nil) {
return acl.ErrPermissionDenied
}
}
@ -282,7 +282,7 @@ func (a *Agent) vetServiceUpdate(token string, serviceID string) error {
// Vet any changes based on the existing services's info.
services := a.state.Services()
if existing, ok := services[serviceID]; ok {
if !rule.ServiceWrite(existing.Service) {
if !rule.ServiceWrite(existing.Service, nil) {
return acl.ErrPermissionDenied
}
} else {
@ -306,11 +306,11 @@ func (a *Agent) vetCheckRegister(token string, check *structs.HealthCheck) error
// Vet the check itself.
if len(check.ServiceName) > 0 {
if !rule.ServiceWrite(check.ServiceName) {
if !rule.ServiceWrite(check.ServiceName, nil) {
return acl.ErrPermissionDenied
}
} else {
if !rule.NodeWrite(a.config.NodeName) {
if !rule.NodeWrite(a.config.NodeName, nil) {
return acl.ErrPermissionDenied
}
}
@ -319,11 +319,11 @@ func (a *Agent) vetCheckRegister(token string, check *structs.HealthCheck) error
checks := a.state.Checks()
if existing, ok := checks[check.CheckID]; ok {
if len(existing.ServiceName) > 0 {
if !rule.ServiceWrite(existing.ServiceName) {
if !rule.ServiceWrite(existing.ServiceName, nil) {
return acl.ErrPermissionDenied
}
} else {
if !rule.NodeWrite(a.config.NodeName) {
if !rule.NodeWrite(a.config.NodeName, nil) {
return acl.ErrPermissionDenied
}
}
@ -347,11 +347,11 @@ func (a *Agent) vetCheckUpdate(token string, checkID types.CheckID) error {
checks := a.state.Checks()
if existing, ok := checks[checkID]; ok {
if len(existing.ServiceName) > 0 {
if !rule.ServiceWrite(existing.ServiceName) {
if !rule.ServiceWrite(existing.ServiceName, nil) {
return acl.ErrPermissionDenied
}
} else {
if !rule.NodeWrite(a.config.NodeName) {
if !rule.NodeWrite(a.config.NodeName, nil) {
return acl.ErrPermissionDenied
}
}

View File

@ -207,7 +207,7 @@ func TestACL_Special_IDs(t *testing.T) {
if !acl.NodeRead("hello") {
t.Fatalf("should be able to read any node")
}
if acl.NodeWrite("hello") {
if acl.NodeWrite("hello", nil) {
t.Fatalf("should not be able to write any node")
}
}

View File

@ -637,7 +637,7 @@ func (s *HTTPServer) AgentNodeMaintenance(resp http.ResponseWriter, req *http.Re
if err != nil {
return nil, err
}
if rule != nil && !rule.NodeWrite(s.agent.config.NodeName) {
if rule != nil && !rule.NodeWrite(s.agent.config.NodeName, nil) {
return nil, acl.ErrPermissionDenied
}

View File

@ -9,6 +9,8 @@ import (
"github.com/armon/go-metrics"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/sentinel"
"github.com/hashicorp/golang-lru"
)
@ -102,6 +104,9 @@ type aclCache struct {
// acls is a non-authoritative ACL cache.
acls *lru.TwoQueueCache
// sentinel is the code engine (can be nil).
sentinel sentinel.Evaluator
// aclPolicyCache is a non-authoritative policy cache.
policies *lru.TwoQueueCache
@ -116,13 +121,14 @@ type aclCache struct {
// newACLCache returns a new non-authoritative cache for ACLs. This is used for
// performance, and is used inside the ACL datacenter on non-leader servers, and
// outside the ACL datacenter everywhere.
func newACLCache(conf *Config, logger *log.Logger, rpc rpcFn, local acl.FaultFunc) (*aclCache, error) {
func newACLCache(conf *Config, logger *log.Logger, rpc rpcFn, local acl.FaultFunc, sentinel sentinel.Evaluator) (*aclCache, error) {
var err error
cache := &aclCache{
config: conf,
logger: logger,
rpc: rpc,
local: local,
sentinel: sentinel,
}
// Initialize the non-authoritative ACL cache
@ -206,7 +212,7 @@ func (c *aclCache) lookupACL(id, authDC string) (acl.ACL, error) {
goto ACL_DOWN
}
policy, err := acl.Parse(rules)
policy, err := acl.Parse(rules, c.sentinel)
if err != nil {
c.logger.Printf("[DEBUG] consul.acl: Failed to parse policy for replicated ACL: %v", err)
goto ACL_DOWN
@ -268,7 +274,7 @@ func (c *aclCache) useACLPolicy(id, authDC string, cached *aclCacheEntry, p *str
}
// Compile the ACL
acl, err := acl.New(parent, p.Policy)
acl, err := acl.New(parent, p.Policy, c.sentinel)
if err != nil {
return nil, err
}
@ -327,7 +333,6 @@ func (f *aclFilter) allowService(service string) bool {
if !f.enforceVersion8 && service == structs.ConsulServiceID {
return true
}
return f.acl.ServiceRead(service)
}
@ -564,8 +569,7 @@ func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) {
}
// filterACL is used to filter results from our service catalog based on the
// rules configured for the provided token. The subject is scrubbed and
// modified in-place, leaving only resources the token can access.
// rules configured for the provided token.
func (s *Server) filterACL(token string, subj interface{}) error {
// Get the ACL from the token
acl, err := s.resolveToken(token)
@ -646,11 +650,45 @@ func vetRegisterWithACL(rule acl.ACL, subj *structs.RegisterRequest,
return nil
}
// This gets called potentially from a few spots so we save it and
// return the structure we made if we have it.
var memo map[string]interface{}
scope := func() map[string]interface{} {
if memo != nil {
return memo
}
node := &api.Node{
ID: string(subj.ID),
Node: subj.Node,
Address: subj.Address,
Datacenter: subj.Datacenter,
TaggedAddresses: subj.TaggedAddresses,
Meta: subj.NodeMeta,
}
var service *api.AgentService
if subj.Service != nil {
service = &api.AgentService{
ID: subj.Service.ID,
Service: subj.Service.Service,
Tags: subj.Service.Tags,
Address: subj.Service.Address,
Port: subj.Service.Port,
EnableTagOverride: subj.Service.EnableTagOverride,
}
}
memo = sentinel.ScopeCatalogUpsert(node, service)
return memo
}
// Vet the node info. This allows service updates to re-post the required
// node info for each request without having to have node "write"
// privileges.
needsNode := ns == nil || subj.ChangesNode(ns.Node)
if needsNode && !rule.NodeWrite(subj.Node) {
if needsNode && !rule.NodeWrite(subj.Node, scope) {
return acl.ErrPermissionDenied
}
@ -658,13 +696,17 @@ func vetRegisterWithACL(rule acl.ACL, subj *structs.RegisterRequest,
// the given service, and that we can write to any existing service that
// is being modified by id (if any).
if subj.Service != nil {
if !rule.ServiceWrite(subj.Service.Service) {
if !rule.ServiceWrite(subj.Service.Service, scope) {
return acl.ErrPermissionDenied
}
if ns != nil {
other, ok := ns.Services[subj.Service.ID]
if ok && !rule.ServiceWrite(other.Service) {
// This is effectively a delete, so we DO NOT apply the
// sentinel scope to the service we are overwriting, just
// the regular ACL policy.
if ok && !rule.ServiceWrite(other.Service, nil) {
return acl.ErrPermissionDenied
}
}
@ -693,7 +735,7 @@ func vetRegisterWithACL(rule acl.ACL, subj *structs.RegisterRequest,
// Node-level check.
if check.ServiceID == "" {
if !rule.NodeWrite(subj.Node) {
if !rule.NodeWrite(subj.Node, scope) {
return acl.ErrPermissionDenied
}
continue
@ -718,7 +760,10 @@ func vetRegisterWithACL(rule acl.ACL, subj *structs.RegisterRequest,
return fmt.Errorf("Unknown service '%s' for check '%s'", check.ServiceID, check.CheckID)
}
if !rule.ServiceWrite(other.Service) {
// We are only adding a check here, so we don't add the scope,
// since the sentinel policy doesn't apply to adding checks at
// this time.
if !rule.ServiceWrite(other.Service, nil) {
return acl.ErrPermissionDenied
}
}
@ -733,11 +778,15 @@ func vetRegisterWithACL(rule acl.ACL, subj *structs.RegisterRequest,
// be nil; similar for the HealthCheck for the referenced health check.
func vetDeregisterWithACL(rule acl.ACL, subj *structs.DeregisterRequest,
ns *structs.NodeService, nc *structs.HealthCheck) error {
// Fast path if ACLs are not enabled.
if rule == nil {
return nil
}
// We don't apply sentinel in this path, since at this time sentinel
// only applies to create and update operations.
// This order must match the code in applyRegister() in fsm.go since it
// also evaluates things in this order, and will ignore fields based on
// this precedence. This lets us also ignore them from an ACL perspective.
@ -745,7 +794,7 @@ func vetDeregisterWithACL(rule acl.ACL, subj *structs.DeregisterRequest,
if ns == nil {
return fmt.Errorf("Unknown service '%s'", subj.ServiceID)
}
if !rule.ServiceWrite(ns.Service) {
if !rule.ServiceWrite(ns.Service, nil) {
return acl.ErrPermissionDenied
}
} else if subj.CheckID != "" {
@ -753,16 +802,16 @@ func vetDeregisterWithACL(rule acl.ACL, subj *structs.DeregisterRequest,
return fmt.Errorf("Unknown check '%s'", subj.CheckID)
}
if nc.ServiceID != "" {
if !rule.ServiceWrite(nc.ServiceName) {
if !rule.ServiceWrite(nc.ServiceName, nil) {
return acl.ErrPermissionDenied
}
} else {
if !rule.NodeWrite(subj.Node) {
if !rule.NodeWrite(subj.Node, nil) {
return acl.ErrPermissionDenied
}
}
} else {
if !rule.NodeWrite(subj.Node) {
if !rule.NodeWrite(subj.Node, nil) {
return acl.ErrPermissionDenied
}
}

View File

@ -107,7 +107,7 @@ func aclApplyInternal(srv *Server, args *structs.ACLRequest, reply *string) erro
}
// Validate the rules compile
_, err := acl.Parse(args.ACL.Rules)
_, err := acl.Parse(args.ACL.Rules, srv.sentinel)
if err != nil {
return fmt.Errorf("ACL rule compilation failed: %v", err)
}

View File

@ -793,11 +793,11 @@ func TestACL_filterHealthChecks(t *testing.T) {
service "foo" {
policy = "read"
}
`)
`, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err := acl.New(acl.DenyAll(), policy)
perms, err := acl.New(acl.DenyAll(), policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -827,11 +827,11 @@ service "foo" {
node "node1" {
policy = "read"
}
`)
`, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err = acl.New(perms, policy)
perms, err = acl.New(perms, policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -918,11 +918,11 @@ func TestACL_filterServiceNodes(t *testing.T) {
service "foo" {
policy = "read"
}
`)
`, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err := acl.New(acl.DenyAll(), policy)
perms, err := acl.New(acl.DenyAll(), policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -952,11 +952,11 @@ service "foo" {
node "node1" {
policy = "read"
}
`)
`, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err = acl.New(perms, policy)
perms, err = acl.New(perms, policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -1024,11 +1024,11 @@ func TestACL_filterNodeServices(t *testing.T) {
service "foo" {
policy = "read"
}
`)
`, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err := acl.New(acl.DenyAll(), policy)
perms, err := acl.New(acl.DenyAll(), policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -1058,11 +1058,11 @@ service "foo" {
node "node1" {
policy = "read"
}
`)
`, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err = acl.New(perms, policy)
perms, err = acl.New(perms, policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -1130,11 +1130,11 @@ func TestACL_filterCheckServiceNodes(t *testing.T) {
service "foo" {
policy = "read"
}
`)
`, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err := acl.New(acl.DenyAll(), policy)
perms, err := acl.New(acl.DenyAll(), policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -1167,11 +1167,11 @@ service "foo" {
node "node1" {
policy = "read"
}
`)
`, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err = acl.New(perms, policy)
perms, err = acl.New(perms, policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -1321,11 +1321,11 @@ func TestACL_filterNodeDump(t *testing.T) {
service "foo" {
policy = "read"
}
`)
`, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err := acl.New(acl.DenyAll(), policy)
perms, err := acl.New(acl.DenyAll(), policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -1361,11 +1361,11 @@ service "foo" {
node "node1" {
policy = "read"
}
`)
`, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err = acl.New(perms, policy)
perms, err = acl.New(perms, policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -1563,11 +1563,11 @@ func TestACL_vetRegisterWithACL(t *testing.T) {
node "node" {
policy = "write"
}
`)
`, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err := acl.New(acl.DenyAll(), policy)
perms, err := acl.New(acl.DenyAll(), policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -1608,11 +1608,11 @@ node "node" {
service "service" {
policy = "write"
}
`)
`, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err = acl.New(perms, policy)
perms, err = acl.New(perms, policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -1638,11 +1638,11 @@ service "service" {
service "other" {
policy = "write"
}
`)
`, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err = acl.New(perms, policy)
perms, err = acl.New(perms, policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -1712,11 +1712,11 @@ service "other" {
service "other" {
policy = "deny"
}
`)
`, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err = acl.New(perms, policy)
perms, err = acl.New(perms, policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -1742,11 +1742,11 @@ service "other" {
node "node" {
policy = "deny"
}
`)
`, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err = acl.New(perms, policy)
perms, err = acl.New(perms, policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
@ -1792,11 +1792,11 @@ node "node" {
service "service" {
policy = "write"
}
`)
`, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err := acl.New(acl.DenyAll(), policy)
perms, err := acl.New(acl.DenyAll(), policy, nil)
if err != nil {
t.Fatalf("err: %v", err)
}

View File

@ -66,7 +66,7 @@ func (c *Catalog) Register(args *structs.RegisterRequest, reply *struct{}) error
// later if version 0.8 is enabled, so we can eventually just
// delete this and do all the ACL checks down there.
if args.Service.Service != structs.ConsulServiceName {
if rule != nil && !rule.ServiceWrite(args.Service.Service) {
if rule != nil && !rule.ServiceWrite(args.Service.Service, nil) {
return acl.ErrPermissionDenied
}
}
@ -149,6 +149,7 @@ func (c *Catalog) Deregister(args *structs.DeregisterRequest, reply *struct{}) e
if err := vetDeregisterWithACL(rule, args, ns, nc); err != nil {
return err
}
}
if _, err := c.srv.raftApply(structs.DeregisterRequestType, args); err != nil {

View File

@ -139,7 +139,10 @@ func (c *Coordinate) Update(args *structs.CoordinateUpdateRequest, reply *struct
return err
}
if rule != nil && c.srv.config.ACLEnforceVersion8 {
if !rule.NodeWrite(args.Node) {
// We don't enforce the sentinel policy here, since at this time
// sentinel only applies to creating or updating node or service
// info, not updating coordinates.
if !rule.NodeWrite(args.Node, nil) {
return acl.ErrPermissionDenied
}
}

View File

@ -10,8 +10,8 @@ import (
func TestFilter_DirEnt(t *testing.T) {
t.Parallel()
policy, _ := acl.Parse(testFilterRules)
aclR, _ := acl.New(acl.DenyAll(), policy)
policy, _ := acl.Parse(testFilterRules, nil)
aclR, _ := acl.New(acl.DenyAll(), policy, nil)
type tcase struct {
in []string
@ -52,8 +52,8 @@ func TestFilter_DirEnt(t *testing.T) {
func TestFilter_Keys(t *testing.T) {
t.Parallel()
policy, _ := acl.Parse(testFilterRules)
aclR, _ := acl.New(acl.DenyAll(), policy)
policy, _ := acl.Parse(testFilterRules, nil)
aclR, _ := acl.New(acl.DenyAll(), policy, nil)
type tcase struct {
in []string
@ -84,8 +84,8 @@ func TestFilter_Keys(t *testing.T) {
func TestFilter_TxnResults(t *testing.T) {
t.Parallel()
policy, _ := acl.Parse(testFilterRules)
aclR, _ := acl.New(acl.DenyAll(), policy)
policy, _ := acl.Parse(testFilterRules, nil)
aclR, _ := acl.New(acl.DenyAll(), policy, nil)
type tcase struct {
in []string

View File

@ -9,6 +9,7 @@ import (
"github.com/hashicorp/consul/agent/consul/state"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/sentinel"
"github.com/hashicorp/go-memdb"
)
@ -22,6 +23,7 @@ type KVS struct {
// must only be done on the leader.
func kvsPreApply(srv *Server, rule acl.ACL, op api.KVOp, dirEnt *structs.DirEntry) (bool, error) {
// Verify the entry.
if dirEnt.Key == "" && op != api.KVDeleteTree {
return false, fmt.Errorf("Must provide key")
}
@ -46,7 +48,10 @@ func kvsPreApply(srv *Server, rule acl.ACL, op api.KVOp, dirEnt *structs.DirEntr
}
default:
if !rule.KeyWrite(dirEnt.Key) {
scope := func() map[string]interface{} {
return sentinel.ScopeKVUpsert(dirEnt.Key, dirEnt.Value, dirEnt.Flags)
}
if !rule.KeyWrite(dirEnt.Key, scope) {
return false, acl.ErrPermissionDenied
}
}
@ -115,11 +120,10 @@ func (k *KVS) Get(args *structs.KeyRequest, reply *structs.IndexedDirEntries) er
return err
}
acl, err := k.srv.resolveToken(args.Token)
aclRule, err := k.srv.resolveToken(args.Token)
if err != nil {
return err
}
return k.srv.blockingQuery(
&args.QueryOptions,
&reply.QueryMeta,
@ -128,9 +132,10 @@ func (k *KVS) Get(args *structs.KeyRequest, reply *structs.IndexedDirEntries) er
if err != nil {
return err
}
if acl != nil && !acl.KeyRead(args.Key) {
if aclRule != nil && !aclRule.KeyRead(args.Key) {
ent = nil
}
if ent == nil {
// Must provide non-zero index to prevent blocking
// Index 1 is impossible anyways (due to Raft internals)

View File

@ -25,6 +25,7 @@ import (
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/agent/token"
"github.com/hashicorp/consul/lib"
"github.com/hashicorp/consul/sentinel"
"github.com/hashicorp/consul/tlsutil"
"github.com/hashicorp/consul/types"
"github.com/hashicorp/raft"
@ -75,6 +76,9 @@ const (
// Server is Consul server which manages the service discovery,
// health checking, DC forwarding, Raft, and multiple Serf pools.
type Server struct {
// sentinel is the Sentinel code engine (can be nil).
sentinel sentinel.Evaluator
// aclAuthCache is the authoritative ACL cache.
aclAuthCache *acl.Cache
@ -317,7 +321,8 @@ func NewServerLogger(config *Config, logger *log.Logger, tokens *token.Store) (*
s.statsFetcher = NewStatsFetcher(logger, s.connPool, s.config.Datacenter)
// Initialize the authoritative ACL cache.
s.aclAuthCache, err = acl.NewCache(aclCacheSize, s.aclLocalFault)
s.sentinel = sentinel.New(logger)
s.aclAuthCache, err = acl.NewCache(aclCacheSize, s.aclLocalFault, s.sentinel)
if err != nil {
s.Shutdown()
return nil, fmt.Errorf("Failed to create authoritative ACL cache: %v", err)
@ -329,7 +334,7 @@ func NewServerLogger(config *Config, logger *log.Logger, tokens *token.Store) (*
if s.IsACLReplicationEnabled() {
local = s.aclLocalFault
}
if s.aclCache, err = newACLCache(config, logger, s.RPC, local); err != nil {
if s.aclCache, err = newACLCache(config, logger, s.RPC, local, s.sentinel); err != nil {
s.Shutdown()
return nil, fmt.Errorf("Failed to create non-authoritative ACL cache: %v", err)
}

8
sentinel/evaluator.go Normal file
View File

@ -0,0 +1,8 @@
package sentinel
// Evaluator wraps the Sentinel evaluator from the HashiCorp Sentinel policy
// engine.
type Evaluator interface {
Compile(policy string) error
Execute(policy string, enforcementLevel string, data map[string]interface{}) bool
}

29
sentinel/scope.go Normal file
View File

@ -0,0 +1,29 @@
package sentinel
import (
"github.com/hashicorp/consul/api"
)
// ScopeFn is a callback that provides a sentinel scope. This is a callback
// so that if we don't run sentinel for some reason (not enabled or a basic
// policy check means we don't have to) then we don't spend the effort to make
// the map.
type ScopeFn func() map[string]interface{}
// ScopeKVUpsert returns the standard sentinel scope for a KV create or update.
func ScopeKVUpsert(key string, value []byte, flags uint64) map[string]interface{} {
return map[string]interface{}{
"key": key,
"value": string(value),
"flags": flags,
}
}
// ScopeCatalogUpsert returns the standard sentinel scope for a catalog create
// or update. Service is allowed to be nil.
func ScopeCatalogUpsert(node *api.Node, service *api.AgentService) map[string]interface{} {
return map[string]interface{}{
"node": node,
"service": service,
}
}

13
sentinel/sentinel_stub.go Normal file
View File

@ -0,0 +1,13 @@
// +build !ent
package sentinel
import (
"log"
)
// New returns a new instance of the Sentinel code engine. This is only available
// in Consul Enterprise so this version always returns nil.
func New(logger *log.Logger) Evaluator {
return nil
}