mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-10 04:27:54 +00:00
Merge pull request #116443 from benluddy/secondary-authz-decision-caching
Cache authz decisions within the scope of validating policy admission.
This commit is contained in:
commit
6ffca50136
@ -265,10 +265,10 @@ var _ Validator = &fakeValidator{}
|
|||||||
|
|
||||||
type fakeValidator struct {
|
type fakeValidator struct {
|
||||||
validationFilter, auditAnnotationFilter, messageFilter *fakeFilter
|
validationFilter, auditAnnotationFilter, messageFilter *fakeFilter
|
||||||
ValidateFunc func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult
|
ValidateFunc func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeValidator) RegisterDefinition(definition *v1alpha1.ValidatingAdmissionPolicy, validateFunc func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult) {
|
func (f *fakeValidator) RegisterDefinition(definition *v1alpha1.ValidatingAdmissionPolicy, validateFunc func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult) {
|
||||||
//Key must be something that we can decipher from the inputs to Validate so using message which will be on the validationCondition object of evalResult
|
//Key must be something that we can decipher from the inputs to Validate so using message which will be on the validationCondition object of evalResult
|
||||||
var key string
|
var key string
|
||||||
if len(definition.Spec.Validations) > 0 {
|
if len(definition.Spec.Validations) > 0 {
|
||||||
@ -285,8 +285,8 @@ func (f *fakeValidator) RegisterDefinition(definition *v1alpha1.ValidatingAdmiss
|
|||||||
validatorMap[key] = f
|
validatorMap[key] = f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeValidator) Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
func (f *fakeValidator) Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return f.ValidateFunc(ctx, versionedAttr, versionedParams, runtimeCELCostBudget)
|
return f.ValidateFunc(ctx, versionedAttr, versionedParams, runtimeCELCostBudget, authz)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Matcher = &fakeMatcher{}
|
var _ Matcher = &fakeMatcher{}
|
||||||
@ -419,7 +419,7 @@ func setupTestCommon(t *testing.T, compiler cel.FilterCompiler, matcher Matcher,
|
|||||||
// Override compiler used by controller for tests
|
// Override compiler used by controller for tests
|
||||||
controller = handler.evaluator.(*celAdmissionController)
|
controller = handler.evaluator.(*celAdmissionController)
|
||||||
controller.policyController.filterCompiler = compiler
|
controller.policyController.filterCompiler = compiler
|
||||||
controller.policyController.newValidator = func(validationFilter cel.Filter, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.Filter, fail *admissionRegistrationv1.FailurePolicyType, authorizer authorizer.Authorizer) Validator {
|
controller.policyController.newValidator = func(validationFilter cel.Filter, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.Filter, fail *admissionRegistrationv1.FailurePolicyType) Validator {
|
||||||
f := validationFilter.(*fakeFilter)
|
f := validationFilter.(*fakeFilter)
|
||||||
v := validatorMap[f.keyId]
|
v := validatorMap[f.keyId]
|
||||||
v.validationFilter = f
|
v.validationFilter = f
|
||||||
@ -770,7 +770,7 @@ func TestBasicPolicyDefinitionFailure(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -840,7 +840,7 @@ func TestDefinitionDoesntMatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -953,7 +953,7 @@ func TestReconfigureBinding(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -1063,7 +1063,7 @@ func TestRemoveDefinition(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -1132,7 +1132,7 @@ func TestRemoveBinding(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -1242,7 +1242,7 @@ func TestInvalidParamSourceInstanceName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -1310,7 +1310,7 @@ func TestEmptyParamSource(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -1412,7 +1412,7 @@ func TestMultiplePoliciesSharedParamType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator1.RegisterDefinition(&policy1, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
validator1.RegisterDefinition(&policy1, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
evaluations1.Add(1)
|
evaluations1.Add(1)
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
@ -1431,7 +1431,7 @@ func TestMultiplePoliciesSharedParamType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator2.RegisterDefinition(&policy2, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
validator2.RegisterDefinition(&policy2, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
evaluations2.Add(1)
|
evaluations2.Add(1)
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
@ -1541,7 +1541,7 @@ func TestNativeTypeParam(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
validator.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
evaluations.Add(1)
|
evaluations.Add(1)
|
||||||
if _, ok := versionedParams.(*v1.ConfigMap); ok {
|
if _, ok := versionedParams.(*v1.ConfigMap); ok {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
@ -1623,7 +1623,7 @@ func TestAuditValidationAction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -1694,7 +1694,7 @@ func TestWarnValidationAction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -1753,7 +1753,7 @@ func TestAllValidationActions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
{
|
{
|
||||||
@ -1824,7 +1824,7 @@ func TestAuditAnnotations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
o, err := meta.Accessor(versionedParams)
|
o, err := meta.Accessor(versionedParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package validatingadmissionpolicy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type authzResult struct {
|
||||||
|
authorized authorizer.Decision
|
||||||
|
reason string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type cachingAuthorizer struct {
|
||||||
|
authorizer authorizer.Authorizer
|
||||||
|
decisions map[string]authzResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCachingAuthorizer(in authorizer.Authorizer) authorizer.Authorizer {
|
||||||
|
return &cachingAuthorizer{
|
||||||
|
authorizer: in,
|
||||||
|
decisions: make(map[string]authzResult),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The attribute accessors known to cache key construction. If this fails to compile, the cache
|
||||||
|
// implementation may need to be updated.
|
||||||
|
var _ authorizer.Attributes = (interface {
|
||||||
|
GetUser() user.Info
|
||||||
|
GetVerb() string
|
||||||
|
IsReadOnly() bool
|
||||||
|
GetNamespace() string
|
||||||
|
GetResource() string
|
||||||
|
GetSubresource() string
|
||||||
|
GetName() string
|
||||||
|
GetAPIGroup() string
|
||||||
|
GetAPIVersion() string
|
||||||
|
IsResourceRequest() bool
|
||||||
|
GetPath() string
|
||||||
|
})(nil)
|
||||||
|
|
||||||
|
// The user info accessors known to cache key construction. If this fails to compile, the cache
|
||||||
|
// implementation may need to be updated.
|
||||||
|
var _ user.Info = (interface {
|
||||||
|
GetName() string
|
||||||
|
GetUID() string
|
||||||
|
GetGroups() []string
|
||||||
|
GetExtra() map[string][]string
|
||||||
|
})(nil)
|
||||||
|
|
||||||
|
// Authorize returns an authorization decision by delegating to another Authorizer. If an equivalent
|
||||||
|
// check has already been performed, a cached result is returned. Not safe for concurrent use.
|
||||||
|
func (ca *cachingAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||||
|
serializableAttributes := authorizer.AttributesRecord{
|
||||||
|
Verb: a.GetVerb(),
|
||||||
|
Namespace: a.GetNamespace(),
|
||||||
|
APIGroup: a.GetAPIGroup(),
|
||||||
|
APIVersion: a.GetAPIVersion(),
|
||||||
|
Resource: a.GetResource(),
|
||||||
|
Subresource: a.GetSubresource(),
|
||||||
|
Name: a.GetName(),
|
||||||
|
ResourceRequest: a.IsResourceRequest(),
|
||||||
|
Path: a.GetPath(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if u := a.GetUser(); u != nil {
|
||||||
|
di := &user.DefaultInfo{
|
||||||
|
Name: u.GetName(),
|
||||||
|
UID: u.GetUID(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Differently-ordered groups or extras could cause otherwise-equivalent checks to
|
||||||
|
// have distinct cache keys.
|
||||||
|
if groups := u.GetGroups(); len(groups) > 0 {
|
||||||
|
di.Groups = make([]string, len(groups))
|
||||||
|
copy(di.Groups, groups)
|
||||||
|
sort.Strings(di.Groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
if extra := u.GetExtra(); len(extra) > 0 {
|
||||||
|
di.Extra = make(map[string][]string, len(extra))
|
||||||
|
for k, vs := range extra {
|
||||||
|
vdupe := make([]string, len(vs))
|
||||||
|
copy(vdupe, vs)
|
||||||
|
sort.Strings(vdupe)
|
||||||
|
di.Extra[k] = vdupe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serializableAttributes.User = di
|
||||||
|
}
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
if err := json.NewEncoder(&b).Encode(serializableAttributes); err != nil {
|
||||||
|
return authorizer.DecisionNoOpinion, "", err
|
||||||
|
}
|
||||||
|
key := b.String()
|
||||||
|
|
||||||
|
if cached, ok := ca.decisions[key]; ok {
|
||||||
|
return cached.authorized, cached.reason, cached.err
|
||||||
|
}
|
||||||
|
|
||||||
|
authorized, reason, err := ca.authorizer.Authorize(ctx, a)
|
||||||
|
|
||||||
|
ca.decisions[key] = authzResult{
|
||||||
|
authorized: authorized,
|
||||||
|
reason: reason,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorized, reason, err
|
||||||
|
}
|
@ -0,0 +1,251 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package validatingadmissionpolicy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCachingAuthorizer(t *testing.T) {
|
||||||
|
type result struct {
|
||||||
|
decision authorizer.Decision
|
||||||
|
reason string
|
||||||
|
error error
|
||||||
|
}
|
||||||
|
|
||||||
|
type invocation struct {
|
||||||
|
attributes authorizer.Attributes
|
||||||
|
expected result
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
calls []invocation
|
||||||
|
backend []result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "hit",
|
||||||
|
calls: []invocation{
|
||||||
|
{
|
||||||
|
attributes: authorizer.AttributesRecord{Name: "test name"},
|
||||||
|
expected: result{
|
||||||
|
decision: authorizer.DecisionAllow,
|
||||||
|
reason: "test reason",
|
||||||
|
error: fmt.Errorf("test error"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributes: authorizer.AttributesRecord{Name: "test name"},
|
||||||
|
expected: result{
|
||||||
|
decision: authorizer.DecisionAllow,
|
||||||
|
reason: "test reason",
|
||||||
|
error: fmt.Errorf("test error"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
backend: []result{
|
||||||
|
{
|
||||||
|
decision: authorizer.DecisionAllow,
|
||||||
|
reason: "test reason",
|
||||||
|
error: fmt.Errorf("test error"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hit with differently-ordered groups",
|
||||||
|
calls: []invocation{
|
||||||
|
{
|
||||||
|
attributes: authorizer.AttributesRecord{
|
||||||
|
User: &user.DefaultInfo{
|
||||||
|
Groups: []string{"a", "b", "c"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: result{
|
||||||
|
decision: authorizer.DecisionAllow,
|
||||||
|
reason: "test reason",
|
||||||
|
error: fmt.Errorf("test error"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributes: authorizer.AttributesRecord{
|
||||||
|
User: &user.DefaultInfo{
|
||||||
|
Groups: []string{"c", "b", "a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: result{
|
||||||
|
decision: authorizer.DecisionAllow,
|
||||||
|
reason: "test reason",
|
||||||
|
error: fmt.Errorf("test error"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
backend: []result{
|
||||||
|
{
|
||||||
|
decision: authorizer.DecisionAllow,
|
||||||
|
reason: "test reason",
|
||||||
|
error: fmt.Errorf("test error"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hit with differently-ordered extra",
|
||||||
|
calls: []invocation{
|
||||||
|
{
|
||||||
|
attributes: authorizer.AttributesRecord{
|
||||||
|
User: &user.DefaultInfo{
|
||||||
|
Extra: map[string][]string{
|
||||||
|
"k": {"a", "b", "c"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: result{
|
||||||
|
decision: authorizer.DecisionAllow,
|
||||||
|
reason: "test reason",
|
||||||
|
error: fmt.Errorf("test error"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributes: authorizer.AttributesRecord{
|
||||||
|
User: &user.DefaultInfo{
|
||||||
|
Extra: map[string][]string{
|
||||||
|
"k": {"c", "b", "a"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: result{
|
||||||
|
decision: authorizer.DecisionAllow,
|
||||||
|
reason: "test reason",
|
||||||
|
error: fmt.Errorf("test error"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
backend: []result{
|
||||||
|
{
|
||||||
|
decision: authorizer.DecisionAllow,
|
||||||
|
reason: "test reason",
|
||||||
|
error: fmt.Errorf("test error"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "miss due to different name",
|
||||||
|
calls: []invocation{
|
||||||
|
{
|
||||||
|
attributes: authorizer.AttributesRecord{Name: "alpha"},
|
||||||
|
expected: result{
|
||||||
|
decision: authorizer.DecisionAllow,
|
||||||
|
reason: "test reason alpha",
|
||||||
|
error: fmt.Errorf("test error alpha"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributes: authorizer.AttributesRecord{Name: "beta"},
|
||||||
|
expected: result{
|
||||||
|
decision: authorizer.DecisionDeny,
|
||||||
|
reason: "test reason beta",
|
||||||
|
error: fmt.Errorf("test error beta"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
backend: []result{
|
||||||
|
{
|
||||||
|
decision: authorizer.DecisionAllow,
|
||||||
|
reason: "test reason alpha",
|
||||||
|
error: fmt.Errorf("test error alpha"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
decision: authorizer.DecisionDeny,
|
||||||
|
reason: "test reason beta",
|
||||||
|
error: fmt.Errorf("test error beta"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "miss due to different user",
|
||||||
|
calls: []invocation{
|
||||||
|
{
|
||||||
|
attributes: authorizer.AttributesRecord{
|
||||||
|
User: &user.DefaultInfo{Name: "alpha"},
|
||||||
|
},
|
||||||
|
expected: result{
|
||||||
|
decision: authorizer.DecisionAllow,
|
||||||
|
reason: "test reason alpha",
|
||||||
|
error: fmt.Errorf("test error alpha"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributes: authorizer.AttributesRecord{
|
||||||
|
User: &user.DefaultInfo{Name: "beta"},
|
||||||
|
},
|
||||||
|
expected: result{
|
||||||
|
decision: authorizer.DecisionDeny,
|
||||||
|
reason: "test reason beta",
|
||||||
|
error: fmt.Errorf("test error beta"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
backend: []result{
|
||||||
|
{
|
||||||
|
decision: authorizer.DecisionAllow,
|
||||||
|
reason: "test reason alpha",
|
||||||
|
error: fmt.Errorf("test error alpha"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
decision: authorizer.DecisionDeny,
|
||||||
|
reason: "test reason beta",
|
||||||
|
error: fmt.Errorf("test error beta"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
var misses int
|
||||||
|
frontend := newCachingAuthorizer(func() authorizer.Authorizer {
|
||||||
|
return authorizer.AuthorizerFunc(func(_ context.Context, attributes authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||||
|
if misses >= len(tc.backend) {
|
||||||
|
t.Fatalf("got more than expected %d backend invocations", len(tc.backend))
|
||||||
|
}
|
||||||
|
result := tc.backend[misses]
|
||||||
|
misses++
|
||||||
|
return result.decision, result.reason, result.error
|
||||||
|
})
|
||||||
|
}())
|
||||||
|
|
||||||
|
for i, invocation := range tc.calls {
|
||||||
|
decision, reason, err := frontend.Authorize(context.TODO(), invocation.attributes)
|
||||||
|
if decision != invocation.expected.decision {
|
||||||
|
t.Errorf("(call %d of %d) expected decision %v, got %v", i+1, len(tc.calls), invocation.expected.decision, decision)
|
||||||
|
}
|
||||||
|
if reason != invocation.expected.reason {
|
||||||
|
t.Errorf("(call %d of %d) expected reason %q, got %q", i+1, len(tc.calls), invocation.expected.reason, reason)
|
||||||
|
}
|
||||||
|
if err.Error() != invocation.expected.error.Error() {
|
||||||
|
t.Errorf("(call %d of %d) expected error %q, got %q", i+1, len(tc.calls), invocation.expected.error.Error(), err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tc.backend) > misses {
|
||||||
|
t.Errorf("expected %d backend invocations, got %d", len(tc.backend), misses)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -67,6 +67,8 @@ type celAdmissionController struct {
|
|||||||
// A snapshot of the current policy configuration is synced with this field
|
// A snapshot of the current policy configuration is synced with this field
|
||||||
// asynchronously
|
// asynchronously
|
||||||
definitions atomic.Value
|
definitions atomic.Value
|
||||||
|
|
||||||
|
authz authorizer.Authorizer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything someone might need to validate a single ValidatingPolicyDefinition
|
// Everything someone might need to validate a single ValidatingPolicyDefinition
|
||||||
@ -147,8 +149,8 @@ func NewAdmissionController(
|
|||||||
informerFactory.Admissionregistration().V1alpha1().ValidatingAdmissionPolicies().Informer()),
|
informerFactory.Admissionregistration().V1alpha1().ValidatingAdmissionPolicies().Informer()),
|
||||||
generic.NewInformer[*v1alpha1.ValidatingAdmissionPolicyBinding](
|
generic.NewInformer[*v1alpha1.ValidatingAdmissionPolicyBinding](
|
||||||
informerFactory.Admissionregistration().V1alpha1().ValidatingAdmissionPolicyBindings().Informer()),
|
informerFactory.Admissionregistration().V1alpha1().ValidatingAdmissionPolicyBindings().Informer()),
|
||||||
authz,
|
|
||||||
),
|
),
|
||||||
|
authz: authz,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,6 +238,8 @@ func (c *celAdmissionController) Validate(
|
|||||||
}
|
}
|
||||||
policyDatas := c.definitions.Load().([]policyData)
|
policyDatas := c.definitions.Load().([]policyData)
|
||||||
|
|
||||||
|
authz := newCachingAuthorizer(c.authz)
|
||||||
|
|
||||||
for _, definitionInfo := range policyDatas {
|
for _, definitionInfo := range policyDatas {
|
||||||
definition := definitionInfo.lastReconciledValue
|
definition := definitionInfo.lastReconciledValue
|
||||||
matches, matchKind, err := c.policyController.matcher.DefinitionMatches(a, o, definition)
|
matches, matchKind, err := c.policyController.matcher.DefinitionMatches(a, o, definition)
|
||||||
@ -336,7 +340,7 @@ func (c *celAdmissionController) Validate(
|
|||||||
versionedAttr = va
|
versionedAttr = va
|
||||||
}
|
}
|
||||||
|
|
||||||
validationResult := bindingInfo.validator.Validate(ctx, versionedAttr, param, celconfig.RuntimeCELCostBudget)
|
validationResult := bindingInfo.validator.Validate(ctx, versionedAttr, param, celconfig.RuntimeCELCostBudget, authz)
|
||||||
|
|
||||||
for i, decision := range validationResult.Decisions {
|
for i, decision := range validationResult.Decisions {
|
||||||
switch decision.Action {
|
switch decision.Action {
|
||||||
|
@ -36,7 +36,6 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic"
|
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
|
||||||
"k8s.io/apiserver/pkg/cel/environment"
|
"k8s.io/apiserver/pkg/cel/environment"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
"k8s.io/client-go/dynamic/dynamicinformer"
|
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||||
@ -96,11 +95,9 @@ type policyController struct {
|
|||||||
definitionsToBindings map[namespacedName]sets.Set[namespacedName]
|
definitionsToBindings map[namespacedName]sets.Set[namespacedName]
|
||||||
|
|
||||||
client kubernetes.Interface
|
client kubernetes.Interface
|
||||||
|
|
||||||
authz authorizer.Authorizer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type newValidator func(validationFilter cel.Filter, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.Filter, failurePolicy *v1.FailurePolicyType, authorizer authorizer.Authorizer) Validator
|
type newValidator func(validationFilter cel.Filter, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.Filter, failurePolicy *v1.FailurePolicyType) Validator
|
||||||
|
|
||||||
func newPolicyController(
|
func newPolicyController(
|
||||||
restMapper meta.RESTMapper,
|
restMapper meta.RESTMapper,
|
||||||
@ -111,7 +108,6 @@ func newPolicyController(
|
|||||||
matcher Matcher,
|
matcher Matcher,
|
||||||
policiesInformer generic.Informer[*v1alpha1.ValidatingAdmissionPolicy],
|
policiesInformer generic.Informer[*v1alpha1.ValidatingAdmissionPolicy],
|
||||||
bindingsInformer generic.Informer[*v1alpha1.ValidatingAdmissionPolicyBinding],
|
bindingsInformer generic.Informer[*v1alpha1.ValidatingAdmissionPolicyBinding],
|
||||||
authz authorizer.Authorizer,
|
|
||||||
) *policyController {
|
) *policyController {
|
||||||
res := &policyController{}
|
res := &policyController{}
|
||||||
*res = policyController{
|
*res = policyController{
|
||||||
@ -142,7 +138,6 @@ func newPolicyController(
|
|||||||
restMapper: restMapper,
|
restMapper: restMapper,
|
||||||
dynamicClient: dynamicClient,
|
dynamicClient: dynamicClient,
|
||||||
client: client,
|
client: client,
|
||||||
authz: authz,
|
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@ -512,7 +507,7 @@ func (c *policyController) latestPolicyData() []policyData {
|
|||||||
for i := range matchConditions {
|
for i := range matchConditions {
|
||||||
matchExpressionAccessors[i] = (*matchconditions.MatchCondition)(&matchConditions[i])
|
matchExpressionAccessors[i] = (*matchconditions.MatchCondition)(&matchConditions[i])
|
||||||
}
|
}
|
||||||
matcher = matchconditions.NewMatcher(c.filterCompiler.Compile(matchExpressionAccessors, optionalVars, environment.StoredExpressions), c.authz, failurePolicy, "validatingadmissionpolicy", definitionInfo.lastReconciledValue.Name)
|
matcher = matchconditions.NewMatcher(c.filterCompiler.Compile(matchExpressionAccessors, optionalVars, environment.StoredExpressions), failurePolicy, "validatingadmissionpolicy", definitionInfo.lastReconciledValue.Name)
|
||||||
}
|
}
|
||||||
bindingInfo.validator = c.newValidator(
|
bindingInfo.validator = c.newValidator(
|
||||||
c.filterCompiler.Compile(convertv1alpha1Validations(definitionInfo.lastReconciledValue.Spec.Validations), optionalVars, environment.StoredExpressions),
|
c.filterCompiler.Compile(convertv1alpha1Validations(definitionInfo.lastReconciledValue.Spec.Validations), optionalVars, environment.StoredExpressions),
|
||||||
@ -520,7 +515,6 @@ func (c *policyController) latestPolicyData() []policyData {
|
|||||||
c.filterCompiler.Compile(convertv1alpha1AuditAnnotations(definitionInfo.lastReconciledValue.Spec.AuditAnnotations), optionalVars, environment.StoredExpressions),
|
c.filterCompiler.Compile(convertv1alpha1AuditAnnotations(definitionInfo.lastReconciledValue.Spec.AuditAnnotations), optionalVars, environment.StoredExpressions),
|
||||||
c.filterCompiler.Compile(convertV1Alpha1MessageExpressions(definitionInfo.lastReconciledValue.Spec.Validations), expressionOptionalVars, environment.StoredExpressions),
|
c.filterCompiler.Compile(convertV1Alpha1MessageExpressions(definitionInfo.lastReconciledValue.Spec.Validations), expressionOptionalVars, environment.StoredExpressions),
|
||||||
failurePolicy,
|
failurePolicy,
|
||||||
c.authz,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
bindingInfos = append(bindingInfos, *bindingInfo)
|
bindingInfos = append(bindingInfos, *bindingInfo)
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ cel.ExpressionAccessor = &ValidationCondition{}
|
var _ cel.ExpressionAccessor = &ValidationCondition{}
|
||||||
@ -85,5 +86,5 @@ type ValidateResult struct {
|
|||||||
type Validator interface {
|
type Validator interface {
|
||||||
// Validate is used to take cel evaluations and convert into decisions
|
// Validate is used to take cel evaluations and convert into decisions
|
||||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||||
Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult
|
Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult
|
||||||
}
|
}
|
||||||
|
@ -42,17 +42,15 @@ type validator struct {
|
|||||||
auditAnnotationFilter cel.Filter
|
auditAnnotationFilter cel.Filter
|
||||||
messageFilter cel.Filter
|
messageFilter cel.Filter
|
||||||
failPolicy *v1.FailurePolicyType
|
failPolicy *v1.FailurePolicyType
|
||||||
authorizer authorizer.Authorizer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewValidator(validationFilter cel.Filter, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.Filter, failPolicy *v1.FailurePolicyType, authorizer authorizer.Authorizer) Validator {
|
func NewValidator(validationFilter cel.Filter, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.Filter, failPolicy *v1.FailurePolicyType) Validator {
|
||||||
return &validator{
|
return &validator{
|
||||||
celMatcher: celMatcher,
|
celMatcher: celMatcher,
|
||||||
validationFilter: validationFilter,
|
validationFilter: validationFilter,
|
||||||
auditAnnotationFilter: auditAnnotationFilter,
|
auditAnnotationFilter: auditAnnotationFilter,
|
||||||
messageFilter: messageFilter,
|
messageFilter: messageFilter,
|
||||||
failPolicy: failPolicy,
|
failPolicy: failPolicy,
|
||||||
authorizer: authorizer,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +70,7 @@ func auditAnnotationEvaluationForError(f v1.FailurePolicyType) PolicyAuditAnnota
|
|||||||
|
|
||||||
// Validate takes a list of Evaluation and a failure policy and converts them into actionable PolicyDecisions
|
// Validate takes a list of Evaluation and a failure policy and converts them into actionable PolicyDecisions
|
||||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||||
func (v *validator) Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) ValidateResult {
|
func (v *validator) Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||||
var f v1.FailurePolicyType
|
var f v1.FailurePolicyType
|
||||||
if v.failPolicy == nil {
|
if v.failPolicy == nil {
|
||||||
f = v1.Fail
|
f = v1.Fail
|
||||||
@ -81,7 +79,7 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if v.celMatcher != nil {
|
if v.celMatcher != nil {
|
||||||
matchResults := v.celMatcher.Match(ctx, versionedAttr, versionedParams)
|
matchResults := v.celMatcher.Match(ctx, versionedAttr, versionedParams, authz)
|
||||||
if matchResults.Error != nil {
|
if matchResults.Error != nil {
|
||||||
return ValidateResult{
|
return ValidateResult{
|
||||||
Decisions: []PolicyDecision{
|
Decisions: []PolicyDecision{
|
||||||
@ -100,7 +98,7 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
optionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams, Authorizer: v.authorizer}
|
optionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams, Authorizer: authz}
|
||||||
expressionOptionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams}
|
expressionOptionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams}
|
||||||
admissionRequest := cel.CreateAdmissionRequest(versionedAttr.Attributes)
|
admissionRequest := cel.CreateAdmissionRequest(versionedAttr.Attributes)
|
||||||
evalResults, remainingBudget, err := v.validationFilter.ForInput(ctx, versionedAttr, admissionRequest, optionalVars, runtimeCELCostBudget)
|
evalResults, remainingBudget, err := v.validationFilter.ForInput(ctx, versionedAttr, admissionRequest, optionalVars, runtimeCELCostBudget)
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||||
"k8s.io/apiserver/pkg/cel/environment"
|
"k8s.io/apiserver/pkg/cel/environment"
|
||||||
)
|
)
|
||||||
@ -70,7 +71,7 @@ type fakeCELMatcher struct {
|
|||||||
matches bool
|
matches bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeCELMatcher) Match(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object) matchconditions.MatchResult {
|
func (f *fakeCELMatcher) Match(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, authz authorizer.Authorizer) matchconditions.MatchResult {
|
||||||
return matchconditions.MatchResult{Matches: f.matches, FailedConditionName: "placeholder", Error: f.error}
|
return matchconditions.MatchResult{Matches: f.matches, FailedConditionName: "placeholder", Error: f.error}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -891,7 +892,7 @@ func TestValidate(t *testing.T) {
|
|||||||
if tc.costBudget != 0 {
|
if tc.costBudget != 0 {
|
||||||
budget = tc.costBudget
|
budget = tc.costBudget
|
||||||
}
|
}
|
||||||
validateResult := v.Validate(ctx, fakeVersionedAttr, nil, budget)
|
validateResult := v.Validate(ctx, fakeVersionedAttr, nil, budget, nil)
|
||||||
|
|
||||||
require.Equal(t, len(validateResult.Decisions), len(tc.policyDecision))
|
require.Equal(t, len(validateResult.Decisions), len(tc.policyDecision))
|
||||||
|
|
||||||
@ -943,7 +944,7 @@ func TestContextCanceled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(context.TODO())
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
cancel()
|
cancel()
|
||||||
validationResult := v.Validate(ctx, fakeVersionedAttr, nil, celconfig.RuntimeCELCostBudget)
|
validationResult := v.Validate(ctx, fakeVersionedAttr, nil, celconfig.RuntimeCELCostBudget, nil)
|
||||||
if len(validationResult.Decisions) != 1 || !strings.Contains(validationResult.Decisions[0].Message, "operation interrupted") {
|
if len(validationResult.Decisions) != 1 || !strings.Contains(validationResult.Decisions[0].Message, "operation interrupted") {
|
||||||
t.Errorf("Expected 'operation interrupted' but got %v", validationResult.Decisions)
|
t.Errorf("Expected 'operation interrupted' but got %v", validationResult.Decisions)
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace"
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object"
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
|
||||||
"k8s.io/apiserver/pkg/cel/environment"
|
"k8s.io/apiserver/pkg/cel/environment"
|
||||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
@ -49,7 +48,7 @@ type WebhookAccessor interface {
|
|||||||
GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error)
|
GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error)
|
||||||
|
|
||||||
// GetCompiledMatcher gets the compiled matcher object
|
// GetCompiledMatcher gets the compiled matcher object
|
||||||
GetCompiledMatcher(compiler cel.FilterCompiler, authorizer authorizer.Authorizer) matchconditions.Matcher
|
GetCompiledMatcher(compiler cel.FilterCompiler) matchconditions.Matcher
|
||||||
|
|
||||||
// GetName gets the webhook Name field. Note that the name is scoped to the webhook
|
// GetName gets the webhook Name field. Note that the name is scoped to the webhook
|
||||||
// configuration and does not provide a globally unique identity, if a unique identity is
|
// configuration and does not provide a globally unique identity, if a unique identity is
|
||||||
@ -125,7 +124,7 @@ func (m *mutatingWebhookAccessor) GetRESTClient(clientManager *webhookutil.Clien
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: graduation to beta: resolve the fact that we rebuild ALL items whenever ANY config changes in NewMutatingWebhookConfigurationManager and NewValidatingWebhookConfigurationManager ... now that we're doing CEL compilation, we probably want to avoid that
|
// TODO: graduation to beta: resolve the fact that we rebuild ALL items whenever ANY config changes in NewMutatingWebhookConfigurationManager and NewValidatingWebhookConfigurationManager ... now that we're doing CEL compilation, we probably want to avoid that
|
||||||
func (m *mutatingWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompiler, authorizer authorizer.Authorizer) matchconditions.Matcher {
|
func (m *mutatingWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompiler) matchconditions.Matcher {
|
||||||
m.compileMatcher.Do(func() {
|
m.compileMatcher.Do(func() {
|
||||||
expressions := make([]cel.ExpressionAccessor, len(m.MutatingWebhook.MatchConditions))
|
expressions := make([]cel.ExpressionAccessor, len(m.MutatingWebhook.MatchConditions))
|
||||||
for i, matchCondition := range m.MutatingWebhook.MatchConditions {
|
for i, matchCondition := range m.MutatingWebhook.MatchConditions {
|
||||||
@ -141,7 +140,7 @@ func (m *mutatingWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompiler
|
|||||||
HasAuthorizer: true,
|
HasAuthorizer: true,
|
||||||
},
|
},
|
||||||
environment.StoredExpressions,
|
environment.StoredExpressions,
|
||||||
), authorizer, m.FailurePolicy, "validating", m.Name)
|
), m.FailurePolicy, "validating", m.Name)
|
||||||
})
|
})
|
||||||
return m.compiledMatcher
|
return m.compiledMatcher
|
||||||
}
|
}
|
||||||
@ -253,7 +252,7 @@ func (v *validatingWebhookAccessor) GetRESTClient(clientManager *webhookutil.Cli
|
|||||||
return v.client, v.clientErr
|
return v.client, v.clientErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *validatingWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompiler, authorizer authorizer.Authorizer) matchconditions.Matcher {
|
func (v *validatingWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompiler) matchconditions.Matcher {
|
||||||
v.compileMatcher.Do(func() {
|
v.compileMatcher.Do(func() {
|
||||||
expressions := make([]cel.ExpressionAccessor, len(v.ValidatingWebhook.MatchConditions))
|
expressions := make([]cel.ExpressionAccessor, len(v.ValidatingWebhook.MatchConditions))
|
||||||
for i, matchCondition := range v.ValidatingWebhook.MatchConditions {
|
for i, matchCondition := range v.ValidatingWebhook.MatchConditions {
|
||||||
@ -269,7 +268,7 @@ func (v *validatingWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompil
|
|||||||
HasAuthorizer: true,
|
HasAuthorizer: true,
|
||||||
},
|
},
|
||||||
environment.StoredExpressions,
|
environment.StoredExpressions,
|
||||||
), authorizer, v.FailurePolicy, "validating", v.Name)
|
), v.FailurePolicy, "validating", v.Name)
|
||||||
})
|
})
|
||||||
return v.compiledMatcher
|
return v.compiledMatcher
|
||||||
}
|
}
|
||||||
|
@ -225,8 +225,8 @@ func (a *Webhook) ShouldCallHook(ctx context.Context, h webhook.WebhookAccessor,
|
|||||||
return nil, apierrors.NewInternalError(err)
|
return nil, apierrors.NewInternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
matcher := h.GetCompiledMatcher(a.filterCompiler, a.authorizer)
|
matcher := h.GetCompiledMatcher(a.filterCompiler)
|
||||||
matchResult := matcher.Match(ctx, versionedAttr, nil)
|
matchResult := matcher.Match(ctx, versionedAttr, nil, a.authorizer)
|
||||||
|
|
||||||
if matchResult.Error != nil {
|
if matchResult.Error != nil {
|
||||||
klog.Warningf("Failed evaluating match conditions, failing closed %v: %v", h.GetName(), matchResult.Error)
|
klog.Warningf("Failed evaluating match conditions, failing closed %v: %v", h.GetName(), matchResult.Error)
|
||||||
|
@ -54,7 +54,7 @@ type fakeMatcher struct {
|
|||||||
matchResult bool
|
matchResult bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeMatcher) Match(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object) matchconditions.MatchResult {
|
func (f *fakeMatcher) Match(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, authz authorizer.Authorizer) matchconditions.MatchResult {
|
||||||
if f.throwError != nil {
|
if f.throwError != nil {
|
||||||
return matchconditions.MatchResult{
|
return matchconditions.MatchResult{
|
||||||
Matches: true,
|
Matches: true,
|
||||||
@ -76,7 +76,7 @@ type fakeWebhookAccessor struct {
|
|||||||
matchResult bool
|
matchResult bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompiler, authorizer authorizer.Authorizer) matchconditions.Matcher {
|
func (f *fakeWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompiler) matchconditions.Matcher {
|
||||||
return &fakeMatcher{
|
return &fakeMatcher{
|
||||||
throwError: f.throwError,
|
throwError: f.throwError,
|
||||||
matchResult: f.matchResult,
|
matchResult: f.matchResult,
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MatchResult struct {
|
type MatchResult struct {
|
||||||
@ -32,5 +33,5 @@ type MatchResult struct {
|
|||||||
// Matcher contains logic for converting Evaluations to bool of matches or does not match
|
// Matcher contains logic for converting Evaluations to bool of matches or does not match
|
||||||
type Matcher interface {
|
type Matcher interface {
|
||||||
// Match is used to take cel evaluations and convert into decisions
|
// Match is used to take cel evaluations and convert into decisions
|
||||||
Match(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object) MatchResult
|
Match(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, authz authorizer.Authorizer) MatchResult
|
||||||
}
|
}
|
||||||
|
@ -53,13 +53,12 @@ var _ Matcher = &matcher{}
|
|||||||
// matcher evaluates compiled cel expressions and determines if they match the given request or not
|
// matcher evaluates compiled cel expressions and determines if they match the given request or not
|
||||||
type matcher struct {
|
type matcher struct {
|
||||||
filter celplugin.Filter
|
filter celplugin.Filter
|
||||||
authorizer authorizer.Authorizer
|
|
||||||
failPolicy v1.FailurePolicyType
|
failPolicy v1.FailurePolicyType
|
||||||
matcherType string
|
matcherType string
|
||||||
objectName string
|
objectName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMatcher(filter celplugin.Filter, authorizer authorizer.Authorizer, failPolicy *v1.FailurePolicyType, matcherType, objectName string) Matcher {
|
func NewMatcher(filter celplugin.Filter, failPolicy *v1.FailurePolicyType, matcherType, objectName string) Matcher {
|
||||||
var f v1.FailurePolicyType
|
var f v1.FailurePolicyType
|
||||||
if failPolicy == nil {
|
if failPolicy == nil {
|
||||||
f = v1.Fail
|
f = v1.Fail
|
||||||
@ -68,17 +67,16 @@ func NewMatcher(filter celplugin.Filter, authorizer authorizer.Authorizer, failP
|
|||||||
}
|
}
|
||||||
return &matcher{
|
return &matcher{
|
||||||
filter: filter,
|
filter: filter,
|
||||||
authorizer: authorizer,
|
|
||||||
failPolicy: f,
|
failPolicy: f,
|
||||||
matcherType: matcherType,
|
matcherType: matcherType,
|
||||||
objectName: objectName,
|
objectName: objectName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *matcher) Match(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object) MatchResult {
|
func (m *matcher) Match(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, authz authorizer.Authorizer) MatchResult {
|
||||||
evalResults, _, err := m.filter.ForInput(ctx, versionedAttr, celplugin.CreateAdmissionRequest(versionedAttr.Attributes), celplugin.OptionalVariableBindings{
|
evalResults, _, err := m.filter.ForInput(ctx, versionedAttr, celplugin.CreateAdmissionRequest(versionedAttr.Attributes), celplugin.OptionalVariableBindings{
|
||||||
VersionedParams: versionedParams,
|
VersionedParams: versionedParams,
|
||||||
Authorizer: m.authorizer,
|
Authorizer: authz,
|
||||||
}, celconfig.RuntimeCELCostBudgetMatchConditions)
|
}, celconfig.RuntimeCELCostBudgetMatchConditions)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -334,9 +334,9 @@ func TestMatch(t *testing.T) {
|
|||||||
m := NewMatcher(&fakeCelFilter{
|
m := NewMatcher(&fakeCelFilter{
|
||||||
evaluations: tc.evaluations,
|
evaluations: tc.evaluations,
|
||||||
throwError: tc.throwError,
|
throwError: tc.throwError,
|
||||||
}, nil, tc.failPolicy, "test", "testhook")
|
}, tc.failPolicy, "test", "testhook")
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
matchResult := m.Match(ctx, fakeVersionedAttr, nil)
|
matchResult := m.Match(ctx, fakeVersionedAttr, nil, nil)
|
||||||
|
|
||||||
if matchResult.Error != nil {
|
if matchResult.Error != nil {
|
||||||
if len(tc.expectError) == 0 {
|
if len(tc.expectError) == 0 {
|
||||||
|
@ -18,12 +18,16 @@ package cel
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
@ -36,12 +40,15 @@ import (
|
|||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
|
||||||
apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
||||||
|
authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
|
||||||
"k8s.io/kubernetes/test/integration/authutil"
|
"k8s.io/kubernetes/test/integration/authutil"
|
||||||
"k8s.io/kubernetes/test/integration/etcd"
|
"k8s.io/kubernetes/test/integration/etcd"
|
||||||
"k8s.io/kubernetes/test/integration/framework"
|
"k8s.io/kubernetes/test/integration/framework"
|
||||||
"k8s.io/kubernetes/test/utils"
|
"k8s.io/kubernetes/test/utils"
|
||||||
|
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@ -51,13 +58,11 @@ import (
|
|||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
|
||||||
|
|
||||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||||
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
|
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
|
||||||
|
authorizationv1 "k8s.io/api/authorization/v1"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Test_ValidateNamespace_NoParams tests a ValidatingAdmissionPolicy that validates creation of a Namespace with no params.
|
// Test_ValidateNamespace_NoParams tests a ValidatingAdmissionPolicy that validates creation of a Namespace with no params.
|
||||||
@ -2451,15 +2456,15 @@ func withWaitReadyConstraintAndExpression(policy *admissionregistrationv1alpha1.
|
|||||||
return policy
|
return policy
|
||||||
}
|
}
|
||||||
|
|
||||||
func createAndWaitReady(t *testing.T, client *clientset.Clientset, binding *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding, matchLabels map[string]string) error {
|
func createAndWaitReady(t *testing.T, client clientset.Interface, binding *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding, matchLabels map[string]string) error {
|
||||||
return createAndWaitReadyNamespaced(t, client, binding, matchLabels, "default")
|
return createAndWaitReadyNamespaced(t, client, binding, matchLabels, "default")
|
||||||
}
|
}
|
||||||
|
|
||||||
func createAndWaitReadyNamespaced(t *testing.T, client *clientset.Clientset, binding *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding, matchLabels map[string]string, ns string) error {
|
func createAndWaitReadyNamespaced(t *testing.T, client clientset.Interface, binding *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding, matchLabels map[string]string, ns string) error {
|
||||||
return createAndWaitReadyNamespacedWithWarnHandler(t, client, binding, matchLabels, ns, newWarningHandler())
|
return createAndWaitReadyNamespacedWithWarnHandler(t, client, binding, matchLabels, ns, newWarningHandler())
|
||||||
}
|
}
|
||||||
|
|
||||||
func createAndWaitReadyNamespacedWithWarnHandler(t *testing.T, client *clientset.Clientset, binding *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding, matchLabels map[string]string, ns string, handler *warningHandler) error {
|
func createAndWaitReadyNamespacedWithWarnHandler(t *testing.T, client clientset.Interface, binding *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding, matchLabels map[string]string, ns string, handler *warningHandler) error {
|
||||||
marker := &v1.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "test-marker", Namespace: ns, Labels: matchLabels}}
|
marker := &v1.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "test-marker", Namespace: ns, Labels: matchLabels}}
|
||||||
defer func() {
|
defer func() {
|
||||||
err := client.CoreV1().Endpoints(ns).Delete(context.TODO(), marker.Name, metav1.DeleteOptions{})
|
err := client.CoreV1().Endpoints(ns).Delete(context.TODO(), marker.Name, metav1.DeleteOptions{})
|
||||||
@ -3022,3 +3027,167 @@ func toHasLengthOf(n int) func(warnings []admissionregistrationv1alpha1.Expressi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationDecisionCaching(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
validations []admissionregistrationv1alpha1.Validation
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "hit",
|
||||||
|
validations: []admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "authorizer.requestResource.check('test').reason() == authorizer.requestResource.check('test').reason()",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "miss",
|
||||||
|
validations: []admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "authorizer.requestResource.subresource('a').check('test').reason() == '1'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Expression: "authorizer.requestResource.subresource('b').check('test').reason() == '2'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Expression: "authorizer.requestResource.subresource('c').check('test').reason() == '3'",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var nChecks int
|
||||||
|
webhook := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var review authorizationv1.SubjectAccessReview
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
review.Status.Allowed = true
|
||||||
|
if review.Spec.ResourceAttributes.Verb == "test" {
|
||||||
|
nChecks++
|
||||||
|
review.Status.Reason = fmt.Sprintf("%d", nChecks)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(review); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer webhook.Close()
|
||||||
|
|
||||||
|
kcfd, err := os.CreateTemp("", "kubeconfig-")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
func() {
|
||||||
|
defer kcfd.Close()
|
||||||
|
tmpl, err := template.New("kubeconfig").Parse(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Config
|
||||||
|
clusters:
|
||||||
|
- name: test-authz-service
|
||||||
|
cluster:
|
||||||
|
server: {{ .Server }}
|
||||||
|
users:
|
||||||
|
- name: test-api-server
|
||||||
|
current-context: webhook
|
||||||
|
contexts:
|
||||||
|
- context:
|
||||||
|
cluster: test-authz-service
|
||||||
|
user: test-api-server
|
||||||
|
name: webhook
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = tmpl.Execute(kcfd, struct {
|
||||||
|
Server string
|
||||||
|
}{
|
||||||
|
Server: webhook.URL,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
client, config, teardown := framework.StartTestServer(ctx, t, framework.TestServerSetup{
|
||||||
|
ModifyServerRunOptions: func(options *options.ServerRunOptions) {
|
||||||
|
options.Admission.GenericAdmission.EnablePlugins = append(options.Admission.GenericAdmission.EnablePlugins, "ValidatingAdmissionPolicy")
|
||||||
|
options.APIEnablement.RuntimeConfig.Set("api/all=true")
|
||||||
|
|
||||||
|
options.Authorization.Modes = []string{authzmodes.ModeWebhook}
|
||||||
|
options.Authorization.WebhookConfigFile = kcfd.Name()
|
||||||
|
options.Authorization.WebhookVersion = "v1"
|
||||||
|
// Bypass webhook cache to observe the policy plugin's cache behavior.
|
||||||
|
options.Authorization.WebhookCacheAuthorizedTTL = 0
|
||||||
|
options.Authorization.WebhookCacheUnauthorizedTTL = 0
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
policy := &admissionregistrationv1alpha1.ValidatingAdmissionPolicy{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-authorization-decision-caching-policy",
|
||||||
|
},
|
||||||
|
Spec: admissionregistrationv1alpha1.ValidatingAdmissionPolicySpec{
|
||||||
|
MatchConstraints: &admissionregistrationv1alpha1.MatchResources{
|
||||||
|
ResourceRules: []admissionregistrationv1alpha1.NamedRuleWithOperations{
|
||||||
|
{
|
||||||
|
ResourceNames: []string{"test-authorization-decision-caching-namespace"},
|
||||||
|
RuleWithOperations: admissionregistrationv1alpha1.RuleWithOperations{
|
||||||
|
Operations: []admissionregistrationv1.OperationType{
|
||||||
|
admissionregistrationv1.Create,
|
||||||
|
},
|
||||||
|
Rule: admissionregistrationv1.Rule{
|
||||||
|
APIGroups: []string{""},
|
||||||
|
APIVersions: []string{"v1"},
|
||||||
|
Resources: []string{"namespaces"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Validations: tc.validations,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
policy, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(ctx, withWaitReadyConstraintAndExpression(policy), metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := createAndWaitReady(t, client, makeBinding(policy.Name+"-binding", policy.Name, ""), nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config = rest.CopyConfig(config)
|
||||||
|
config.Impersonate = rest.ImpersonationConfig{
|
||||||
|
UserName: "alice",
|
||||||
|
UID: "1234",
|
||||||
|
}
|
||||||
|
client, err = clientset.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := client.CoreV1().Namespaces().Create(
|
||||||
|
ctx,
|
||||||
|
&v1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-authorization-decision-caching-namespace",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metav1.CreateOptions{},
|
||||||
|
); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user