mirror of https://github.com/status-im/consul.git
acl: Expose service policy checks
This commit is contained in:
parent
3695f65292
commit
8ff08819c8
69
acl/acl.go
69
acl/acl.go
|
@ -46,6 +46,12 @@ type ACL interface {
|
||||||
// that deny a write.
|
// that deny a write.
|
||||||
KeyWritePrefix(string) bool
|
KeyWritePrefix(string) bool
|
||||||
|
|
||||||
|
// ServiceWrite checks for permission to read a given service
|
||||||
|
ServiceWrite(string) bool
|
||||||
|
|
||||||
|
// ServiceRead checks for permission to read a given service
|
||||||
|
ServiceRead(string) bool
|
||||||
|
|
||||||
// ACLList checks for permission to list all the ACLs
|
// ACLList checks for permission to list all the ACLs
|
||||||
ACLList() bool
|
ACLList() bool
|
||||||
|
|
||||||
|
@ -73,6 +79,14 @@ func (s *StaticACL) KeyWritePrefix(string) bool {
|
||||||
return s.defaultAllow
|
return s.defaultAllow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StaticACL) ServiceRead(string) bool {
|
||||||
|
return s.defaultAllow
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StaticACL) ServiceWrite(string) bool {
|
||||||
|
return s.defaultAllow
|
||||||
|
}
|
||||||
|
|
||||||
func (s *StaticACL) ACLList() bool {
|
func (s *StaticACL) ACLList() bool {
|
||||||
return s.allowManage
|
return s.allowManage
|
||||||
}
|
}
|
||||||
|
@ -119,20 +133,29 @@ type PolicyACL struct {
|
||||||
|
|
||||||
// keyRules contains the key policies
|
// keyRules contains the key policies
|
||||||
keyRules *radix.Tree
|
keyRules *radix.Tree
|
||||||
|
|
||||||
|
// serviceRules contains the service policies
|
||||||
|
serviceRules map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) (*PolicyACL, error) {
|
||||||
p := &PolicyACL{
|
p := &PolicyACL{
|
||||||
parent: parent,
|
parent: parent,
|
||||||
keyRules: radix.New(),
|
keyRules: radix.New(),
|
||||||
|
serviceRules: make(map[string]string, len(policy.Services)),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
p.keyRules.Insert(kp.Prefix, kp.Policy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load the service policy
|
||||||
|
for _, sp := range policy.Services {
|
||||||
|
p.serviceRules[sp.Name] = sp.Policy
|
||||||
|
}
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,6 +228,48 @@ func (p *PolicyACL) KeyWritePrefix(prefix string) bool {
|
||||||
return p.parent.KeyWritePrefix(prefix)
|
return p.parent.KeyWritePrefix(prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServiceRead checks if reading (discovery) of a service is allowed
|
||||||
|
func (p *PolicyACL) ServiceRead(name string) bool {
|
||||||
|
// Check for an exact rule or catch-all
|
||||||
|
rule, ok := p.serviceRules[name]
|
||||||
|
if !ok {
|
||||||
|
rule, ok = p.serviceRules[""]
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
switch rule {
|
||||||
|
case ServicePolicyWrite:
|
||||||
|
return true
|
||||||
|
case ServicePolicyRead:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No matching rule, use the parent.
|
||||||
|
return p.parent.ServiceRead(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceWrite checks if writing (registering) a service is allowed
|
||||||
|
func (p *PolicyACL) ServiceWrite(name string) bool {
|
||||||
|
// Check for an exact rule or catch-all
|
||||||
|
rule, ok := p.serviceRules[name]
|
||||||
|
if !ok {
|
||||||
|
rule, ok = p.serviceRules[""]
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
switch rule {
|
||||||
|
case ServicePolicyWrite:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No matching rule, use the parent.
|
||||||
|
return p.parent.ServiceWrite(name)
|
||||||
|
}
|
||||||
|
|
||||||
// ACLList checks if listing of ACLs is allowed
|
// ACLList checks if listing of ACLs is allowed
|
||||||
func (p *PolicyACL) ACLList() bool {
|
func (p *PolicyACL) ACLList() bool {
|
||||||
return p.parent.ACLList()
|
return p.parent.ACLList()
|
||||||
|
|
|
@ -41,6 +41,12 @@ func TestStaticACL(t *testing.T) {
|
||||||
if !all.KeyWrite("foobar") {
|
if !all.KeyWrite("foobar") {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
|
if !all.ServiceRead("foobar") {
|
||||||
|
t.Fatalf("should allow")
|
||||||
|
}
|
||||||
|
if !all.ServiceWrite("foobar") {
|
||||||
|
t.Fatalf("should allow")
|
||||||
|
}
|
||||||
if all.ACLList() {
|
if all.ACLList() {
|
||||||
t.Fatalf("should not allow")
|
t.Fatalf("should not allow")
|
||||||
}
|
}
|
||||||
|
@ -54,6 +60,12 @@ func TestStaticACL(t *testing.T) {
|
||||||
if none.KeyWrite("foobar") {
|
if none.KeyWrite("foobar") {
|
||||||
t.Fatalf("should not allow")
|
t.Fatalf("should not allow")
|
||||||
}
|
}
|
||||||
|
if none.ServiceRead("foobar") {
|
||||||
|
t.Fatalf("should not allow")
|
||||||
|
}
|
||||||
|
if none.ServiceWrite("foobar") {
|
||||||
|
t.Fatalf("should not allow")
|
||||||
|
}
|
||||||
if none.ACLList() {
|
if none.ACLList() {
|
||||||
t.Fatalf("should not noneow")
|
t.Fatalf("should not noneow")
|
||||||
}
|
}
|
||||||
|
@ -67,6 +79,12 @@ func TestStaticACL(t *testing.T) {
|
||||||
if !manage.KeyWrite("foobar") {
|
if !manage.KeyWrite("foobar") {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
|
if !manage.ServiceRead("foobar") {
|
||||||
|
t.Fatalf("should allow")
|
||||||
|
}
|
||||||
|
if !manage.ServiceWrite("foobar") {
|
||||||
|
t.Fatalf("should allow")
|
||||||
|
}
|
||||||
if !manage.ACLList() {
|
if !manage.ACLList() {
|
||||||
t.Fatalf("should allow")
|
t.Fatalf("should allow")
|
||||||
}
|
}
|
||||||
|
@ -96,19 +114,33 @@ func TestPolicyACL(t *testing.T) {
|
||||||
Policy: KeyPolicyRead,
|
Policy: KeyPolicyRead,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Services: []*ServicePolicy{
|
||||||
|
&ServicePolicy{
|
||||||
|
Name: "",
|
||||||
|
Policy: ServicePolicyWrite,
|
||||||
|
},
|
||||||
|
&ServicePolicy{
|
||||||
|
Name: "foo",
|
||||||
|
Policy: ServicePolicyRead,
|
||||||
|
},
|
||||||
|
&ServicePolicy{
|
||||||
|
Name: "bar",
|
||||||
|
Policy: ServicePolicyDeny,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
acl, err := New(all, policy)
|
acl, err := New(all, policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
type tcase struct {
|
type keycase struct {
|
||||||
inp string
|
inp string
|
||||||
read bool
|
read bool
|
||||||
write bool
|
write bool
|
||||||
writePrefix bool
|
writePrefix bool
|
||||||
}
|
}
|
||||||
cases := []tcase{
|
cases := []keycase{
|
||||||
{"other", true, true, true},
|
{"other", true, true, true},
|
||||||
{"foo/test", true, true, true},
|
{"foo/test", true, true, true},
|
||||||
{"foo/priv/test", false, false, false},
|
{"foo/priv/test", false, false, false},
|
||||||
|
@ -128,6 +160,26 @@ func TestPolicyACL(t *testing.T) {
|
||||||
t.Fatalf("Write prefix fail: %#v", c)
|
t.Fatalf("Write prefix fail: %#v", c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test the services
|
||||||
|
type servicecase struct {
|
||||||
|
inp string
|
||||||
|
read bool
|
||||||
|
write bool
|
||||||
|
}
|
||||||
|
scases := []servicecase{
|
||||||
|
{"other", true, true},
|
||||||
|
{"foo", true, false},
|
||||||
|
{"bar", false, false},
|
||||||
|
}
|
||||||
|
for _, c := range scases {
|
||||||
|
if c.read != acl.ServiceRead(c.inp) {
|
||||||
|
t.Fatalf("Read fail: %#v", c)
|
||||||
|
}
|
||||||
|
if c.write != acl.ServiceWrite(c.inp) {
|
||||||
|
t.Fatalf("Write fail: %#v", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolicyACL_Parent(t *testing.T) {
|
func TestPolicyACL_Parent(t *testing.T) {
|
||||||
|
@ -143,6 +195,16 @@ func TestPolicyACL_Parent(t *testing.T) {
|
||||||
Policy: KeyPolicyRead,
|
Policy: KeyPolicyRead,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Services: []*ServicePolicy{
|
||||||
|
&ServicePolicy{
|
||||||
|
Name: "other",
|
||||||
|
Policy: ServicePolicyWrite,
|
||||||
|
},
|
||||||
|
&ServicePolicy{
|
||||||
|
Name: "foo",
|
||||||
|
Policy: ServicePolicyRead,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
root, err := New(deny, policyRoot)
|
root, err := New(deny, policyRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -164,19 +226,25 @@ func TestPolicyACL_Parent(t *testing.T) {
|
||||||
Policy: KeyPolicyRead,
|
Policy: KeyPolicyRead,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Services: []*ServicePolicy{
|
||||||
|
&ServicePolicy{
|
||||||
|
Name: "bar",
|
||||||
|
Policy: ServicePolicyDeny,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
acl, err := New(root, policy)
|
acl, err := New(root, policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
type tcase struct {
|
type keycase struct {
|
||||||
inp string
|
inp string
|
||||||
read bool
|
read bool
|
||||||
write bool
|
write bool
|
||||||
writePrefix bool
|
writePrefix bool
|
||||||
}
|
}
|
||||||
cases := []tcase{
|
cases := []keycase{
|
||||||
{"other", false, false, false},
|
{"other", false, false, false},
|
||||||
{"foo/test", true, true, true},
|
{"foo/test", true, true, true},
|
||||||
{"foo/priv/test", true, false, false},
|
{"foo/priv/test", true, false, false},
|
||||||
|
@ -194,4 +262,25 @@ func TestPolicyACL_Parent(t *testing.T) {
|
||||||
t.Fatalf("Write prefix fail: %#v", c)
|
t.Fatalf("Write prefix fail: %#v", c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test the services
|
||||||
|
type servicecase struct {
|
||||||
|
inp string
|
||||||
|
read bool
|
||||||
|
write bool
|
||||||
|
}
|
||||||
|
scases := []servicecase{
|
||||||
|
{"fail", false, false},
|
||||||
|
{"other", true, true},
|
||||||
|
{"foo", true, false},
|
||||||
|
{"bar", false, false},
|
||||||
|
}
|
||||||
|
for _, c := range scases {
|
||||||
|
if c.read != acl.ServiceRead(c.inp) {
|
||||||
|
t.Fatalf("Read fail: %#v", c)
|
||||||
|
}
|
||||||
|
if c.write != acl.ServiceWrite(c.inp) {
|
||||||
|
t.Fatalf("Write fail: %#v", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue