Skip to content
Open
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
30 changes: 30 additions & 0 deletions policy/condition/func.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,36 @@ func (functions Functions) Keys() KeySet {
return keySet
}

// ValuesByKey returns the literal values every function constrains key to,
// keyed by condition name (for example "StringEquals"). Callers deriving which
// resources a policy permits use it to read the allowed set;
func (functions Functions) ValuesByKey(key Key) map[string][]string {
var byName map[string][]string
for _, f := range functions {
if f.key() != key {
continue
}
values, ok := f.toMap()[key]
if !ok {
continue
}
fname := f.name().String()
for _, v := range values.ToSlice() {
s, err := v.GetString()
if err != nil {
// Non-string values cannot name a resource; skip them so the
// caller sees no constraint rather than a bogus one.
continue
}
if byName == nil {
byName = make(map[string][]string)
}
byName[fname] = append(byName[fname], s)
}
}
return byName
}

// Clone clones Functions structure
func (functions Functions) Clone() Functions {
funcs := []Function{}
Expand Down
76 changes: 76 additions & 0 deletions policy/condition/func_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"encoding/json"
"fmt"
"reflect"
"slices"
"testing"
)

Expand Down Expand Up @@ -123,6 +124,81 @@ func TestFunctionsKeys(t *testing.T) {
}
}

func TestFunctionsValuesByKey(t *testing.T) {
equalsFunc, err := newStringEqualsFunc(S3TablesNamespace.ToKey(), NewValueSet(NewStringValue("ns1"), NewStringValue("ns2")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}

likeFunc, err := newStringLikeFunc(S3TablesNamespace.ToKey(), NewValueSet(NewStringValue("ns3*")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}

// A function on another key must not contribute to the queried key.
otherKeyFunc, err := newStringEqualsFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}

// A non-string value names no resource, so it is skipped rather than
// reported as a constraint the caller could narrow on.
boolFunc, err := newNullFunc(S3TablesNamespace.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}

testCases := []struct {
name string
functions Functions
key Key
expectedResult map[string][]string
}{
{
name: "values group under their condition name",
functions: NewFunctions(equalsFunc, likeFunc),
key: S3TablesNamespace.ToKey(),
expectedResult: map[string][]string{stringEquals: {"ns1", "ns2"}, stringLike: {"ns3*"}},
},
{
name: "another key contributes nothing",
functions: NewFunctions(otherKeyFunc),
key: S3TablesNamespace.ToKey(),
expectedResult: nil,
},
{
name: "a key no function names yields nothing",
functions: NewFunctions(equalsFunc),
key: AWSSourceIP.ToKey(),
expectedResult: nil,
},
{
name: "non-string values are skipped",
functions: NewFunctions(boolFunc),
key: S3TablesNamespace.ToKey(),
expectedResult: nil,
},
{
name: "no functions yields nothing",
functions: NewFunctions(),
key: S3TablesNamespace.ToKey(),
expectedResult: nil,
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
result := testCase.functions.ValuesByKey(testCase.key)
for _, values := range result {
slices.Sort(values)
}
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("expected: %v, got: %v\n", testCase.expectedResult, result)
}
})
}
}

func TestFunctionsMarshalJSON(t *testing.T) {
func1, err := newStringLikeFunc(S3XAmzMetadataDirective.ToKey(), NewValueSet(NewStringValue("REPL*")), "")
if err != nil {
Expand Down
9 changes: 9 additions & 0 deletions policy/condition/name.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ const (
forAnyValue = "ForAnyValue"
)

// IsAllowList reports whether a condition name constrains its key to a set of
// permitted values, as StringEquals and StringLike do. A caller deriving which
// resources a policy reaches can read those values as the reachable set; every
// other form (a negation, a numeric or date comparison) excludes or bounds
// rather than enumerating, so nothing can be derived from it.
func IsAllowList(name string) bool {
return name == stringEquals || name == stringLike
}

// Names - list of all supported condition names.
var Names = map[string]struct{}{
stringEquals: {},
Expand Down
9 changes: 6 additions & 3 deletions policy/condition/stringfunc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,10 @@ func TestBinaryEqualsFuncEvaluate(t *testing.T) {

case4Function, err := newBinaryEqualsFunc(
JWTGroups.ToKey(),
NewValueSet(NewStringValue(
base64.StdEncoding.EncodeToString([]byte("prod"))),
NewValueSet(
NewStringValue(
base64.StdEncoding.EncodeToString([]byte("prod")),
),
NewStringValue(base64.StdEncoding.EncodeToString([]byte("art"))),
),
forAnyValue,
Expand Down Expand Up @@ -573,7 +575,8 @@ func TestStringEqualsFuncToMap(t *testing.T) {
S3XAmzCopySource.ToKey(): NewValueSet(NewStringValue("mybucket/myobject")),
}

case2Function, err := newStringEqualsFunc(S3XAmzCopySource.ToKey(),
case2Function, err := newStringEqualsFunc(
S3XAmzCopySource.ToKey(),
NewValueSet(
NewStringValue("mybucket/myobject"),
NewStringValue("yourbucket/myobject"),
Expand Down
Loading
Loading