Skip to content

Commit 112f9f3

Browse files
authored
pan: add ACL policy (#212)
Add an ACL policy following the ACL pattern in https://scion.docs.anapaya.net/en/latest/PathPolicy.html#acl
1 parent 812dfce commit 112f9f3

File tree

2 files changed

+146
-0
lines changed

2 files changed

+146
-0
lines changed

pkg/pan/policy.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package pan
1616

1717
import (
18+
"fmt"
1819
"net"
1920
"sort"
2021

@@ -118,6 +119,45 @@ func (s Sequence) Filter(paths []*Path) []*Path {
118119
return ps
119120
}
120121

122+
// ACL is a policy filtering paths matching an ACL pattern. The ACL pattern is
123+
// an ordered list of allow/deny actions over hop predicates.
124+
// See https://scion.docs.anapaya.net/en/latest/PathPolicy.html#acl.
125+
type ACL struct {
126+
entries *pathpol.ACL
127+
}
128+
129+
// NewACL creates a new ACL from a string list
130+
func NewACL(list []string) (ACL, error) {
131+
aclEntries := make([]*pathpol.ACLEntry, len(list))
132+
for i, entry := range list {
133+
aclEntry := &pathpol.ACLEntry{}
134+
if err := aclEntry.LoadFromString(entry); err != nil {
135+
return ACL{}, fmt.Errorf("parsing ACL entries: %w", err)
136+
}
137+
aclEntries[i] = aclEntry
138+
}
139+
acl, err := pathpol.NewACL(aclEntries...)
140+
if err != nil {
141+
return ACL{}, fmt.Errorf("creating ACL: %w", err)
142+
}
143+
return ACL{entries: acl}, nil
144+
}
145+
146+
// Filter evaluates the interface ACL and returns the set of paths
147+
// that match the list
148+
func (l ACL) Filter(paths []*Path) []*Path {
149+
wps := make([]snet.Path, len(paths))
150+
for i := range paths {
151+
wps[i] = snetPathWrapper{wrapped: paths[i]}
152+
}
153+
wps = l.entries.Eval(wps)
154+
ps := make([]*Path, len(wps))
155+
for i := range wps {
156+
ps[i] = wps[i].(snetPathWrapper).wrapped
157+
}
158+
return ps
159+
}
160+
121161
// snetPathWrapper wraps a *Path to snet.Path, only supporting the minimal
122162
// interface to use pathpol.Sequence
123163
type snetPathWrapper struct {

pkg/pan/policy_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,3 +363,109 @@ func TestSequencePolicy(t *testing.T) {
363363
})
364364
}
365365
}
366+
367+
// TestACLPolicy checks that the glue logic for invoking the pathpol.ACL
368+
// works correctly. We do not need to extensively test the sequence
369+
// language itself here.
370+
func TestACLPolicy(t *testing.T) {
371+
asA := IA{1, 0xff00_0000_000a}
372+
asB := IA{1, 0xff00_0000_000b}
373+
asC := IA{1, 0xff00_0000_000c}
374+
pAB := &Path{
375+
Metadata: &PathMetadata{
376+
Interfaces: []PathInterface{
377+
{IA: asA, IfID: 1},
378+
{IA: asB, IfID: 2},
379+
},
380+
},
381+
}
382+
pACB := &Path{
383+
Metadata: &PathMetadata{
384+
Interfaces: []PathInterface{
385+
{IA: asA, IfID: 1},
386+
{IA: asB, IfID: 2},
387+
{IA: asB, IfID: 3},
388+
{IA: asC, IfID: 4},
389+
},
390+
},
391+
}
392+
cases := []struct {
393+
name string
394+
acl []string
395+
in []*Path
396+
out []*Path
397+
assertFunc assert.ErrorAssertionFunc
398+
}{
399+
{
400+
name: "nil",
401+
acl: nil,
402+
in: nil,
403+
out: []*Path{},
404+
assertFunc: assert.Error,
405+
},
406+
{
407+
name: "empty",
408+
acl: []string{},
409+
in: nil,
410+
out: []*Path{},
411+
assertFunc: assert.Error,
412+
},
413+
{
414+
name: "malformed",
415+
acl: []string{""},
416+
in: nil,
417+
out: []*Path{},
418+
assertFunc: assert.Error,
419+
},
420+
{
421+
name: "any",
422+
acl: []string{"+"},
423+
in: []*Path{pAB, pACB},
424+
out: []*Path{pAB, pACB},
425+
assertFunc: assert.NoError,
426+
},
427+
{
428+
name: "blacklist C",
429+
acl: []string{"- 1-ff00:0:c", "+"},
430+
in: []*Path{pAB, pACB},
431+
out: []*Path{pAB},
432+
assertFunc: assert.NoError,
433+
},
434+
{
435+
name: "blacklist B",
436+
acl: []string{"- 1-ff00:0:B", "+"},
437+
in: []*Path{pAB, pACB},
438+
out: []*Path{},
439+
assertFunc: assert.NoError,
440+
},
441+
{
442+
name: "blacklist C, white ISD 1",
443+
acl: []string{"- 1-ff00:0:C", "+ 1", "-"},
444+
in: []*Path{pAB, pACB},
445+
out: []*Path{pAB},
446+
assertFunc: assert.NoError,
447+
},
448+
{
449+
name: "whitelist only AB",
450+
acl: []string{"+ 1-ff00:0:B", "+ 1-ff00:0:A", "-"},
451+
in: []*Path{pAB, pACB},
452+
out: []*Path{pAB},
453+
assertFunc: assert.NoError,
454+
},
455+
{
456+
name: "none",
457+
acl: []string{"-"},
458+
in: []*Path{pAB, pACB},
459+
out: []*Path{},
460+
assertFunc: assert.NoError,
461+
},
462+
}
463+
for _, c := range cases {
464+
t.Run(c.name, func(t *testing.T) {
465+
acl, err := NewACL(c.acl)
466+
c.assertFunc(t, err)
467+
actual := acl.Filter(c.in)
468+
assert.Equal(t, c.out, actual)
469+
})
470+
}
471+
}

0 commit comments

Comments
 (0)