Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
43c1596
Add name field to mutation match
julianKatz Aug 25, 2021
807d981
Add name to constraint match
julianKatz Aug 27, 2021
b551107
Add name matching (with prefix glob support) to constraints (rego)
julianKatz Sep 8, 2021
581008f
Add prefix matching to mutation match
julianKatz Sep 8, 2021
e42fc74
Merge branch 'master' of github.com:open-policy-agent/gatekeeper into…
julianKatz Sep 8, 2021
208b171
Add name match support
julianKatz Sep 8, 2021
cc3a4be
Merge branch 'master' of github.com:open-policy-agent/gatekeeper into…
julianKatz Sep 9, 2021
f2b4a98
Merge branch 'master' of github.com:open-policy-agent/gatekeeper into…
julianKatz Sep 9, 2021
1894500
Add description for name in MatchSchema and mutation Match
julianKatz Sep 9, 2021
30b7515
Add pattern validation for `name` in match
julianKatz Sep 9, 2021
5eea07a
Use the pre-existing PrefixWildcard type for all prefix-matching
julianKatz Sep 9, 2021
2c6219d
Remove test for now invalid object name `test-`, as regex makes it in…
julianKatz Sep 9, 2021
d6ac702
Fix a compiler error from missing util.PrefixWildcard
julianKatz Sep 9, 2021
4d1c280
Add name to the website
julianKatz Sep 10, 2021
369a9b5
Removed unused prefixMatch function in mutation
julianKatz Sep 10, 2021
2a46e94
Add prefix-based glob info to both namespaces and excludedNamespaces …
julianKatz Sep 17, 2021
421aa72
Merge branch 'master' of github.com:open-policy-agent/gatekeeper into…
julianKatz Sep 17, 2021
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
2 changes: 2 additions & 0 deletions config/crd/bases/mutations.gatekeeper.sh_assign.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
name:
type: string
Comment thread
julianKatz marked this conversation as resolved.
namespaceSelector:
description: A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
name:
type: string
namespaceSelector:
description: A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.
properties:
Expand Down
2 changes: 2 additions & 0 deletions config/crd/bases/mutations.gatekeeper.sh_modifyset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
name:
type: string
namespaceSelector:
description: A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
name:
type: string
namespaceSelector:
description: A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
name:
type: string
namespaceSelector:
description: A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ spec:
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
name:
type: string
namespaceSelector:
description: A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.
properties:
Expand Down
13 changes: 13 additions & 0 deletions pkg/mutation/match/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type Match struct {
ExcludedNamespaces []string `json:"excludedNamespaces,omitempty"`
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"`
Name string `json:"name,omitempty"`
}

// Kinds accepts a list of objects with apiGroups and kinds fields
Expand Down Expand Up @@ -79,6 +80,7 @@ func Matches(match *Match, obj client.Object, ns *corev1.Namespace) (bool, error
excludedNamespacesMatch,
labelSelectorMatch,
namespaceSelectorMatch,
namesMatch,
}

for _, fn := range topLevelMatchers {
Expand Down Expand Up @@ -206,6 +208,17 @@ func kindsMatch(match *Match, obj client.Object, ns *corev1.Namespace) (bool, er
return false, nil
}

func namesMatch(match *Match, obj client.Object, ns *corev1.Namespace) (bool, error) {
// A blank string could be undefined or an intentional blank string by the user. Either way,
// we will assume this means "any name". This goes with the undefined == match everything
// pattern that we've already got going in the Match.
if match.Name == "" {
return true, nil
}

return match.Name == obj.GetName() || prefixMatch(match.Name, obj.GetName()), nil
}

func scopeMatch(match *Match, obj client.Object, ns *corev1.Namespace) (bool, error) {
clusterScoped := ns == nil || isNamespace(obj)

Expand Down
46 changes: 46 additions & 0 deletions pkg/mutation/match/match_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,52 @@ func TestMatch(t *testing.T) {
},
shouldMatch: false,
},
{
tname: "match name",
toMatch: makeObject("kind", "group", "namespace", "name-foo"),
match: Match{
Name: "name-foo",
},
namespace: &corev1.Namespace{},
shouldMatch: true,
},
{
tname: "match wildcard name",
toMatch: makeObject("kind", "group", "namespace", "name-foo"),
match: Match{
Name: "name-*",
},
namespace: &corev1.Namespace{},
shouldMatch: true,
},
{
tname: "missing asterisk in name wildcard does not match",
toMatch: makeObject("kind", "group", "namespace", "name-foo"),
match: Match{
Name: "name-",
},
namespace: &corev1.Namespace{},
shouldMatch: false,
},
{
tname: "wrong name does not match",
toMatch: makeObject("kind", "group", "namespace", "name-foo"),
match: Match{
Name: "name-bar",
},
namespace: &corev1.Namespace{},
shouldMatch: false,
},
{
tname: "no match with correct name and wrong namespace",
toMatch: makeObject("kind", "group", "namespace", "name-foo"),
match: Match{
Name: "name-foo",
Namespaces: []string{"other-namespace"},
},
namespace: &corev1.Namespace{},
shouldMatch: false,
},
}
for _, tc := range table {
t.Run(tc.tname, func(t *testing.T) {
Expand Down
26 changes: 26 additions & 0 deletions pkg/target/regolib/name_selector_test.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package target

test_name_match {
matches_name({"name": "foo"})
with input.review.name as "foo"
}

test_name_no_match {
not matches_name({"name": "bar"})
with input.review.name as "foo"
}

test_no_name_is_match {
matches_name({})
with input.review.name as "foo"
}

test_wildcard_name_match {
matches_name({"name": "foo*"})
with input.review.name as "foobar"
}

test_wildcard_no_asterisk_no_match {
not matches_name({"name": "foo"})
with input.review.name as "foobar"
}
8 changes: 4 additions & 4 deletions pkg/target/regolib/namespace_selector_test.rego
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
package target

test_name_match {
test_namespace_match {
matches_namespaces({"namespaces": ["kube-system", "gatekeeper-system"]})
with input.review.kind as pod_kind
with input.review.namespace as "gatekeeper-system"
}

test_name_no_match {
test_namespace_no_match {
not matches_namespaces({"namespaces": ["kube-system", "gatekeeper-system"]})
with input.review.kind as pod_kind
with input.review.namespace as "burrito"
}

test_name_match_is_ns {
test_namespace_match_is_ns {
matches_namespaces({"namespaces": ["kube-system", "gatekeeper-system"]})
with input.review.kind as ns_kind
with input.review.object.metadata.name as "gatekeeper-system"
}

test_name_no_match_is_ns {
test_namespace_no_match_is_ns {
not matches_namespaces({"namespaces": ["kube-system", "gatekeeper-system"]})
with input.review.kind as ns_kind
with input.review.object.metadata.name as "front-end"
Expand Down
61 changes: 54 additions & 7 deletions pkg/target/regolib/src.rego
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ matching_constraints[constraint] {

matches_scope(match)

matches_name(match)

label_selector := get_default(match, "labelSelector", {})
any_labelselector_match(label_selector)
}
Expand Down Expand Up @@ -152,6 +154,51 @@ kind_matches(ks) {
ks.kinds[_] == input.review.kind.kind
}

#######################
# Name Selector Logic #
#######################

matches_name(match) {
not has_field(match, "name")
}

matches_name(match) {
has_field(match, "name")
input.review.name == match.name
}

matches_name(match) {
has_field(match, "name")
input.review.object.metadata.name == match.name
}

# oldObject covers Updates and Deletes. The name of an object can't be changed, so there's no
# need to worry about a difference between object and oldObject.
matches_name(match) {
has_field(match, "name")
input.review.oldObject.metadata.name == match.name
}

matches_name(match) {
has_field(match, "name")
wildcard_name_match(match.name, input.review.name)
}

matches_name(match) {
has_field(match, "name")
wildcard_name_match(match.name, input.review.object.metadata.name)
}

matches_name(match) {
has_field(match, "name")
wildcard_name_match(match.name, input.review.oldObject.metadata.name)
}

wildcard_name_match(wildcard, subject) {
endswith(wildcard, "*")
glob.match(wildcard, [], subject)
Comment thread
willbeason marked this conversation as resolved.
}

########################
# Scope Selector Logic #
########################
Expand Down Expand Up @@ -337,17 +384,17 @@ matches_namespaces(match) {
has_field(match, "namespaces")
not always_match_ns_selectors
get_ns_name[ns]
wild_nss := wildcard_namespaces(match.namespaces)
wild_nss := wildcard_tokens(match.namespaces)
prefix_glob_match(wild_nss, ns)
}

wildcard_namespaces(ns_array) = out {
out := [ nss | endswith(ns_array[i], "*")
nss := ns_array[i] ]
wildcard_tokens(token_array) = out {
out := [ wld_tokens | endswith(token_array[i], "*")
wld_tokens := token_array[i] ]
}

prefix_glob_match(match_nss, object_ns) {
glob.match(match_nss[_], [], object_ns)
prefix_glob_match(matchables, subject) {
glob.match(matchables[_], [], subject)
}

does_not_match_excludednamespaces(match) {
Expand All @@ -369,7 +416,7 @@ does_not_match_excludednamespaces(match) {
not nss[ns]

# Check for prefix matches
wild_ex_nss := wildcard_namespaces(match.excludedNamespaces)
wild_ex_nss := wildcard_tokens(match.excludedNamespaces)
not prefix_glob_match(wild_ex_nss, ns)
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/target/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,9 @@ func (h *K8sValidationTarget) MatchSchema() apiextensions.JSONSchemaProps {
"Namespaced",
},
},
"name": {
Type: "string",
},
},
}
}
Expand Down
38 changes: 38 additions & 0 deletions pkg/target/target_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ spec:
}
`
)
const testResourceName = "test-resource"

type buildArg func(*unstructured.Unstructured)

Expand Down Expand Up @@ -102,6 +103,14 @@ func setScope(scope string) buildArg {
}
}

func setName(name string) buildArg {
return func(obj *unstructured.Unstructured) {
if err := unstructured.SetNestedField(obj.Object, name, "spec", "match", "name"); err != nil {
panic(err)
}
}
}

func makeConstraint(o ...buildArg) *unstructured.Unstructured {
u := &unstructured.Unstructured{}
u.SetName("my-constraint")
Expand All @@ -114,6 +123,7 @@ func makeConstraint(o ...buildArg) *unstructured.Unstructured {

func makeResource(group, kind string, labels ...map[string]string) *unstructured.Unstructured {
u := &unstructured.Unstructured{}
u.SetName(testResourceName)
u.SetGroupVersionKind(schema.GroupVersionKind{Group: group, Version: "v1", Kind: kind})
if len(labels) > 0 {
u.SetLabels(labels[0])
Expand Down Expand Up @@ -215,6 +225,34 @@ func TestConstraintEnforcement(t *testing.T) {
constraint: makeConstraint(setKinds([]string{"different"}, []string{"Thing"})),
allowed: true,
},
{
name: "match name",
obj: makeResource("some", "Thing"),
ns: makeNamespace("my-ns"),
constraint: makeConstraint(setName(testResourceName)),
allowed: false,
},
{
name: "no match name",
obj: makeResource("some", "Thing"),
ns: makeNamespace("my-ns"),
constraint: makeConstraint(setName("other-name")),
allowed: true,
},
{
name: "match name wildcard",
obj: makeResource("some", "Thing"),
ns: makeNamespace("my-ns"),
constraint: makeConstraint(setName("test-*")),
allowed: false,
},
{
name: "no match wildcard name when missing asterisk",
obj: makeResource("some", "Thing"),
ns: makeNamespace("my-ns"),
constraint: makeConstraint(setName("test-")),
allowed: true,
},
{
name: "match everything",
obj: makeResource("some", "Thing", map[string]string{"obj": "label"}),
Expand Down
Loading