diff --git a/internal/disk/path_policy.go b/internal/disk/path_policy.go new file mode 100644 index 000000000..d1383381f --- /dev/null +++ b/internal/disk/path_policy.go @@ -0,0 +1,54 @@ +package disk + +import ( + "fmt" + "path" +) + +type PathPolicy struct { + Deny bool // explicitly do not allow this entry + Exact bool // require and exact match, no subdirs +} + +type PathPolicies = PathTrie + +// Create a new PathPolicies trie from a map of path to PathPolicy +func NewPathPolicies(entries map[string]PathPolicy) *PathPolicies { + + noType := make(map[string]interface{}, len(entries)) + + for k, v := range entries { + noType[k] = v + } + + return NewPathTrieFromMap(noType) +} + +// Check a given path at dir against the PathPolicies +func (pol *PathPolicies) Check(dir string) error { + + // Quickly check we have a mountpoint and it is absolute + if dir == "" || dir[0] != '/' { + return fmt.Errorf("mountpoint must be absolute path") + } + + // ensure that only clean mountpoints are valid + if dir != path.Clean(dir) { + return fmt.Errorf("mountpoint must be a canonical path") + } + + node, left := pol.Lookup(dir) + policy, ok := node.Payload.(PathPolicy) + if !ok { + panic("programming error: invalid path trie payload") + } + + // 1) path is explicitly not allowed or + // 2) a subpath was match but an explicit match is required + if policy.Deny || (policy.Exact && len(left) > 0) { + return fmt.Errorf("path '%s ' is not allowed", dir) + } + + // exact match or recursive mountpoints allowed + return nil +} diff --git a/internal/disk/path_policy_test.go b/internal/disk/path_policy_test.go new file mode 100644 index 000000000..2d30c1c90 --- /dev/null +++ b/internal/disk/path_policy_test.go @@ -0,0 +1,50 @@ +package disk + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPathPolicyCheck(t *testing.T) { + assert := assert.New(t) + + entires := map[string]PathPolicy{ + "/": {Exact: true}, + "/boot": {Exact: true}, + "/boot/efi": {Exact: true}, + "/var": {}, + "/var/empty": {Deny: true}, + "/srv": {}, + "/home": {}, + } + + policies := NewPathPolicies(entires) + assert.NotNil(policies) + + tests := map[string]bool{ + "/": true, + "/custom": false, + "/boot": true, + "/boot/grub2": false, + "/boot/efi": true, + "/boot/efi/redora": false, + "/srv": true, + "/srv/www": true, + "/srv/www/data": true, + "/var": true, + "/var/log": true, + "/var/empty": false, + "/var/empty/dir": false, + } + + for k, v := range tests { + err := policies.Check(k) + if v { + assert.NoError(err) + } else { + assert.Errorf(err, "unexpected error for path '%s'", k) + + } + } +}