mirror of https://github.com/status-im/consul.git
Adds support to ACL package for node policies.
This commit is contained in:
parent
9b4f316b21
commit
7fa4ab3fd1
63
acl/acl.go
63
acl/acl.go
|
@ -65,6 +65,13 @@ type ACL interface {
|
|||
// KeyringWrite determines if the keyring can be manipulated
|
||||
KeyringWrite() bool
|
||||
|
||||
// NodeRead checks for permission to read (discover) a given node.
|
||||
NodeRead(string) bool
|
||||
|
||||
// NodeWrite checks for permission to create or update (register) a
|
||||
// given node.
|
||||
NodeWrite(string) bool
|
||||
|
||||
// OperatorRead determines if the read-only Consul operator functions
|
||||
// can be used.
|
||||
OperatorRead() bool
|
||||
|
@ -84,7 +91,8 @@ type ACL interface {
|
|||
// ServiceRead checks for permission to read a given service
|
||||
ServiceRead(string) bool
|
||||
|
||||
// ServiceWrite checks for permission to read a given service
|
||||
// ServiceWrite checks for permission to create or update a given
|
||||
// service
|
||||
ServiceWrite(string) bool
|
||||
|
||||
// Snapshot checks for permission to take and restore snapshots.
|
||||
|
@ -135,6 +143,14 @@ func (s *StaticACL) KeyringWrite() bool {
|
|||
return s.defaultAllow
|
||||
}
|
||||
|
||||
func (s *StaticACL) NodeRead(string) bool {
|
||||
return s.defaultAllow
|
||||
}
|
||||
|
||||
func (s *StaticACL) NodeWrite(string) bool {
|
||||
return s.defaultAllow
|
||||
}
|
||||
|
||||
func (s *StaticACL) OperatorRead() bool {
|
||||
return s.defaultAllow
|
||||
}
|
||||
|
@ -202,6 +218,9 @@ type PolicyACL struct {
|
|||
// keyRules contains the key policies
|
||||
keyRules *radix.Tree
|
||||
|
||||
// nodeRules contains the node policies
|
||||
nodeRules *radix.Tree
|
||||
|
||||
// serviceRules contains the service policies
|
||||
serviceRules *radix.Tree
|
||||
|
||||
|
@ -226,6 +245,7 @@ func New(parent ACL, policy *Policy) (*PolicyACL, error) {
|
|||
p := &PolicyACL{
|
||||
parent: parent,
|
||||
keyRules: radix.New(),
|
||||
nodeRules: radix.New(),
|
||||
serviceRules: radix.New(),
|
||||
eventRules: radix.New(),
|
||||
preparedQueryRules: radix.New(),
|
||||
|
@ -236,6 +256,11 @@ func New(parent ACL, policy *Policy) (*PolicyACL, error) {
|
|||
p.keyRules.Insert(kp.Prefix, kp.Policy)
|
||||
}
|
||||
|
||||
// Load the node policy
|
||||
for _, np := range policy.Nodes {
|
||||
p.nodeRules.Insert(np.Name, np.Policy)
|
||||
}
|
||||
|
||||
// Load the service policy
|
||||
for _, sp := range policy.Services {
|
||||
p.serviceRules.Insert(sp.Name, sp.Policy)
|
||||
|
@ -404,6 +429,42 @@ func (p *PolicyACL) OperatorRead() bool {
|
|||
}
|
||||
}
|
||||
|
||||
// NodeRead checks if reading (discovery) of a node is allowed
|
||||
func (p *PolicyACL) NodeRead(name string) bool {
|
||||
// Check for an exact rule or catch-all
|
||||
_, rule, ok := p.nodeRules.LongestPrefix(name)
|
||||
|
||||
if ok {
|
||||
switch rule {
|
||||
case PolicyRead, PolicyWrite:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// No matching rule, use the parent.
|
||||
return p.parent.NodeRead(name)
|
||||
}
|
||||
|
||||
// NodeWrite checks if writing (registering) a node is allowed
|
||||
func (p *PolicyACL) NodeWrite(name string) bool {
|
||||
// Check for an exact rule or catch-all
|
||||
_, rule, ok := p.nodeRules.LongestPrefix(name)
|
||||
|
||||
if ok {
|
||||
switch rule {
|
||||
case PolicyWrite:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// No matching rule, use the parent.
|
||||
return p.parent.NodeWrite(name)
|
||||
}
|
||||
|
||||
// OperatorWrite determines if the state-changing operator functions are
|
||||
// allowed.
|
||||
func (p *PolicyACL) OperatorWrite() bool {
|
||||
|
|
101
acl/acl_test.go
101
acl/acl_test.go
|
@ -59,6 +59,12 @@ func TestStaticACL(t *testing.T) {
|
|||
if !all.KeyringWrite() {
|
||||
t.Fatalf("should allow")
|
||||
}
|
||||
if !all.NodeRead("foobar") {
|
||||
t.Fatalf("should allow")
|
||||
}
|
||||
if !all.NodeWrite("foobar") {
|
||||
t.Fatalf("should allow")
|
||||
}
|
||||
if !all.OperatorRead() {
|
||||
t.Fatalf("should allow")
|
||||
}
|
||||
|
@ -111,6 +117,12 @@ func TestStaticACL(t *testing.T) {
|
|||
if none.KeyringWrite() {
|
||||
t.Fatalf("should not allow")
|
||||
}
|
||||
if none.NodeRead("foobar") {
|
||||
t.Fatalf("should not allow")
|
||||
}
|
||||
if none.NodeWrite("foobar") {
|
||||
t.Fatalf("should not allow")
|
||||
}
|
||||
if none.OperatorRead() {
|
||||
t.Fatalf("should now allow")
|
||||
}
|
||||
|
@ -157,6 +169,12 @@ func TestStaticACL(t *testing.T) {
|
|||
if !manage.KeyringWrite() {
|
||||
t.Fatalf("should allow")
|
||||
}
|
||||
if !manage.NodeRead("foobar") {
|
||||
t.Fatalf("should allow")
|
||||
}
|
||||
if !manage.NodeWrite("foobar") {
|
||||
t.Fatalf("should allow")
|
||||
}
|
||||
if !manage.OperatorRead() {
|
||||
t.Fatalf("should allow")
|
||||
}
|
||||
|
@ -560,3 +578,86 @@ func TestPolicyACL_Operator(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPolicyACL_Node(t *testing.T) {
|
||||
deny := DenyAll()
|
||||
policyRoot := &Policy{
|
||||
Nodes: []*NodePolicy{
|
||||
&NodePolicy{
|
||||
Name: "root-nope",
|
||||
Policy: PolicyDeny,
|
||||
},
|
||||
&NodePolicy{
|
||||
Name: "root-ro",
|
||||
Policy: PolicyRead,
|
||||
},
|
||||
&NodePolicy{
|
||||
Name: "root-rw",
|
||||
Policy: PolicyWrite,
|
||||
},
|
||||
&NodePolicy{
|
||||
Name: "override",
|
||||
Policy: PolicyDeny,
|
||||
},
|
||||
},
|
||||
}
|
||||
root, err := New(deny, policyRoot)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
policy := &Policy{
|
||||
Nodes: []*NodePolicy{
|
||||
&NodePolicy{
|
||||
Name: "child-nope",
|
||||
Policy: PolicyDeny,
|
||||
},
|
||||
&NodePolicy{
|
||||
Name: "child-ro",
|
||||
Policy: PolicyRead,
|
||||
},
|
||||
&NodePolicy{
|
||||
Name: "child-rw",
|
||||
Policy: PolicyWrite,
|
||||
},
|
||||
&NodePolicy{
|
||||
Name: "override",
|
||||
Policy: PolicyWrite,
|
||||
},
|
||||
},
|
||||
}
|
||||
acl, err := New(root, policy)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
type nodecase struct {
|
||||
inp string
|
||||
read bool
|
||||
write bool
|
||||
}
|
||||
cases := []nodecase{
|
||||
{"nope", false, false},
|
||||
{"root-nope", false, false},
|
||||
{"root-ro", true, false},
|
||||
{"root-rw", true, true},
|
||||
{"root-nope-prefix", false, false},
|
||||
{"root-ro-prefix", true, false},
|
||||
{"root-rw-prefix", true, true},
|
||||
{"child-nope", false, false},
|
||||
{"child-ro", true, false},
|
||||
{"child-rw", true, true},
|
||||
{"child-nope-prefix", false, false},
|
||||
{"child-ro-prefix", true, false},
|
||||
{"child-rw-prefix", true, true},
|
||||
{"override", true, true},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if c.read != acl.NodeRead(c.inp) {
|
||||
t.Fatalf("Read fail: %#v", c)
|
||||
}
|
||||
if c.write != acl.NodeWrite(c.inp) {
|
||||
t.Fatalf("Write fail: %#v", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ const (
|
|||
type Policy struct {
|
||||
ID string `hcl:"-"`
|
||||
Keys []*KeyPolicy `hcl:"key,expand"`
|
||||
Nodes []*NodePolicy `hcl:"node,expand"`
|
||||
Services []*ServicePolicy `hcl:"service,expand"`
|
||||
Events []*EventPolicy `hcl:"event,expand"`
|
||||
PreparedQueries []*PreparedQueryPolicy `hcl:"query,expand"`
|
||||
|
@ -34,14 +35,24 @@ func (k *KeyPolicy) GoString() string {
|
|||
return fmt.Sprintf("%#v", *k)
|
||||
}
|
||||
|
||||
// NodePolicy represents a policy for a node
|
||||
type NodePolicy struct {
|
||||
Name string `hcl:",key"`
|
||||
Policy string
|
||||
}
|
||||
|
||||
func (n *NodePolicy) GoString() string {
|
||||
return fmt.Sprintf("%#v", *n)
|
||||
}
|
||||
|
||||
// ServicePolicy represents a policy for a service
|
||||
type ServicePolicy struct {
|
||||
Name string `hcl:",key"`
|
||||
Policy string
|
||||
}
|
||||
|
||||
func (k *ServicePolicy) GoString() string {
|
||||
return fmt.Sprintf("%#v", *k)
|
||||
func (s *ServicePolicy) GoString() string {
|
||||
return fmt.Sprintf("%#v", *s)
|
||||
}
|
||||
|
||||
// EventPolicy represents a user event policy.
|
||||
|
@ -60,8 +71,8 @@ type PreparedQueryPolicy struct {
|
|||
Policy string
|
||||
}
|
||||
|
||||
func (e *PreparedQueryPolicy) GoString() string {
|
||||
return fmt.Sprintf("%#v", *e)
|
||||
func (p *PreparedQueryPolicy) GoString() string {
|
||||
return fmt.Sprintf("%#v", *p)
|
||||
}
|
||||
|
||||
// isPolicyValid makes sure the given string matches one of the valid policies.
|
||||
|
@ -100,7 +111,14 @@ func Parse(rules string) (*Policy, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Validate the service policy
|
||||
// Validate the node policies
|
||||
for _, np := range p.Nodes {
|
||||
if !isPolicyValid(np.Policy) {
|
||||
return nil, fmt.Errorf("Invalid node policy: %#v", np)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the service policies
|
||||
for _, sp := range p.Services {
|
||||
if !isPolicyValid(sp.Policy) {
|
||||
return nil, fmt.Errorf("Invalid service policy: %#v", sp)
|
||||
|
|
|
@ -30,6 +30,15 @@ key "foo/bar/baz" {
|
|||
policy = "deny"
|
||||
}
|
||||
keyring = "deny"
|
||||
node "" {
|
||||
policy = "read"
|
||||
}
|
||||
node "foo" {
|
||||
policy = "write"
|
||||
}
|
||||
node "bar" {
|
||||
policy = "deny"
|
||||
}
|
||||
operator = "deny"
|
||||
service "" {
|
||||
policy = "write"
|
||||
|
@ -81,6 +90,20 @@ query "bar" {
|
|||
Policy: PolicyDeny,
|
||||
},
|
||||
},
|
||||
Nodes: []*NodePolicy{
|
||||
&NodePolicy{
|
||||
Name: "",
|
||||
Policy: PolicyRead,
|
||||
},
|
||||
&NodePolicy{
|
||||
Name: "foo",
|
||||
Policy: PolicyWrite,
|
||||
},
|
||||
&NodePolicy{
|
||||
Name: "bar",
|
||||
Policy: PolicyDeny,
|
||||
},
|
||||
},
|
||||
Operator: PolicyDeny,
|
||||
PreparedQueries: []*PreparedQueryPolicy{
|
||||
&PreparedQueryPolicy{
|
||||
|
@ -146,6 +169,17 @@ func TestACLPolicy_Parse_JSON(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"keyring": "deny",
|
||||
"node": {
|
||||
"": {
|
||||
"policy": "read"
|
||||
},
|
||||
"foo": {
|
||||
"policy": "write"
|
||||
},
|
||||
"bar": {
|
||||
"policy": "deny"
|
||||
}
|
||||
},
|
||||
"operator": "deny",
|
||||
"query": {
|
||||
"": {
|
||||
|
@ -201,6 +235,20 @@ func TestACLPolicy_Parse_JSON(t *testing.T) {
|
|||
Policy: PolicyDeny,
|
||||
},
|
||||
},
|
||||
Nodes: []*NodePolicy{
|
||||
&NodePolicy{
|
||||
Name: "",
|
||||
Policy: PolicyRead,
|
||||
},
|
||||
&NodePolicy{
|
||||
Name: "foo",
|
||||
Policy: PolicyWrite,
|
||||
},
|
||||
&NodePolicy{
|
||||
Name: "bar",
|
||||
Policy: PolicyDeny,
|
||||
},
|
||||
},
|
||||
Operator: PolicyDeny,
|
||||
PreparedQueries: []*PreparedQueryPolicy{
|
||||
&PreparedQueryPolicy{
|
||||
|
@ -279,6 +327,7 @@ func TestACLPolicy_Bad_Policy(t *testing.T) {
|
|||
`event "" { policy = "nope" }`,
|
||||
`key "" { policy = "nope" }`,
|
||||
`keyring = "nope"`,
|
||||
`node "" { policy = "nope" }`,
|
||||
`operator = "nope"`,
|
||||
`query "" { policy = "nope" }`,
|
||||
`service "" { policy = "nope" }`,
|
||||
|
|
Loading…
Reference in New Issue