Files
releases/storage/disk/paths.go
T
Stephan Renatus 51181a8257 storage/disk: wildcard partition validation, docs caveat (#4519)
A bunch of smaller follow-up tasks to #4381.

* storage/disk_test: check invalid patches with wildcard partition, too
* docs/disk: add caveat re: bundles loaded into memory
* storage/disk: auto-manage /system partitions

If these are found in the user-provided partitions, we'll error out.

* storage/disk: pretty-print partitions with "*" instead of %2A
* storage/disk: respect wildcard-replacement in partition validation

It is now allowed to replace a partition like

    /foo/bar

by

    /foo/*

also if multiple wildcards are used.

Caveats:

You cannot add a wildcard partition like /*/*, since it would overlap
the managed "/system/*" partition.

When attempting to go back from /foo/* to /foo/bar, an error is
raised _unconditionally_ -- we could check the existing data, but
currently don't.

* storage/disk: check prefix when adding wildcard partitions

The previously done check would have falsely returned that there is no problem
when adding a wildcard partition: lookup of "/foo/*" with '*' not interpreted
as a wildcard, but as a string, would yield a not-found, even if there was any
data under /foo/.

Now, we'll check the prefix-until-wildcard. It's more cautious than
theoretically necessary, but safe.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-03-31 09:52:26 +02:00

158 lines
3.4 KiB
Go

// Copyright 2021 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package disk
import (
"fmt"
"sort"
"strings"
"github.com/open-policy-agent/opa/storage"
)
const pathWildcard = "*"
type pathMapper struct {
dataPrefix string
dataPrefixNoTrailingSlash string
policiesPrefix string
}
func newPathMapper(schemaVersion, partitionVersion int64) *pathMapper {
var pm pathMapper
pm.dataPrefix = fmt.Sprintf("/%v/%v/data/", schemaVersion, partitionVersion)
pm.dataPrefixNoTrailingSlash = pm.dataPrefix[:len(pm.dataPrefix)-1]
pm.policiesPrefix = fmt.Sprintf("/%v/%v/policies/", schemaVersion, partitionVersion)
return &pm
}
func (pm *pathMapper) PolicyKey2ID(key []byte) string {
return string(key[len(pm.policiesPrefix):])
}
func (pm *pathMapper) PolicyIDPrefix() []byte {
return []byte(pm.policiesPrefix)
}
func (pm *pathMapper) PolicyID2Key(id string) []byte {
return []byte(pm.policiesPrefix + id)
}
func (pm *pathMapper) DataKey2Path(key []byte) (storage.Path, error) {
p, ok := storage.ParsePathEscaped(string(key))
if !ok {
return nil, &storage.Error{Code: storage.InternalErr, Message: fmt.Sprintf("corrupt key: %s", key)}
}
// skip /<schema_version>/<partition_version>/<data prefix>
return p[3:], nil
}
func (pm *pathMapper) DataPrefix2Key(path storage.Path) ([]byte, error) {
if len(path) == 0 {
return []byte(pm.dataPrefix), nil
}
return []byte(pm.dataPrefixNoTrailingSlash + path.String() + "/"), nil
}
func (pm *pathMapper) DataPath2Key(path storage.Path) ([]byte, error) {
if len(path) == 0 {
return nil, &storage.Error{Code: storage.InternalErr, Message: "empty path"}
}
return []byte(pm.dataPrefixNoTrailingSlash + path.String()), nil
}
type pathSet []storage.Path
func (ps pathSet) String() string {
if len(ps) == 0 {
return "[]"
}
buf := strings.Builder{}
buf.WriteRune('[')
for j, p := range ps.Sorted() {
if j != 0 {
buf.WriteRune(' ')
}
buf.WriteString(toString(p))
}
buf.WriteRune(']')
return buf.String()
}
func (ps pathSet) IsDisjoint() bool {
for i := range ps {
for j := range ps {
if i != j {
if hasPrefixWithWildcard(ps[i], ps[j]) {
return false
}
}
}
}
return true
}
// hasPrefixWithWildcard returns true if p starts with other; respecting
// wildcards
func hasPrefixWithWildcard(p, other storage.Path) bool {
if len(other) > len(p) {
return false
}
for i := range other {
if p[i] == pathWildcard || other[i] == pathWildcard {
continue
}
if p[i] != other[i] {
return false
}
}
return true
}
// isMatchedBy returns true if p starts with other, or is matched by it
// respecting wildcards _in other_ -- not in p.
func isMatchedBy(p, other storage.Path) bool {
if len(other) != len(p) {
return false
}
for i := range other {
if other[i] == pathWildcard {
continue
}
if p[i] != other[i] {
return false
}
}
return true
}
func (ps pathSet) Diff(other pathSet) pathSet {
diff := pathSet{}
for _, x := range ps {
if !other.Contains(x) {
diff = append(diff, x)
}
}
return diff
}
func (ps pathSet) Contains(x storage.Path) bool {
for _, other := range ps {
if x.Equal(other) {
return true
}
}
return false
}
func (ps pathSet) Sorted() []storage.Path {
cpy := make(pathSet, len(ps))
copy(cpy, ps)
sort.Slice(cpy, func(i, j int) bool {
return cpy[i].Compare(cpy[j]) < 0
})
return cpy
}