Introduce Code Policy validation via sentinel, with a noop implementation
This commit is contained in:
parent
69a088ca85
commit
8394ad08db
113
acl/acl.go
113
acl/acl.go
|
@ -2,6 +2,7 @@ package acl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/armon/go-radix"
|
"github.com/armon/go-radix"
|
||||||
|
"github.com/hashicorp/consul/sentinel"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -17,6 +18,10 @@ var (
|
||||||
manageAll ACL
|
manageAll ACL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DefaultPolicyEnforcementLevel will be used if the user leaves the level
|
||||||
|
// blank when configuring an ACL.
|
||||||
|
const DefaultPolicyEnforcementLevel = "hard-mandatory"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Setup the singletons
|
// Setup the singletons
|
||||||
allowAll = &StaticACL{
|
allowAll = &StaticACL{
|
||||||
|
@ -59,7 +64,7 @@ type ACL interface {
|
||||||
KeyRead(string) bool
|
KeyRead(string) bool
|
||||||
|
|
||||||
// KeyWrite checks for permission to write a given key
|
// 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
|
// KeyWritePrefix checks for permission to write to an
|
||||||
// entire key prefix. This means there must be no sub-policies
|
// 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
|
// NodeWrite checks for permission to create or update (register) a
|
||||||
// given node.
|
// given node.
|
||||||
NodeWrite(string) bool
|
NodeWrite(string, sentinel.ScopeFn) bool
|
||||||
|
|
||||||
// OperatorRead determines if the read-only Consul operator functions
|
// OperatorRead determines if the read-only Consul operator functions
|
||||||
// can be used.
|
// can be used.
|
||||||
|
@ -101,7 +106,7 @@ type ACL interface {
|
||||||
|
|
||||||
// ServiceWrite checks for permission to create or update a given
|
// ServiceWrite checks for permission to create or update a given
|
||||||
// service
|
// service
|
||||||
ServiceWrite(string) bool
|
ServiceWrite(string, sentinel.ScopeFn) bool
|
||||||
|
|
||||||
// SessionRead checks for permission to read sessions for a given node.
|
// SessionRead checks for permission to read sessions for a given node.
|
||||||
SessionRead(string) bool
|
SessionRead(string) bool
|
||||||
|
@ -150,7 +155,7 @@ func (s *StaticACL) KeyRead(string) bool {
|
||||||
return s.defaultAllow
|
return s.defaultAllow
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StaticACL) KeyWrite(string) bool {
|
func (s *StaticACL) KeyWrite(string, sentinel.ScopeFn) bool {
|
||||||
return s.defaultAllow
|
return s.defaultAllow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,7 +175,7 @@ func (s *StaticACL) NodeRead(string) bool {
|
||||||
return s.defaultAllow
|
return s.defaultAllow
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StaticACL) NodeWrite(string) bool {
|
func (s *StaticACL) NodeWrite(string, sentinel.ScopeFn) bool {
|
||||||
return s.defaultAllow
|
return s.defaultAllow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,7 +199,7 @@ func (s *StaticACL) ServiceRead(string) bool {
|
||||||
return s.defaultAllow
|
return s.defaultAllow
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StaticACL) ServiceWrite(string) bool {
|
func (s *StaticACL) ServiceWrite(string, sentinel.ScopeFn) bool {
|
||||||
return s.defaultAllow
|
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
|
// PolicyACL is used to wrap a set of ACL policies to provide
|
||||||
// the ACL interface.
|
// the ACL interface.
|
||||||
type PolicyACL struct {
|
type PolicyACL struct {
|
||||||
|
@ -246,6 +261,10 @@ type PolicyACL struct {
|
||||||
// no matching rule.
|
// no matching rule.
|
||||||
parent ACL
|
parent ACL
|
||||||
|
|
||||||
|
// sentinel is an interface for validating and executing sentinel code
|
||||||
|
// policies.
|
||||||
|
sentinel sentinel.Evaluator
|
||||||
|
|
||||||
// agentRules contains the agent policies
|
// agentRules contains the agent policies
|
||||||
agentRules *radix.Tree
|
agentRules *radix.Tree
|
||||||
|
|
||||||
|
@ -278,7 +297,7 @@ type PolicyACL struct {
|
||||||
|
|
||||||
// New is used to construct a policy based ACL from a set of policies
|
// New is used to construct a policy based ACL from a set of policies
|
||||||
// and a parent policy to resolve missing cases.
|
// 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{
|
p := &PolicyACL{
|
||||||
parent: parent,
|
parent: parent,
|
||||||
agentRules: radix.New(),
|
agentRules: radix.New(),
|
||||||
|
@ -288,6 +307,7 @@ func New(parent ACL, policy *Policy) (*PolicyACL, error) {
|
||||||
sessionRules: radix.New(),
|
sessionRules: radix.New(),
|
||||||
eventRules: radix.New(),
|
eventRules: radix.New(),
|
||||||
preparedQueryRules: radix.New(),
|
preparedQueryRules: radix.New(),
|
||||||
|
sentinel: sentinel,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the agent policy
|
// Load the agent policy
|
||||||
|
@ -297,17 +317,29 @@ func New(parent ACL, policy *Policy) (*PolicyACL, error) {
|
||||||
|
|
||||||
// Load the key policy
|
// Load the key policy
|
||||||
for _, kp := range policy.Keys {
|
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
|
// Load the node policy
|
||||||
for _, np := range policy.Nodes {
|
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
|
// Load the service policy
|
||||||
for _, sp := range policy.Services {
|
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
|
// Load the session policy
|
||||||
|
@ -421,7 +453,8 @@ func (p *PolicyACL) KeyRead(key string) bool {
|
||||||
// Look for a matching rule
|
// Look for a matching rule
|
||||||
_, rule, ok := p.keyRules.LongestPrefix(key)
|
_, rule, ok := p.keyRules.LongestPrefix(key)
|
||||||
if ok {
|
if ok {
|
||||||
switch rule.(string) {
|
pr := rule.(PolicyRule)
|
||||||
|
switch pr.aclPolicy {
|
||||||
case PolicyRead, PolicyWrite:
|
case PolicyRead, PolicyWrite:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
|
@ -434,27 +467,28 @@ func (p *PolicyACL) KeyRead(key string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyWrite returns if a key is allowed to be written
|
// 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
|
// Look for a matching rule
|
||||||
_, rule, ok := p.keyRules.LongestPrefix(key)
|
_, rule, ok := p.keyRules.LongestPrefix(key)
|
||||||
if ok {
|
if ok {
|
||||||
switch rule.(string) {
|
pr := rule.(PolicyRule)
|
||||||
|
switch pr.aclPolicy {
|
||||||
case PolicyWrite:
|
case PolicyWrite:
|
||||||
return true
|
return p.executeCodePolicy(&pr.sentinelPolicy, scope)
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No matching rule, use the parent.
|
// 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
|
// KeyWritePrefix returns if a prefix is allowed to be written
|
||||||
func (p *PolicyACL) KeyWritePrefix(prefix string) bool {
|
func (p *PolicyACL) KeyWritePrefix(prefix string) bool {
|
||||||
// Look for a matching rule that denies
|
// Look for a matching rule that denies
|
||||||
_, rule, ok := p.keyRules.LongestPrefix(prefix)
|
_, rule, ok := p.keyRules.LongestPrefix(prefix)
|
||||||
if ok && rule.(string) != PolicyWrite {
|
if ok && rule.(PolicyRule).aclPolicy != PolicyWrite {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,7 +496,7 @@ func (p *PolicyACL) KeyWritePrefix(prefix string) bool {
|
||||||
deny := false
|
deny := false
|
||||||
p.keyRules.WalkPrefix(prefix, func(path string, rule interface{}) bool {
|
p.keyRules.WalkPrefix(prefix, func(path string, rule interface{}) bool {
|
||||||
// We have a rule to prevent a write in a sub-directory!
|
// We have a rule to prevent a write in a sub-directory!
|
||||||
if rule.(string) != PolicyWrite {
|
if rule.(PolicyRule).aclPolicy != PolicyWrite {
|
||||||
deny = true
|
deny = true
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -522,7 +556,8 @@ func (p *PolicyACL) NodeRead(name string) bool {
|
||||||
_, rule, ok := p.nodeRules.LongestPrefix(name)
|
_, rule, ok := p.nodeRules.LongestPrefix(name)
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
switch rule {
|
pr := rule.(PolicyRule)
|
||||||
|
switch pr.aclPolicy {
|
||||||
case PolicyRead, PolicyWrite:
|
case PolicyRead, PolicyWrite:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
|
@ -535,21 +570,22 @@ func (p *PolicyACL) NodeRead(name string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeWrite checks if writing (registering) a node is allowed
|
// 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
|
// Check for an exact rule or catch-all
|
||||||
_, rule, ok := p.nodeRules.LongestPrefix(name)
|
_, rule, ok := p.nodeRules.LongestPrefix(name)
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
switch rule {
|
pr := rule.(PolicyRule)
|
||||||
|
switch pr.aclPolicy {
|
||||||
case PolicyWrite:
|
case PolicyWrite:
|
||||||
return true
|
return p.executeCodePolicy(&pr.sentinelPolicy, scope)
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No matching rule, use the parent.
|
// 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
|
// 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 {
|
func (p *PolicyACL) ServiceRead(name string) bool {
|
||||||
// Check for an exact rule or catch-all
|
// Check for an exact rule or catch-all
|
||||||
_, rule, ok := p.serviceRules.LongestPrefix(name)
|
_, rule, ok := p.serviceRules.LongestPrefix(name)
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
switch rule {
|
pr := rule.(PolicyRule)
|
||||||
|
switch pr.aclPolicy {
|
||||||
case PolicyRead, PolicyWrite:
|
case PolicyRead, PolicyWrite:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
|
@ -618,21 +654,21 @@ func (p *PolicyACL) ServiceRead(name string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceWrite checks if writing (registering) a service is allowed
|
// 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
|
// Check for an exact rule or catch-all
|
||||||
_, rule, ok := p.serviceRules.LongestPrefix(name)
|
_, rule, ok := p.serviceRules.LongestPrefix(name)
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
switch rule {
|
pr := rule.(PolicyRule)
|
||||||
|
switch pr.aclPolicy {
|
||||||
case PolicyWrite:
|
case PolicyWrite:
|
||||||
return true
|
return p.executeCodePolicy(&pr.sentinelPolicy, scope)
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No matching rule, use the parent.
|
// 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.
|
// 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.
|
// No matching rule, use the parent.
|
||||||
return p.parent.SessionWrite(node)
|
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") {
|
if !all.KeyRead("foobar") {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
if !all.KeyWrite("foobar") {
|
if !all.KeyWrite("foobar", nil) {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
if !all.KeyringRead() {
|
if !all.KeyringRead() {
|
||||||
|
@ -68,7 +68,7 @@ func TestStaticACL(t *testing.T) {
|
||||||
if !all.NodeRead("foobar") {
|
if !all.NodeRead("foobar") {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
if !all.NodeWrite("foobar") {
|
if !all.NodeWrite("foobar", nil) {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
if !all.OperatorRead() {
|
if !all.OperatorRead() {
|
||||||
|
@ -86,7 +86,7 @@ func TestStaticACL(t *testing.T) {
|
||||||
if !all.ServiceRead("foobar") {
|
if !all.ServiceRead("foobar") {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
if !all.ServiceWrite("foobar") {
|
if !all.ServiceWrite("foobar", nil) {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
if !all.SessionRead("foobar") {
|
if !all.SessionRead("foobar") {
|
||||||
|
@ -126,7 +126,7 @@ func TestStaticACL(t *testing.T) {
|
||||||
if none.KeyRead("foobar") {
|
if none.KeyRead("foobar") {
|
||||||
t.Fatalf("should not allow")
|
t.Fatalf("should not allow")
|
||||||
}
|
}
|
||||||
if none.KeyWrite("foobar") {
|
if none.KeyWrite("foobar", nil) {
|
||||||
t.Fatalf("should not allow")
|
t.Fatalf("should not allow")
|
||||||
}
|
}
|
||||||
if none.KeyringRead() {
|
if none.KeyringRead() {
|
||||||
|
@ -138,7 +138,7 @@ func TestStaticACL(t *testing.T) {
|
||||||
if none.NodeRead("foobar") {
|
if none.NodeRead("foobar") {
|
||||||
t.Fatalf("should not allow")
|
t.Fatalf("should not allow")
|
||||||
}
|
}
|
||||||
if none.NodeWrite("foobar") {
|
if none.NodeWrite("foobar", nil) {
|
||||||
t.Fatalf("should not allow")
|
t.Fatalf("should not allow")
|
||||||
}
|
}
|
||||||
if none.OperatorRead() {
|
if none.OperatorRead() {
|
||||||
|
@ -156,7 +156,7 @@ func TestStaticACL(t *testing.T) {
|
||||||
if none.ServiceRead("foobar") {
|
if none.ServiceRead("foobar") {
|
||||||
t.Fatalf("should not allow")
|
t.Fatalf("should not allow")
|
||||||
}
|
}
|
||||||
if none.ServiceWrite("foobar") {
|
if none.ServiceWrite("foobar", nil) {
|
||||||
t.Fatalf("should not allow")
|
t.Fatalf("should not allow")
|
||||||
}
|
}
|
||||||
if none.SessionRead("foobar") {
|
if none.SessionRead("foobar") {
|
||||||
|
@ -190,7 +190,7 @@ func TestStaticACL(t *testing.T) {
|
||||||
if !manage.KeyRead("foobar") {
|
if !manage.KeyRead("foobar") {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
if !manage.KeyWrite("foobar") {
|
if !manage.KeyWrite("foobar", nil) {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
if !manage.KeyringRead() {
|
if !manage.KeyringRead() {
|
||||||
|
@ -202,7 +202,7 @@ func TestStaticACL(t *testing.T) {
|
||||||
if !manage.NodeRead("foobar") {
|
if !manage.NodeRead("foobar") {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
if !manage.NodeWrite("foobar") {
|
if !manage.NodeWrite("foobar", nil) {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
if !manage.OperatorRead() {
|
if !manage.OperatorRead() {
|
||||||
|
@ -220,7 +220,7 @@ func TestStaticACL(t *testing.T) {
|
||||||
if !manage.ServiceRead("foobar") {
|
if !manage.ServiceRead("foobar") {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
if !manage.ServiceWrite("foobar") {
|
if !manage.ServiceWrite("foobar", nil) {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
if !manage.SessionRead("foobar") {
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -330,7 +330,7 @@ func TestPolicyACL(t *testing.T) {
|
||||||
if c.read != acl.KeyRead(c.inp) {
|
if c.read != acl.KeyRead(c.inp) {
|
||||||
t.Fatalf("Read fail: %#v", c)
|
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)
|
t.Fatalf("Write fail: %#v", c)
|
||||||
}
|
}
|
||||||
if c.writePrefix != acl.KeyWritePrefix(c.inp) {
|
if c.writePrefix != acl.KeyWritePrefix(c.inp) {
|
||||||
|
@ -357,7 +357,7 @@ func TestPolicyACL(t *testing.T) {
|
||||||
if c.read != acl.ServiceRead(c.inp) {
|
if c.read != acl.ServiceRead(c.inp) {
|
||||||
t.Fatalf("Read fail: %#v", c)
|
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)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -499,7 +499,7 @@ func TestPolicyACL_Parent(t *testing.T) {
|
||||||
if c.read != acl.KeyRead(c.inp) {
|
if c.read != acl.KeyRead(c.inp) {
|
||||||
t.Fatalf("Read fail: %#v", c)
|
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)
|
t.Fatalf("Write fail: %#v", c)
|
||||||
}
|
}
|
||||||
if c.writePrefix != acl.KeyWritePrefix(c.inp) {
|
if c.writePrefix != acl.KeyWritePrefix(c.inp) {
|
||||||
|
@ -523,7 +523,7 @@ func TestPolicyACL_Parent(t *testing.T) {
|
||||||
if c.read != acl.ServiceRead(c.inp) {
|
if c.read != acl.ServiceRead(c.inp) {
|
||||||
t.Fatalf("Read fail: %#v", c)
|
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)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -659,7 +659,7 @@ func TestPolicyACL_Keyring(t *testing.T) {
|
||||||
{PolicyDeny, false, false},
|
{PolicyDeny, false, false},
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
acl, err := New(DenyAll(), &Policy{Keyring: c.inp})
|
acl, err := New(DenyAll(), &Policy{Keyring: c.inp}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bad: %s", err)
|
t.Fatalf("bad: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -685,7 +685,7 @@ func TestPolicyACL_Operator(t *testing.T) {
|
||||||
{PolicyDeny, false, false},
|
{PolicyDeny, false, false},
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
acl, err := New(DenyAll(), &Policy{Operator: c.inp})
|
acl, err := New(DenyAll(), &Policy{Operator: c.inp}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bad: %s", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -775,7 +775,7 @@ func TestPolicyACL_Node(t *testing.T) {
|
||||||
if c.read != acl.NodeRead(c.inp) {
|
if c.read != acl.NodeRead(c.inp) {
|
||||||
t.Fatalf("Read fail: %#v", c)
|
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)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/sentinel"
|
||||||
"github.com/hashicorp/golang-lru"
|
"github.com/hashicorp/golang-lru"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,10 +25,11 @@ type Cache struct {
|
||||||
aclCache *lru.TwoQueueCache // Cache id -> acl
|
aclCache *lru.TwoQueueCache // Cache id -> acl
|
||||||
policyCache *lru.TwoQueueCache // Cache policy -> acl
|
policyCache *lru.TwoQueueCache // Cache policy -> acl
|
||||||
ruleCache *lru.TwoQueueCache // Cache rules -> policy
|
ruleCache *lru.TwoQueueCache // Cache rules -> policy
|
||||||
|
sentinel sentinel.Evaluator
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCache constructs a new policy and ACL cache of a given size
|
// 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 {
|
if size <= 0 {
|
||||||
return nil, fmt.Errorf("Must provide positive cache size")
|
return nil, fmt.Errorf("Must provide positive cache size")
|
||||||
}
|
}
|
||||||
|
@ -52,6 +54,7 @@ func NewCache(size int, faultfn FaultFunc) (*Cache, error) {
|
||||||
aclCache: ac,
|
aclCache: ac,
|
||||||
policyCache: pc,
|
policyCache: pc,
|
||||||
ruleCache: rc,
|
ruleCache: rc,
|
||||||
|
sentinel: sentinel,
|
||||||
}
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
@ -69,7 +72,7 @@ func (c *Cache) getPolicy(id, rules string) (*Policy, error) {
|
||||||
if ok {
|
if ok {
|
||||||
return raw.(*Policy), nil
|
return raw.(*Policy), nil
|
||||||
}
|
}
|
||||||
policy, err := Parse(rules)
|
policy, err := Parse(rules, c.sentinel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -149,7 +152,7 @@ func (c *Cache) GetACL(id string) (ACL, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile the ACL
|
// Compile the ACL
|
||||||
acl, err := New(parent, policy)
|
acl, err := New(parent, policy, c.sentinel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCache_GetPolicy(t *testing.T) {
|
func TestCache_GetPolicy(t *testing.T) {
|
||||||
c, err := NewCache(2, nil)
|
c, err := NewCache(2, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ func TestCache_GetACL(t *testing.T) {
|
||||||
return "deny", policies[id], nil
|
return "deny", policies[id], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := NewCache(2, faultfn)
|
c, err := NewCache(2, faultfn, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -125,7 +125,7 @@ func TestCache_ClearACL(t *testing.T) {
|
||||||
return "deny", policies[id], nil
|
return "deny", policies[id], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := NewCache(16, faultfn)
|
c, err := NewCache(16, faultfn, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ func TestCache_Purge(t *testing.T) {
|
||||||
return "deny", policies[id], nil
|
return "deny", policies[id], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := NewCache(16, faultfn)
|
c, err := NewCache(16, faultfn, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -192,7 +192,7 @@ func TestCache_GetACLPolicy(t *testing.T) {
|
||||||
faultfn := func(id string) (string, string, error) {
|
faultfn := func(id string) (string, string, error) {
|
||||||
return "deny", policies[id], nil
|
return "deny", policies[id], nil
|
||||||
}
|
}
|
||||||
c, err := NewCache(16, faultfn)
|
c, err := NewCache(16, faultfn, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -245,7 +245,7 @@ func TestCache_GetACL_Parent(t *testing.T) {
|
||||||
return "", "", nil
|
return "", "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := NewCache(16, faultfn)
|
c, err := NewCache(16, faultfn, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -276,7 +276,7 @@ func TestCache_GetACL_ParentCache(t *testing.T) {
|
||||||
return "", "", nil
|
return "", "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := NewCache(16, faultfn)
|
c, err := NewCache(16, faultfn, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package acl
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/sentinel"
|
||||||
"github.com/hashicorp/hcl"
|
"github.com/hashicorp/hcl"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,6 +28,12 @@ type Policy struct {
|
||||||
Operator string `hcl:"operator"`
|
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
|
// AgentPolicy represents a policy for working with agent endpoints on nodes
|
||||||
// with specific name prefixes.
|
// with specific name prefixes.
|
||||||
type AgentPolicy struct {
|
type AgentPolicy struct {
|
||||||
|
@ -42,6 +49,7 @@ func (a *AgentPolicy) GoString() string {
|
||||||
type KeyPolicy struct {
|
type KeyPolicy struct {
|
||||||
Prefix string `hcl:",key"`
|
Prefix string `hcl:",key"`
|
||||||
Policy string
|
Policy string
|
||||||
|
Sentinel Sentinel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *KeyPolicy) GoString() string {
|
func (k *KeyPolicy) GoString() string {
|
||||||
|
@ -52,6 +60,7 @@ func (k *KeyPolicy) GoString() string {
|
||||||
type NodePolicy struct {
|
type NodePolicy struct {
|
||||||
Name string `hcl:",key"`
|
Name string `hcl:",key"`
|
||||||
Policy string
|
Policy string
|
||||||
|
Sentinel Sentinel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NodePolicy) GoString() string {
|
func (n *NodePolicy) GoString() string {
|
||||||
|
@ -62,6 +71,7 @@ func (n *NodePolicy) GoString() string {
|
||||||
type ServicePolicy struct {
|
type ServicePolicy struct {
|
||||||
Name string `hcl:",key"`
|
Name string `hcl:",key"`
|
||||||
Policy string
|
Policy string
|
||||||
|
Sentinel Sentinel
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServicePolicy) GoString() string {
|
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
|
// Parse is used to parse the specified ACL rules into an
|
||||||
// intermediary set of policies, before being compiled into
|
// intermediary set of policies, before being compiled into
|
||||||
// the ACL
|
// the ACL
|
||||||
func Parse(rules string) (*Policy, error) {
|
func Parse(rules string, sentinel sentinel.Evaluator) (*Policy, error) {
|
||||||
// Decode the rules
|
// Decode the rules
|
||||||
p := &Policy{}
|
p := &Policy{}
|
||||||
if rules == "" {
|
if rules == "" {
|
||||||
|
@ -140,6 +178,9 @@ func Parse(rules string) (*Policy, error) {
|
||||||
if !isPolicyValid(kp.Policy) {
|
if !isPolicyValid(kp.Policy) {
|
||||||
return nil, fmt.Errorf("Invalid key policy: %#v", kp)
|
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
|
// Validate the node policies
|
||||||
|
@ -147,6 +188,9 @@ func Parse(rules string) (*Policy, error) {
|
||||||
if !isPolicyValid(np.Policy) {
|
if !isPolicyValid(np.Policy) {
|
||||||
return nil, fmt.Errorf("Invalid node policy: %#v", np)
|
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
|
// Validate the service policies
|
||||||
|
@ -154,6 +198,9 @@ func Parse(rules string) (*Policy, error) {
|
||||||
if !isPolicyValid(sp.Policy) {
|
if !isPolicyValid(sp.Policy) {
|
||||||
return nil, fmt.Errorf("Invalid service policy: %#v", sp)
|
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
|
// Validate the session policies
|
||||||
|
|
|
@ -163,7 +163,7 @@ query "bar" {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := Parse(inp)
|
out, err := Parse(inp, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -362,7 +362,7 @@ keyring = ""
|
||||||
Keyring: "",
|
Keyring: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := Parse(inp)
|
out, err := Parse(inp, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -380,7 +380,7 @@ operator = ""
|
||||||
Operator: "",
|
Operator: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := Parse(inp)
|
out, err := Parse(inp, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -403,7 +403,7 @@ func TestACLPolicy_Bad_Policy(t *testing.T) {
|
||||||
`session "" { policy = "nope" }`,
|
`session "" { policy = "nope" }`,
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
_, err := Parse(c)
|
_, err := Parse(c, nil)
|
||||||
if err == nil || !strings.Contains(err.Error(), "Invalid") {
|
if err == nil || !strings.Contains(err.Error(), "Invalid") {
|
||||||
t.Fatalf("expected policy error, got: %#v", err)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -252,14 +252,14 @@ func (a *Agent) vetServiceRegister(token string, service *structs.NodeService) e
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vet the service itself.
|
// Vet the service itself.
|
||||||
if !rule.ServiceWrite(service.Service) {
|
if !rule.ServiceWrite(service.Service, nil) {
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vet any service that might be getting overwritten.
|
// Vet any service that might be getting overwritten.
|
||||||
services := a.state.Services()
|
services := a.state.Services()
|
||||||
if existing, ok := services[service.ID]; ok {
|
if existing, ok := services[service.ID]; ok {
|
||||||
if !rule.ServiceWrite(existing.Service) {
|
if !rule.ServiceWrite(existing.Service, nil) {
|
||||||
return acl.ErrPermissionDenied
|
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.
|
// Vet any changes based on the existing services's info.
|
||||||
services := a.state.Services()
|
services := a.state.Services()
|
||||||
if existing, ok := services[serviceID]; ok {
|
if existing, ok := services[serviceID]; ok {
|
||||||
if !rule.ServiceWrite(existing.Service) {
|
if !rule.ServiceWrite(existing.Service, nil) {
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -306,11 +306,11 @@ func (a *Agent) vetCheckRegister(token string, check *structs.HealthCheck) error
|
||||||
|
|
||||||
// Vet the check itself.
|
// Vet the check itself.
|
||||||
if len(check.ServiceName) > 0 {
|
if len(check.ServiceName) > 0 {
|
||||||
if !rule.ServiceWrite(check.ServiceName) {
|
if !rule.ServiceWrite(check.ServiceName, nil) {
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !rule.NodeWrite(a.config.NodeName) {
|
if !rule.NodeWrite(a.config.NodeName, nil) {
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,11 +319,11 @@ func (a *Agent) vetCheckRegister(token string, check *structs.HealthCheck) error
|
||||||
checks := a.state.Checks()
|
checks := a.state.Checks()
|
||||||
if existing, ok := checks[check.CheckID]; ok {
|
if existing, ok := checks[check.CheckID]; ok {
|
||||||
if len(existing.ServiceName) > 0 {
|
if len(existing.ServiceName) > 0 {
|
||||||
if !rule.ServiceWrite(existing.ServiceName) {
|
if !rule.ServiceWrite(existing.ServiceName, nil) {
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !rule.NodeWrite(a.config.NodeName) {
|
if !rule.NodeWrite(a.config.NodeName, nil) {
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -347,11 +347,11 @@ func (a *Agent) vetCheckUpdate(token string, checkID types.CheckID) error {
|
||||||
checks := a.state.Checks()
|
checks := a.state.Checks()
|
||||||
if existing, ok := checks[checkID]; ok {
|
if existing, ok := checks[checkID]; ok {
|
||||||
if len(existing.ServiceName) > 0 {
|
if len(existing.ServiceName) > 0 {
|
||||||
if !rule.ServiceWrite(existing.ServiceName) {
|
if !rule.ServiceWrite(existing.ServiceName, nil) {
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !rule.NodeWrite(a.config.NodeName) {
|
if !rule.NodeWrite(a.config.NodeName, nil) {
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,7 +207,7 @@ func TestACL_Special_IDs(t *testing.T) {
|
||||||
if !acl.NodeRead("hello") {
|
if !acl.NodeRead("hello") {
|
||||||
t.Fatalf("should be able to read any node")
|
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")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
return nil, acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"github.com/armon/go-metrics"
|
"github.com/armon/go-metrics"
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
"github.com/hashicorp/consul/api"
|
||||||
|
"github.com/hashicorp/consul/sentinel"
|
||||||
"github.com/hashicorp/golang-lru"
|
"github.com/hashicorp/golang-lru"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -102,6 +104,9 @@ type aclCache struct {
|
||||||
// acls is a non-authoritative ACL cache.
|
// acls is a non-authoritative ACL cache.
|
||||||
acls *lru.TwoQueueCache
|
acls *lru.TwoQueueCache
|
||||||
|
|
||||||
|
// sentinel is the code engine (can be nil).
|
||||||
|
sentinel sentinel.Evaluator
|
||||||
|
|
||||||
// aclPolicyCache is a non-authoritative policy cache.
|
// aclPolicyCache is a non-authoritative policy cache.
|
||||||
policies *lru.TwoQueueCache
|
policies *lru.TwoQueueCache
|
||||||
|
|
||||||
|
@ -116,13 +121,14 @@ type aclCache struct {
|
||||||
// newACLCache returns a new non-authoritative cache for ACLs. This is used for
|
// 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
|
// performance, and is used inside the ACL datacenter on non-leader servers, and
|
||||||
// outside the ACL datacenter everywhere.
|
// 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
|
var err error
|
||||||
cache := &aclCache{
|
cache := &aclCache{
|
||||||
config: conf,
|
config: conf,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
rpc: rpc,
|
rpc: rpc,
|
||||||
local: local,
|
local: local,
|
||||||
|
sentinel: sentinel,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the non-authoritative ACL cache
|
// Initialize the non-authoritative ACL cache
|
||||||
|
@ -206,7 +212,7 @@ func (c *aclCache) lookupACL(id, authDC string) (acl.ACL, error) {
|
||||||
goto ACL_DOWN
|
goto ACL_DOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
policy, err := acl.Parse(rules)
|
policy, err := acl.Parse(rules, c.sentinel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Printf("[DEBUG] consul.acl: Failed to parse policy for replicated ACL: %v", err)
|
c.logger.Printf("[DEBUG] consul.acl: Failed to parse policy for replicated ACL: %v", err)
|
||||||
goto ACL_DOWN
|
goto ACL_DOWN
|
||||||
|
@ -268,7 +274,7 @@ func (c *aclCache) useACLPolicy(id, authDC string, cached *aclCacheEntry, p *str
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile the ACL
|
// Compile the ACL
|
||||||
acl, err := acl.New(parent, p.Policy)
|
acl, err := acl.New(parent, p.Policy, c.sentinel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -327,7 +333,6 @@ func (f *aclFilter) allowService(service string) bool {
|
||||||
if !f.enforceVersion8 && service == structs.ConsulServiceID {
|
if !f.enforceVersion8 && service == structs.ConsulServiceID {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.acl.ServiceRead(service)
|
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
|
// filterACL is used to filter results from our service catalog based on the
|
||||||
// rules configured for the provided token. The subject is scrubbed and
|
// rules configured for the provided token.
|
||||||
// modified in-place, leaving only resources the token can access.
|
|
||||||
func (s *Server) filterACL(token string, subj interface{}) error {
|
func (s *Server) filterACL(token string, subj interface{}) error {
|
||||||
// Get the ACL from the token
|
// Get the ACL from the token
|
||||||
acl, err := s.resolveToken(token)
|
acl, err := s.resolveToken(token)
|
||||||
|
@ -646,11 +650,45 @@ func vetRegisterWithACL(rule acl.ACL, subj *structs.RegisterRequest,
|
||||||
return nil
|
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
|
// Vet the node info. This allows service updates to re-post the required
|
||||||
// node info for each request without having to have node "write"
|
// node info for each request without having to have node "write"
|
||||||
// privileges.
|
// privileges.
|
||||||
needsNode := ns == nil || subj.ChangesNode(ns.Node)
|
needsNode := ns == nil || subj.ChangesNode(ns.Node)
|
||||||
if needsNode && !rule.NodeWrite(subj.Node) {
|
|
||||||
|
if needsNode && !rule.NodeWrite(subj.Node, scope) {
|
||||||
return acl.ErrPermissionDenied
|
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
|
// the given service, and that we can write to any existing service that
|
||||||
// is being modified by id (if any).
|
// is being modified by id (if any).
|
||||||
if subj.Service != nil {
|
if subj.Service != nil {
|
||||||
if !rule.ServiceWrite(subj.Service.Service) {
|
if !rule.ServiceWrite(subj.Service.Service, scope) {
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
if ns != nil {
|
if ns != nil {
|
||||||
other, ok := ns.Services[subj.Service.ID]
|
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
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -693,7 +735,7 @@ func vetRegisterWithACL(rule acl.ACL, subj *structs.RegisterRequest,
|
||||||
|
|
||||||
// Node-level check.
|
// Node-level check.
|
||||||
if check.ServiceID == "" {
|
if check.ServiceID == "" {
|
||||||
if !rule.NodeWrite(subj.Node) {
|
if !rule.NodeWrite(subj.Node, scope) {
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
continue
|
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)
|
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
|
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.
|
// be nil; similar for the HealthCheck for the referenced health check.
|
||||||
func vetDeregisterWithACL(rule acl.ACL, subj *structs.DeregisterRequest,
|
func vetDeregisterWithACL(rule acl.ACL, subj *structs.DeregisterRequest,
|
||||||
ns *structs.NodeService, nc *structs.HealthCheck) error {
|
ns *structs.NodeService, nc *structs.HealthCheck) error {
|
||||||
|
|
||||||
// Fast path if ACLs are not enabled.
|
// Fast path if ACLs are not enabled.
|
||||||
if rule == nil {
|
if rule == nil {
|
||||||
return 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
|
// 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
|
// also evaluates things in this order, and will ignore fields based on
|
||||||
// this precedence. This lets us also ignore them from an ACL perspective.
|
// 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 {
|
if ns == nil {
|
||||||
return fmt.Errorf("Unknown service '%s'", subj.ServiceID)
|
return fmt.Errorf("Unknown service '%s'", subj.ServiceID)
|
||||||
}
|
}
|
||||||
if !rule.ServiceWrite(ns.Service) {
|
if !rule.ServiceWrite(ns.Service, nil) {
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
} else if subj.CheckID != "" {
|
} else if subj.CheckID != "" {
|
||||||
|
@ -753,16 +802,16 @@ func vetDeregisterWithACL(rule acl.ACL, subj *structs.DeregisterRequest,
|
||||||
return fmt.Errorf("Unknown check '%s'", subj.CheckID)
|
return fmt.Errorf("Unknown check '%s'", subj.CheckID)
|
||||||
}
|
}
|
||||||
if nc.ServiceID != "" {
|
if nc.ServiceID != "" {
|
||||||
if !rule.ServiceWrite(nc.ServiceName) {
|
if !rule.ServiceWrite(nc.ServiceName, nil) {
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !rule.NodeWrite(subj.Node) {
|
if !rule.NodeWrite(subj.Node, nil) {
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !rule.NodeWrite(subj.Node) {
|
if !rule.NodeWrite(subj.Node, nil) {
|
||||||
return acl.ErrPermissionDenied
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,7 @@ func aclApplyInternal(srv *Server, args *structs.ACLRequest, reply *string) erro
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the rules compile
|
// Validate the rules compile
|
||||||
_, err := acl.Parse(args.ACL.Rules)
|
_, err := acl.Parse(args.ACL.Rules, srv.sentinel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ACL rule compilation failed: %v", err)
|
return fmt.Errorf("ACL rule compilation failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -793,11 +793,11 @@ func TestACL_filterHealthChecks(t *testing.T) {
|
||||||
service "foo" {
|
service "foo" {
|
||||||
policy = "read"
|
policy = "read"
|
||||||
}
|
}
|
||||||
`)
|
`, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err %v", err)
|
t.Fatalf("err %v", err)
|
||||||
}
|
}
|
||||||
perms, err := acl.New(acl.DenyAll(), policy)
|
perms, err := acl.New(acl.DenyAll(), policy, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -827,11 +827,11 @@ service "foo" {
|
||||||
node "node1" {
|
node "node1" {
|
||||||
policy = "read"
|
policy = "read"
|
||||||
}
|
}
|
||||||
`)
|
`, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err %v", err)
|
t.Fatalf("err %v", err)
|
||||||
}
|
}
|
||||||
perms, err = acl.New(perms, policy)
|
perms, err = acl.New(perms, policy, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -918,11 +918,11 @@ func TestACL_filterServiceNodes(t *testing.T) {
|
||||||
service "foo" {
|
service "foo" {
|
||||||
policy = "read"
|
policy = "read"
|
||||||
}
|
}
|
||||||
`)
|
`, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err %v", err)
|
t.Fatalf("err %v", err)
|
||||||
}
|
}
|
||||||
perms, err := acl.New(acl.DenyAll(), policy)
|
perms, err := acl.New(acl.DenyAll(), policy, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -952,11 +952,11 @@ service "foo" {
|
||||||
node "node1" {
|
node "node1" {
|
||||||
policy = "read"
|
policy = "read"
|
||||||
}
|
}
|
||||||
`)
|
`, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err %v", err)
|
t.Fatalf("err %v", err)
|
||||||
}
|
}
|
||||||
perms, err = acl.New(perms, policy)
|
perms, err = acl.New(perms, policy, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1024,11 +1024,11 @@ func TestACL_filterNodeServices(t *testing.T) {
|
||||||
service "foo" {
|
service "foo" {
|
||||||
policy = "read"
|
policy = "read"
|
||||||
}
|
}
|
||||||
`)
|
`, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err %v", err)
|
t.Fatalf("err %v", err)
|
||||||
}
|
}
|
||||||
perms, err := acl.New(acl.DenyAll(), policy)
|
perms, err := acl.New(acl.DenyAll(), policy, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1058,11 +1058,11 @@ service "foo" {
|
||||||
node "node1" {
|
node "node1" {
|
||||||
policy = "read"
|
policy = "read"
|
||||||
}
|
}
|
||||||
`)
|
`, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err %v", err)
|
t.Fatalf("err %v", err)
|
||||||
}
|
}
|
||||||
perms, err = acl.New(perms, policy)
|
perms, err = acl.New(perms, policy, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1130,11 +1130,11 @@ func TestACL_filterCheckServiceNodes(t *testing.T) {
|
||||||
service "foo" {
|
service "foo" {
|
||||||
policy = "read"
|
policy = "read"
|
||||||
}
|
}
|
||||||
`)
|
`, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err %v", err)
|
t.Fatalf("err %v", err)
|
||||||
}
|
}
|
||||||
perms, err := acl.New(acl.DenyAll(), policy)
|
perms, err := acl.New(acl.DenyAll(), policy, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1167,11 +1167,11 @@ service "foo" {
|
||||||
node "node1" {
|
node "node1" {
|
||||||
policy = "read"
|
policy = "read"
|
||||||
}
|
}
|
||||||
`)
|
`, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err %v", err)
|
t.Fatalf("err %v", err)
|
||||||
}
|
}
|
||||||
perms, err = acl.New(perms, policy)
|
perms, err = acl.New(perms, policy, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1321,11 +1321,11 @@ func TestACL_filterNodeDump(t *testing.T) {
|
||||||
service "foo" {
|
service "foo" {
|
||||||
policy = "read"
|
policy = "read"
|
||||||
}
|
}
|
||||||
`)
|
`, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err %v", err)
|
t.Fatalf("err %v", err)
|
||||||
}
|
}
|
||||||
perms, err := acl.New(acl.DenyAll(), policy)
|
perms, err := acl.New(acl.DenyAll(), policy, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1361,11 +1361,11 @@ service "foo" {
|
||||||
node "node1" {
|
node "node1" {
|
||||||
policy = "read"
|
policy = "read"
|
||||||
}
|
}
|
||||||
`)
|
`, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err %v", err)
|
t.Fatalf("err %v", err)
|
||||||
}
|
}
|
||||||
perms, err = acl.New(perms, policy)
|
perms, err = acl.New(perms, policy, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1563,11 +1563,11 @@ func TestACL_vetRegisterWithACL(t *testing.T) {
|
||||||
node "node" {
|
node "node" {
|
||||||
policy = "write"
|
policy = "write"
|
||||||
}
|
}
|
||||||
`)
|
`, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err %v", err)
|
t.Fatalf("err %v", err)
|
||||||
}
|
}
|
||||||
perms, err := acl.New(acl.DenyAll(), policy)
|
perms, err := acl.New(acl.DenyAll(), policy, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1608,11 +1608,11 @@ node "node" {
|
||||||
service "service" {
|
service "service" {
|
||||||
policy = "write"
|
policy = "write"
|
||||||
}
|
}
|
||||||
`)
|
`, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err %v", err)
|
t.Fatalf("err %v", err)
|
||||||
}
|
}
|
||||||
perms, err = acl.New(perms, policy)
|
perms, err = acl.New(perms, policy, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1638,11 +1638,11 @@ service "service" {
|
||||||
service "other" {
|
service "other" {
|
||||||
policy = "write"
|
policy = "write"
|
||||||
}
|
}
|
||||||
`)
|
`, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err %v", err)
|
t.Fatalf("err %v", err)
|
||||||
}
|
}
|
||||||
perms, err = acl.New(perms, policy)
|
perms, err = acl.New(perms, policy, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1712,11 +1712,11 @@ service "other" {
|
||||||
service "other" {
|
service "other" {
|
||||||
policy = "deny"
|
policy = "deny"
|
||||||
}
|
}
|
||||||
`)
|
`, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err %v", err)
|
t.Fatalf("err %v", err)
|
||||||
}
|
}
|
||||||
perms, err = acl.New(perms, policy)
|
perms, err = acl.New(perms, policy, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1742,11 +1742,11 @@ service "other" {
|
||||||
node "node" {
|
node "node" {
|
||||||
policy = "deny"
|
policy = "deny"
|
||||||
}
|
}
|
||||||
`)
|
`, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err %v", err)
|
t.Fatalf("err %v", err)
|
||||||
}
|
}
|
||||||
perms, err = acl.New(perms, policy)
|
perms, err = acl.New(perms, policy, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -1792,11 +1792,11 @@ node "node" {
|
||||||
service "service" {
|
service "service" {
|
||||||
policy = "write"
|
policy = "write"
|
||||||
}
|
}
|
||||||
`)
|
`, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err %v", err)
|
t.Fatalf("err %v", err)
|
||||||
}
|
}
|
||||||
perms, err := acl.New(acl.DenyAll(), policy)
|
perms, err := acl.New(acl.DenyAll(), policy, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
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
|
// later if version 0.8 is enabled, so we can eventually just
|
||||||
// delete this and do all the ACL checks down there.
|
// delete this and do all the ACL checks down there.
|
||||||
if args.Service.Service != structs.ConsulServiceName {
|
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
|
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 {
|
if err := vetDeregisterWithACL(rule, args, ns, nc); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := c.srv.raftApply(structs.DeregisterRequestType, args); err != nil {
|
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
|
return err
|
||||||
}
|
}
|
||||||
if rule != nil && c.srv.config.ACLEnforceVersion8 {
|
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
|
return acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@ import (
|
||||||
|
|
||||||
func TestFilter_DirEnt(t *testing.T) {
|
func TestFilter_DirEnt(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
policy, _ := acl.Parse(testFilterRules)
|
policy, _ := acl.Parse(testFilterRules, nil)
|
||||||
aclR, _ := acl.New(acl.DenyAll(), policy)
|
aclR, _ := acl.New(acl.DenyAll(), policy, nil)
|
||||||
|
|
||||||
type tcase struct {
|
type tcase struct {
|
||||||
in []string
|
in []string
|
||||||
|
@ -52,8 +52,8 @@ func TestFilter_DirEnt(t *testing.T) {
|
||||||
|
|
||||||
func TestFilter_Keys(t *testing.T) {
|
func TestFilter_Keys(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
policy, _ := acl.Parse(testFilterRules)
|
policy, _ := acl.Parse(testFilterRules, nil)
|
||||||
aclR, _ := acl.New(acl.DenyAll(), policy)
|
aclR, _ := acl.New(acl.DenyAll(), policy, nil)
|
||||||
|
|
||||||
type tcase struct {
|
type tcase struct {
|
||||||
in []string
|
in []string
|
||||||
|
@ -84,8 +84,8 @@ func TestFilter_Keys(t *testing.T) {
|
||||||
|
|
||||||
func TestFilter_TxnResults(t *testing.T) {
|
func TestFilter_TxnResults(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
policy, _ := acl.Parse(testFilterRules)
|
policy, _ := acl.Parse(testFilterRules, nil)
|
||||||
aclR, _ := acl.New(acl.DenyAll(), policy)
|
aclR, _ := acl.New(acl.DenyAll(), policy, nil)
|
||||||
|
|
||||||
type tcase struct {
|
type tcase struct {
|
||||||
in []string
|
in []string
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/hashicorp/consul/agent/consul/state"
|
"github.com/hashicorp/consul/agent/consul/state"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
|
"github.com/hashicorp/consul/sentinel"
|
||||||
"github.com/hashicorp/go-memdb"
|
"github.com/hashicorp/go-memdb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ type KVS struct {
|
||||||
// must only be done on the leader.
|
// must only be done on the leader.
|
||||||
func kvsPreApply(srv *Server, rule acl.ACL, op api.KVOp, dirEnt *structs.DirEntry) (bool, error) {
|
func kvsPreApply(srv *Server, rule acl.ACL, op api.KVOp, dirEnt *structs.DirEntry) (bool, error) {
|
||||||
// Verify the entry.
|
// Verify the entry.
|
||||||
|
|
||||||
if dirEnt.Key == "" && op != api.KVDeleteTree {
|
if dirEnt.Key == "" && op != api.KVDeleteTree {
|
||||||
return false, fmt.Errorf("Must provide key")
|
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:
|
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
|
return false, acl.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,11 +120,10 @@ func (k *KVS) Get(args *structs.KeyRequest, reply *structs.IndexedDirEntries) er
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
acl, err := k.srv.resolveToken(args.Token)
|
aclRule, err := k.srv.resolveToken(args.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return k.srv.blockingQuery(
|
return k.srv.blockingQuery(
|
||||||
&args.QueryOptions,
|
&args.QueryOptions,
|
||||||
&reply.QueryMeta,
|
&reply.QueryMeta,
|
||||||
|
@ -128,9 +132,10 @@ func (k *KVS) Get(args *structs.KeyRequest, reply *structs.IndexedDirEntries) er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if acl != nil && !acl.KeyRead(args.Key) {
|
if aclRule != nil && !aclRule.KeyRead(args.Key) {
|
||||||
ent = nil
|
ent = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ent == nil {
|
if ent == nil {
|
||||||
// Must provide non-zero index to prevent blocking
|
// Must provide non-zero index to prevent blocking
|
||||||
// Index 1 is impossible anyways (due to Raft internals)
|
// 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/structs"
|
||||||
"github.com/hashicorp/consul/agent/token"
|
"github.com/hashicorp/consul/agent/token"
|
||||||
"github.com/hashicorp/consul/lib"
|
"github.com/hashicorp/consul/lib"
|
||||||
|
"github.com/hashicorp/consul/sentinel"
|
||||||
"github.com/hashicorp/consul/tlsutil"
|
"github.com/hashicorp/consul/tlsutil"
|
||||||
"github.com/hashicorp/consul/types"
|
"github.com/hashicorp/consul/types"
|
||||||
"github.com/hashicorp/raft"
|
"github.com/hashicorp/raft"
|
||||||
|
@ -75,6 +76,9 @@ const (
|
||||||
// Server is Consul server which manages the service discovery,
|
// Server is Consul server which manages the service discovery,
|
||||||
// health checking, DC forwarding, Raft, and multiple Serf pools.
|
// health checking, DC forwarding, Raft, and multiple Serf pools.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
// sentinel is the Sentinel code engine (can be nil).
|
||||||
|
sentinel sentinel.Evaluator
|
||||||
|
|
||||||
// aclAuthCache is the authoritative ACL cache.
|
// aclAuthCache is the authoritative ACL cache.
|
||||||
aclAuthCache *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)
|
s.statsFetcher = NewStatsFetcher(logger, s.connPool, s.config.Datacenter)
|
||||||
|
|
||||||
// Initialize the authoritative ACL cache.
|
// 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 {
|
if err != nil {
|
||||||
s.Shutdown()
|
s.Shutdown()
|
||||||
return nil, fmt.Errorf("Failed to create authoritative ACL cache: %v", err)
|
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() {
|
if s.IsACLReplicationEnabled() {
|
||||||
local = s.aclLocalFault
|
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()
|
s.Shutdown()
|
||||||
return nil, fmt.Errorf("Failed to create non-authoritative ACL cache: %v", err)
|
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