Add topology ACL filter

This commit is contained in:
freddygv 2020-09-28 19:41:47 -06:00
parent ac54bf99b3
commit 21c4708fe9
2 changed files with 181 additions and 0 deletions

View File

@ -1409,6 +1409,21 @@ func (f *aclFilter) filterCheckServiceNodes(nodes *structs.CheckServiceNodes) {
*nodes = csn *nodes = csn
} }
// filterServiceTopology is used to filter upstreams/downstreams based on ACL rules.
// this filter is unlike other in that it also returns whether the result was filtered by ACLs
func (f *aclFilter) filterServiceTopology(topology *structs.ServiceTopology) bool {
numUp := len(topology.Upstreams)
numDown := len(topology.Downstreams)
f.filterCheckServiceNodes(&topology.Upstreams)
f.filterCheckServiceNodes(&topology.Downstreams)
if numUp != len(topology.Upstreams) || numDown != len(topology.Downstreams) {
return true
}
return false
}
// filterDatacenterCheckServiceNodes is used to filter nodes based on ACL rules. // filterDatacenterCheckServiceNodes is used to filter nodes based on ACL rules.
func (f *aclFilter) filterDatacenterCheckServiceNodes(datacenterNodes *map[string]structs.CheckServiceNodes) { func (f *aclFilter) filterDatacenterCheckServiceNodes(datacenterNodes *map[string]structs.CheckServiceNodes) {
dn := *datacenterNodes dn := *datacenterNodes
@ -1846,6 +1861,12 @@ func (r *ACLResolver) filterACLWithAuthorizer(authorizer acl.Authorizer, subj in
case *structs.IndexedCheckServiceNodes: case *structs.IndexedCheckServiceNodes:
filt.filterCheckServiceNodes(&v.Nodes) filt.filterCheckServiceNodes(&v.Nodes)
case *structs.IndexedServiceTopology:
filtered := filt.filterServiceTopology(v.ServiceTopology)
if filtered {
v.FilteredByACLs = true
}
case *structs.DatacenterIndexedCheckServiceNodes: case *structs.DatacenterIndexedCheckServiceNodes:
filt.filterDatacenterCheckServiceNodes(&v.DatacenterNodes) filt.filterDatacenterCheckServiceNodes(&v.DatacenterNodes)

View File

@ -2766,6 +2766,166 @@ node "node1" {
} }
} }
func TestACL_filterServiceTopology(t *testing.T) {
t.Parallel()
// Create some nodes.
fill := func() structs.ServiceTopology {
return structs.ServiceTopology{
Upstreams: structs.CheckServiceNodes{
structs.CheckServiceNode{
Node: &structs.Node{
Node: "node1",
},
Service: &structs.NodeService{
ID: "foo",
Service: "foo",
},
Checks: structs.HealthChecks{
&structs.HealthCheck{
Node: "node1",
CheckID: "check1",
ServiceName: "foo",
},
},
},
},
Downstreams: structs.CheckServiceNodes{
structs.CheckServiceNode{
Node: &structs.Node{
Node: "node2",
},
Service: &structs.NodeService{
ID: "bar",
Service: "bar",
},
Checks: structs.HealthChecks{
&structs.HealthCheck{
Node: "node2",
CheckID: "check1",
ServiceName: "bar",
},
},
},
},
}
}
original := fill()
t.Run("allow all without permissions", func(t *testing.T) {
topo := fill()
f := newACLFilter(acl.AllowAll(), nil)
filtered := f.filterServiceTopology(&topo)
if filtered {
t.Fatalf("should not have been filtered")
}
assert.Equal(t, original, topo)
})
t.Run("deny all without permissions", func(t *testing.T) {
topo := fill()
f := newACLFilter(acl.DenyAll(), nil)
filtered := f.filterServiceTopology(&topo)
if !filtered {
t.Fatalf("should have been marked as filtered")
}
assert.Len(t, topo.Upstreams, 0)
assert.Len(t, topo.Upstreams, 0)
})
t.Run("only upstream permissions", func(t *testing.T) {
rules := `
node "node1" {
policy = "read"
}
service "foo" {
policy = "read"
}`
policy, err := acl.NewPolicyFromSource("", 0, rules, acl.SyntaxLegacy, nil, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
topo := fill()
f := newACLFilter(perms, nil)
filtered := f.filterServiceTopology(&topo)
if !filtered {
t.Fatalf("should have been marked as filtered")
}
assert.Equal(t, original.Upstreams, topo.Upstreams)
assert.Len(t, topo.Downstreams, 0)
})
t.Run("only downstream permissions", func(t *testing.T) {
rules := `
node "node2" {
policy = "read"
}
service "bar" {
policy = "read"
}`
policy, err := acl.NewPolicyFromSource("", 0, rules, acl.SyntaxLegacy, nil, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
topo := fill()
f := newACLFilter(perms, nil)
filtered := f.filterServiceTopology(&topo)
if !filtered {
t.Fatalf("should have been marked as filtered")
}
assert.Equal(t, original.Downstreams, topo.Downstreams)
assert.Len(t, topo.Upstreams, 0)
})
t.Run("upstream and downstream permissions", func(t *testing.T) {
rules := `
node "node1" {
policy = "read"
}
service "foo" {
policy = "read"
}
node "node2" {
policy = "read"
}
service "bar" {
policy = "read"
}`
policy, err := acl.NewPolicyFromSource("", 0, rules, acl.SyntaxLegacy, nil, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
topo := fill()
f := newACLFilter(perms, nil)
filtered := f.filterServiceTopology(&topo)
if filtered {
t.Fatalf("should not have been filtered")
}
original := fill()
assert.Equal(t, original, topo)
})
}
func TestACL_filterCoordinates(t *testing.T) { func TestACL_filterCoordinates(t *testing.T) {
t.Parallel() t.Parallel()
// Create some coordinates. // Create some coordinates.