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:
commit
b0549da664
113
acl/acl.go
113
acl/acl.go
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
@ -40,8 +47,9 @@ func (a *AgentPolicy) GoString() string {
|
|||
|
||||
// KeyPolicy represents a policy for a key
|
||||
type KeyPolicy struct {
|
||||
Prefix string `hcl:",key"`
|
||||
Policy string
|
||||
Prefix string `hcl:",key"`
|
||||
Policy string
|
||||
Sentinel Sentinel
|
||||
}
|
||||
|
||||
func (k *KeyPolicy) GoString() string {
|
||||
|
@ -50,8 +58,9 @@ func (k *KeyPolicy) GoString() string {
|
|||
|
||||
// NodePolicy represents a policy for a node
|
||||
type NodePolicy struct {
|
||||
Name string `hcl:",key"`
|
||||
Policy string
|
||||
Name string `hcl:",key"`
|
||||
Policy string
|
||||
Sentinel Sentinel
|
||||
}
|
||||
|
||||
func (n *NodePolicy) GoString() string {
|
||||
|
@ -60,8 +69,9 @@ func (n *NodePolicy) GoString() string {
|
|||
|
||||
// ServicePolicy represents a policy for a service
|
||||
type ServicePolicy struct {
|
||||
Name string `hcl:",key"`
|
||||
Policy string
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
22
agent/acl.go
22
agent/acl.go
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue