Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pathpol: ensure deserialized ACL has default rule #4505

Merged
merged 3 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions private/path/pathpol/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ go_test(
"@com_github_golang_mock//gomock:go_default_library",
"@com_github_stretchr_testify//assert:go_default_library",
"@com_github_stretchr_testify//require:go_default_library",
"@in_gopkg_yaml_v2//:go_default_library",
],
)
40 changes: 36 additions & 4 deletions private/path/pathpol/acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
var (
// ErrNoDefault indicates that there is no default acl entry.
ErrNoDefault = errors.New("ACL does not have a default")
// ErrExtraEntries indicates that there extra entries after the default entry.
ErrExtraEntries = errors.New("ACL has unused extra entries after a default entry")
)

type ACL struct {
Expand All @@ -35,8 +37,8 @@ type ACL struct {

// NewACL creates a new entry and checks for the presence of a default action
func NewACL(entries ...*ACLEntry) (*ACL, error) {
if len(entries) == 0 || !entries[len(entries)-1].Rule.matchesAll() {
return nil, ErrNoDefault
if err := validateACL(entries); err != nil {
return nil, err
}
return &ACL{Entries: entries}, nil
}
Expand All @@ -61,15 +63,21 @@ func (a *ACL) MarshalJSON() ([]byte, error) {
}

func (a *ACL) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, &a.Entries)
if err := json.Unmarshal(b, &a.Entries); err != nil {
return err
}
return validateACL(a.Entries)
}

func (a *ACL) MarshalYAML() (interface{}, error) {
return a.Entries, nil
}

func (a *ACL) UnmarshalYAML(unmarshal func(interface{}) error) error {
return unmarshal(&a.Entries)
if err := unmarshal(&a.Entries); err != nil {
return err
}
return validateACL(a.Entries)
}

func (a *ACL) evalPath(pm *snet.PathMetadata) ACLAction {
Expand All @@ -90,6 +98,30 @@ func (a *ACL) evalInterface(iface snet.PathInterface, ingress bool) ACLAction {
panic("Default ACL action missing")
}

func validateACL(entries []*ACLEntry) error {
if len(entries) == 0 {
return ErrNoDefault
}

foundAt := -1
for i, e := range entries {
if e.Rule.matchesAll() {
foundAt = i
break
}
}

if foundAt < 0 {
return ErrNoDefault
}

if foundAt != len(entries)-1 {
return ErrExtraEntries
}

return nil
}

type ACLEntry struct {
Action ACLAction
Rule *HopPredicate
Expand Down
44 changes: 44 additions & 0 deletions private/path/pathpol/acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
package pathpol

import (
"encoding/json"
"testing"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v2"

"github.com/scionproto/scion/pkg/addr"
"github.com/scionproto/scion/pkg/private/common"
Expand Down Expand Up @@ -60,6 +62,48 @@ func TestNewACL(t *testing.T) {
}
}

func TestUnmarshal(t *testing.T) {
tests := map[string]struct {
Input string
ExpectedErr error
}{
"No entry": {
Input: `[]`,
ExpectedErr: ErrNoDefault,
},
"No default entry": {
Input: `["+ 42"]`,
ExpectedErr: ErrNoDefault,
},
"Entry without rule": {
Input: `["+"]`,
},
"Entry with hop predicates": {
Input: `["+ 42", "-"]`,
},
"Extra entries (first)": {
Input: `["-", "+ 27"]`,
ExpectedErr: ErrExtraEntries,
},
"Extra entries (in the middle)": {
Input: `["+ 42", "-", "+ 27", "- 30"]`,
ExpectedErr: ErrExtraEntries,
},
}
for name, test := range tests {
t.Run("json: "+name, func(t *testing.T) {
var acl ACL
err := json.Unmarshal([]byte(test.Input), &acl)
assert.ErrorIs(t, err, test.ExpectedErr)
})
t.Run("yaml: "+name, func(t *testing.T) {
var acl ACL
err := yaml.Unmarshal([]byte(test.Input), &acl)
assert.ErrorIs(t, err, test.ExpectedErr)
})
}
}

func TestACLEntryLoadFromString(t *testing.T) {
tests := map[string]struct {
String string
Expand Down
13 changes: 7 additions & 6 deletions private/path/pathpol/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,16 +586,17 @@ func TestFilterOpt(t *testing.T) {
}

func TestPolicyJsonConversion(t *testing.T) {
acl, err := NewACL(
&ACLEntry{Action: Allow, Rule: mustHopPredicate(t, "42-0#0")},
denyEntry,
)
require.NoError(t, err)

policy := NewPolicy("", nil, nil, []Option{
{
Policy: &ExtPolicy{
Policy: &Policy{
ACL: &ACL{
Entries: []*ACLEntry{
{Action: Allow, Rule: mustHopPredicate(t, "0-0#0")},
denyEntry,
},
},
ACL: acl,
LocalISDAS: &LocalISDAS{
AllowedIAs: []addr.IA{
xtest.MustParseIA("64-123"),
Expand Down
Loading