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

Implement the match logic #932

Merged
merged 3 commits into from
Nov 30, 2020
Merged

Conversation

fedepaol
Copy link
Contributor

@fedepaol fedepaol commented Nov 2, 2020

What this PR does / why we need it:

Implement both a Matches function and an AppliesTo function that checks if the given object matches a Match / ApplyTo.
This tells if a given mutation will need to be applied to a given object.

Which issue(s) this PR fixes (optional, using fixes #<issue number>(, fixes #<issue_number>, ...) format, will close the issue(s) when the PR gets merged):
Fixes #929

Special notes for your reviewer:

Built on top of
#926
#916

The last commit is the relevant one, namely with match.go / match_test.go files.

This assumes that the Match structure will need to be changed according to https://github.com/open-policy-agent/gatekeeper/pull/916/files#r516071473

@fedepaol fedepaol changed the title Matchlogic Implement the match logic Nov 2, 2020
@maxsmythe
Copy link
Contributor

Thanks! I'll take a look at this once we merge some of the other PRs, it's getting a bit hard to track which diff belongs to which PR.

@fedepaol
Copy link
Contributor Author

fedepaol commented Nov 4, 2020

Thanks! I'll take a look at this once we merge some of the other PRs, it's getting a bit hard to track which diff belongs to which PR.

Actually (not asking to), but you could do by reviewing only the last commit d6cff6b

@maxsmythe
Copy link
Contributor

oh, nice! I'll take a look

Copy link
Contributor

@maxsmythe maxsmythe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM @ritazh @shomron thoughts?

@fedepaol
Copy link
Contributor Author

@maxsmythe @ritazh @shomron rebased, ready for 👀

Copy link
Contributor

@maxsmythe maxsmythe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Some comments around the label selector logic

corev1 "k8s.io/api/core/v1"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: merge import blocks

return false, nil
}
}
if match.LabelSelector != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also match the label selector against old object?

It seems weird, but that's how MutatingWebhookConfiguration's logic works:

https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#mutatingwebhook-v1-admissionregistration-k8s-io

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assumed it was only against the old object, as it's the criteria for applying a given mutation.

Given all the stability premises and the fact that we are only adding (and not changing) labels, I think it's safe to assume that if the old object matches the selector, the new one will. If only the new one will, it will be a matter of another round of mutation before the stability.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree the old object/new object premise doesn't really work for mutation. It's a bit weird to be deviating from K8s convention and what we do with admission control, but I think it makes sense.

@shomron @ritazh @sozercan @brycecr thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the last bit, I think apart from this discussion this is good to go.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the above people to the review, hopefully that kick-starts the discussion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on not deviating from K8s mutatingwebhookconfig logic to avoid surprises to users

we are only adding (and not changing) labels

what if users want to match label on the old object then mutate the object to delete that label? is that a supported scenario for MVP?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make sure I understood.
Here defining "the old" vs "the new" is a bit tricky, as we apply a bunch of mutations at the same time, and we can't just refer to the old as "before all mutations" and new as "after all mutations" because the decision of whether applying a mutation or not is based on the match logic. I think what we do here is to test only against "the old" object, with respect to the single mutation.

See

if m.Matches(obj, ns) {

In that case, what is referred as old vs new? And what should happen if we find out that after a mutation the object does not match anymore?
Is it fine to assume that we match against the old, we mutate and then we "rollback" the mutation (i.e restoring a backup of the object) if match of the single mutation fails afterwards?

Re: deleting labels, this is not possible with the current design as we are only adding non-existing labels / annotations. This is needed for making sure the mutation converges.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given all the reasons above, +1 on not testing OldObject for now

Re: deleting labels, this is not possible with the current design as we are only adding non-existing labels / annotations. This is needed for making sure the mutation converges.

Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lemme make sure I'm getting this straight:
If we also match against newObject, then, if a mutation were to add a label to a resource, then all instances of that resource type would match. The other angle on that would be that you couldn't add a label to a subset of objects of one GVK by selecting by label.

I think I don't grok the purpose of applying to both old and new objects unless you allow the mutation webhook to create or delete objects. It seems unexpected at best.

I'm in favor of shirking the k8s standard here and doing what makes more common sense, but I suspect I am missing something, too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One point of confusion is OldObject has a specific meaning, defined here:

https://github.com/kubernetes/api/blob/4a626d306b987a4096cf0784ec01af1be2f6d67f/admission/v1beta1/types.go#L101-L106

I don't think that changes much, if any, of the above analysis, but has been the source of a bit of confusion.

}
}

if match.NamespaceSelector != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For MutatingWebhookConfigurations:

  • Cluster scoped resources always match an NS selector
  • If the object itself is an NS, the namespace selector is evaluated against the object's labels
  • Unclear whether the old object + new object matching logic also applies here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The namespace remains the same, so there won't be difference in new vs old.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented the first two points and added tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The namespace would remain the same unless the object itself is a namespace.

Adding a Match function that accepts a Match instance, an object to be matched against
and its related namespace.

Adding a AppliesTo function that accepts a ApplyTo slice and an object to be matched against.

Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
@fedepaol
Copy link
Contributor Author

Rebased and integrated the two mutators' Matches method with the Matches function.
Also, changed the Matches() method of the mutator interface to accept runtime.Object as metav1.Object do not have kind / group accessors.

Copy link
Contributor

@maxsmythe maxsmythe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return false, nil
}
}
if match.LabelSelector != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the notes in the discussion on the meeting minutes:

https://docs.google.com/document/d/1A1-Q-1OMw3QODs1wT6eqfLTagcGmgzAJAjJihiO3T48/edit#heading=h.5gv5xh7vxp5i

TL;DR my opinion is we not test OldObject for mutation for now, and re-visit the question before graduating from alpha, so we can make this decision based off real usage data.

Each mutator matches uses the Matches function to check if the given object matches
the mutator. This also includes a change in the mutator interface as metav1.Object
do not have accessors for kind / group.

Signed-off-by: Federico Paolinelli <fpaoline@redhat.com>
@codecov-io
Copy link

Codecov Report

Merging #932 (55aaaff) into master (964864d) will increase coverage by 0.86%.
The diff coverage is 80.95%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #932      +/-   ##
==========================================
+ Coverage   46.99%   47.86%   +0.86%     
==========================================
  Files          58       59       +1     
  Lines        3541     3623      +82     
==========================================
+ Hits         1664     1734      +70     
- Misses       1668     1678      +10     
- Partials      209      211       +2     
Flag Coverage Δ
unittests 47.86% <80.95%> (+0.86%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
pkg/mutation/assign_mutator.go 54.71% <0.00%> (-4.47%) ⬇️
pkg/mutation/assignmeta_mutator.go 61.90% <0.00%> (-6.52%) ⬇️
pkg/mutation/mutation.go 100.00% <ø> (ø)
pkg/mutation/mutator.go 77.77% <ø> (ø)
pkg/mutation/match.go 91.89% <91.89%> (ø)
...onstrainttemplate/constrainttemplate_controller.go 55.70% <0.00%> (-0.66%) ⬇️
pkg/readiness/ready_tracker.go 69.81% <0.00%> (+0.72%) ⬆️
pkg/readiness/object_tracker.go 79.14% <0.00%> (+1.22%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 964864d...55aaaff. Read the comment docs.

@fedepaol
Copy link
Contributor Author

Added this last change https://github.com/open-policy-agent/gatekeeper/compare/379dc934fa7a12f7a4a99f080aa3c211a5a5197f..55aaaffc6c91e4ce33787d62e55d26be8a416251

Apart from this, based on the previous discussions I think this one is ready to go.

}
for _, g := range apply.Versions {
if g == gvk.Version {
matchesVersion = true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how do we handle when there are multiple versions of the same group kind in the cluster? this here would resolve to mismatch but the apiserver would consider it a match.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

applyTo should be a strict check, because mutations create implicit schema.

Note that this check is for the mutation.spec.applyTo field, not spec.match.kinds

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I got the point.
ApplyTo allows to specify multiple versions, which would then match (or, apply) to different versions of the same group.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha! Let's make sure we document this. To support multiple versions of the same schema, user needs to add another gvk to the applyTo list in the mutation object.

Copy link
Member

@ritazh ritazh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@maxsmythe maxsmythe merged commit 0af9133 into open-policy-agent:master Nov 30, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Mutation: implement the matching logic
5 participants