2025-01-30 00:07:01 +01:00
|
|
|
package matching
|
|
|
|
|
|
|
|
import (
|
|
|
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
"k8s.io/apiserver/pkg/admission"
|
|
|
|
"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
|
|
|
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace"
|
|
|
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/rules"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Matcher interface {
|
2025-01-30 21:36:41 +01:00
|
|
|
Match(criteria matching.MatchCriteria, attr admission.Attributes, namespace runtime.Object) (bool, error)
|
2025-01-30 00:07:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewMatcher() Matcher {
|
|
|
|
return &matcher{}
|
|
|
|
}
|
|
|
|
|
|
|
|
type matcher struct{}
|
|
|
|
|
2025-01-30 21:36:41 +01:00
|
|
|
func (e *matcher) Match(criteria matching.MatchCriteria, attr admission.Attributes, namespace runtime.Object) (bool, error) {
|
2025-01-30 00:07:01 +01:00
|
|
|
matches, matchNsErr := matchNamespace(criteria, namespace)
|
|
|
|
// Should not return an error here for policy which do not apply to the request, even if err is an unexpected scenario.
|
|
|
|
if !matches && matchNsErr == nil {
|
|
|
|
return false, nil
|
|
|
|
}
|
2025-01-30 21:36:41 +01:00
|
|
|
matches, matchObjErr := matchObject(criteria, attr)
|
2025-01-30 00:07:01 +01:00
|
|
|
// Should not return an error here for policy which do not apply to the request, even if err is an unexpected scenario.
|
|
|
|
if !matches && matchObjErr == nil {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
matchResources := criteria.GetMatchResources()
|
|
|
|
if isExcluded, err := matchesResourceRules(matchResources.ExcludeResourceRules, attr); isExcluded || err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
var (
|
|
|
|
isMatch bool
|
|
|
|
matchErr error
|
|
|
|
)
|
|
|
|
if len(matchResources.ResourceRules) == 0 {
|
|
|
|
isMatch = true
|
|
|
|
} else {
|
|
|
|
isMatch, matchErr = matchesResourceRules(matchResources.ResourceRules, attr)
|
|
|
|
}
|
|
|
|
if matchErr != nil {
|
|
|
|
return false, matchErr
|
|
|
|
}
|
|
|
|
if !isMatch {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
// now that we know this applies to this request otherwise, if there were selector errors, return them
|
|
|
|
if matchNsErr != nil {
|
|
|
|
return false, matchNsErr
|
|
|
|
}
|
|
|
|
if matchObjErr != nil {
|
|
|
|
return false, matchObjErr
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func matchNamespace(provider namespace.NamespaceSelectorProvider, namespace runtime.Object) (bool, error) {
|
|
|
|
if namespace == nil {
|
|
|
|
// If the request is about a cluster scoped resource, and it is not a
|
|
|
|
// namespace, it is never exempted.
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
selector, err := provider.GetParsedNamespaceSelector()
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
if selector.Empty() {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
accessor, err := meta.Accessor(namespace)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return selector.Matches(labels.Set(accessor.GetLabels())), nil
|
|
|
|
}
|
|
|
|
|
2025-01-30 21:36:41 +01:00
|
|
|
func _matchObject(obj runtime.Object, selector labels.Selector) bool {
|
|
|
|
if obj == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
accessor, err := meta.Accessor(obj)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return selector.Matches(labels.Set(accessor.GetLabels()))
|
|
|
|
}
|
|
|
|
|
|
|
|
func matchObject(provider namespace.NamespaceSelectorProvider, attr admission.Attributes) (bool, error) {
|
2025-01-30 00:07:01 +01:00
|
|
|
selector, err := provider.GetParsedNamespaceSelector()
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
if selector.Empty() {
|
|
|
|
return true, nil
|
|
|
|
}
|
2025-01-30 21:36:41 +01:00
|
|
|
return _matchObject(attr.GetObject(), selector) || _matchObject(attr.GetOldObject(), selector), nil
|
2025-01-30 00:07:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func matchesResourceRules(namedRules []admissionregistrationv1.NamedRuleWithOperations, attr admission.Attributes) (bool, error) {
|
|
|
|
for _, namedRule := range namedRules {
|
|
|
|
ruleMatcher := rules.Matcher{
|
|
|
|
Rule: namedRule.RuleWithOperations,
|
|
|
|
Attr: attr,
|
|
|
|
}
|
|
|
|
if !ruleMatcher.Matches() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// an empty name list always matches
|
|
|
|
if len(namedRule.ResourceNames) == 0 {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
// TODO: GetName() can return an empty string if the user is relying on
|
|
|
|
// the API server to generate the name... figure out what to do for this edge case
|
|
|
|
name := attr.GetName()
|
|
|
|
for _, matchedName := range namedRule.ResourceNames {
|
|
|
|
if name == matchedName {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false, nil
|
|
|
|
}
|