ValidatingAdmissionPolicy: support namespace access (#118267)
* Support namespace access from cel expression in validatingadmissionpolicy. * Whitelist the exposed fields in namespace object and add test * better handling of cluster-scoped resources. * [API REVIEW] namespaceObject in Expression doc. * compatibility with composition. * generated: ./hack/update-codegen.sh && ./hack/update-openapi-spec.sh * workaround namespace of namespace is unexpectedly set. * basic test coverage for namespaceObject. --------- Co-authored-by: Jiahui Feng <jhf@google.com> Kubernetes-commit: 13172cba5c0e1c6a076dbda4aeebbccaf658c7f1
This commit is contained in:
parent
2af49f82c0
commit
04b26c4697
4
go.mod
4
go.mod
|
|
@ -41,7 +41,7 @@ require (
|
|||
google.golang.org/protobuf v1.30.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
k8s.io/api v0.0.0-20230714211713-ac1defa44e72
|
||||
k8s.io/api v0.0.0-20230715005308-48d7a4d6b0f6
|
||||
k8s.io/apimachinery v0.0.0-20230714211010-7924d2c22746
|
||||
k8s.io/client-go v0.0.0-20230714212436-f19b40cda940
|
||||
k8s.io/component-base v0.0.0-20230714213649-faf645bcb8bf
|
||||
|
|
@ -125,7 +125,7 @@ require (
|
|||
)
|
||||
|
||||
replace (
|
||||
k8s.io/api => k8s.io/api v0.0.0-20230714211713-ac1defa44e72
|
||||
k8s.io/api => k8s.io/api v0.0.0-20230715005308-48d7a4d6b0f6
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20230714211010-7924d2c22746
|
||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20230714212436-f19b40cda940
|
||||
k8s.io/component-base => k8s.io/component-base v0.0.0-20230714213649-faf645bcb8bf
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -668,8 +668,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.0.0-20230714211713-ac1defa44e72 h1:VuW3Hv7ZC0+Mf7MwYisaDUAczwTtd0Q+puDnQMCIXYo=
|
||||
k8s.io/api v0.0.0-20230714211713-ac1defa44e72/go.mod h1:nHelHGU5nRmkT223Jbg1sZZjj9DvhsKLsEekrET50A4=
|
||||
k8s.io/api v0.0.0-20230715005308-48d7a4d6b0f6 h1:Vyx1tzAt+Z9+Lr1vyjxRu3Oqw2E5UFqQSOWo7ObFvsY=
|
||||
k8s.io/api v0.0.0-20230715005308-48d7a4d6b0f6/go.mod h1:nHelHGU5nRmkT223Jbg1sZZjj9DvhsKLsEekrET50A4=
|
||||
k8s.io/apimachinery v0.0.0-20230714211010-7924d2c22746 h1:ZRR1OH9l7t2q4sW0wB74g7ZDDSAWvAuO/jAcSHt7FVk=
|
||||
k8s.io/apimachinery v0.0.0-20230714211010-7924d2c22746/go.mod h1:82hjKsW08vcDFh8wS9BtEee9UTcm0IlvyBnJ/zawF6E=
|
||||
k8s.io/client-go v0.0.0-20230714212436-f19b40cda940 h1:EUg/9w1/iqWVzXNXjS2UvB02elWNuW4gTPG3cFlhIEo=
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ const (
|
|||
OldObjectVarName = "oldObject"
|
||||
ParamsVarName = "params"
|
||||
RequestVarName = "request"
|
||||
NamespaceVarName = "namespaceObject"
|
||||
AuthorizerVarName = "authorizer"
|
||||
RequestResourceAuthorizerVarName = "authorizer.requestResource"
|
||||
VariableVarName = "variables"
|
||||
|
|
@ -85,6 +86,56 @@ func BuildRequestType() *apiservercel.DeclType {
|
|||
))
|
||||
}
|
||||
|
||||
// BuildNamespaceType generates a DeclType for Namespace.
|
||||
// Certain nested fields in Namespace (e.g. managedFields, ownerReferences etc.) are omitted in the generated DeclType
|
||||
// by design.
|
||||
func BuildNamespaceType() *apiservercel.DeclType {
|
||||
field := func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField {
|
||||
return apiservercel.NewDeclField(name, declType, required, nil, nil)
|
||||
}
|
||||
fields := func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField {
|
||||
result := make(map[string]*apiservercel.DeclField, len(fields))
|
||||
for _, f := range fields {
|
||||
result[f.Name] = f
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
specType := apiservercel.NewObjectType("kubernetes.NamespaceSpec", fields(
|
||||
field("finalizers", apiservercel.NewListType(apiservercel.StringType, -1), true),
|
||||
))
|
||||
conditionType := apiservercel.NewObjectType("kubernetes.NamespaceCondition", fields(
|
||||
field("status", apiservercel.StringType, true),
|
||||
field("type", apiservercel.StringType, true),
|
||||
field("lastTransitionTime", apiservercel.TimestampType, true),
|
||||
field("message", apiservercel.StringType, true),
|
||||
field("reason", apiservercel.StringType, true),
|
||||
))
|
||||
statusType := apiservercel.NewObjectType("kubernetes.NamespaceStatus", fields(
|
||||
field("conditions", apiservercel.NewListType(conditionType, -1), true),
|
||||
field("phase", apiservercel.StringType, true),
|
||||
))
|
||||
metadataType := apiservercel.NewObjectType("kubernetes.NamespaceMetadata", fields(
|
||||
field("name", apiservercel.StringType, true),
|
||||
field("generateName", apiservercel.StringType, true),
|
||||
field("namespace", apiservercel.StringType, true),
|
||||
field("labels", apiservercel.NewMapType(apiservercel.StringType, apiservercel.StringType, -1), true),
|
||||
field("annotations", apiservercel.NewMapType(apiservercel.StringType, apiservercel.StringType, -1), true),
|
||||
field("UID", apiservercel.StringType, true),
|
||||
field("creationTimestamp", apiservercel.TimestampType, true),
|
||||
field("deletionGracePeriodSeconds", apiservercel.IntType, true),
|
||||
field("deletionTimestamp", apiservercel.TimestampType, true),
|
||||
field("generation", apiservercel.IntType, true),
|
||||
field("resourceVersion", apiservercel.StringType, true),
|
||||
field("finalizers", apiservercel.NewListType(apiservercel.StringType, -1), true),
|
||||
))
|
||||
return apiservercel.NewObjectType("kubernetes.Namespace", fields(
|
||||
field("metadata", metadataType, true),
|
||||
field("spec", specType, true),
|
||||
field("status", statusType, true),
|
||||
))
|
||||
}
|
||||
|
||||
// CompilationResult represents a compiled validations expression.
|
||||
type CompilationResult struct {
|
||||
Program cel.Program
|
||||
|
|
@ -168,6 +219,7 @@ func (c compiler) CompileCELExpression(expressionAccessor ExpressionAccessor, op
|
|||
|
||||
func mustBuildEnvs(baseEnv *environment.EnvSet) variableDeclEnvs {
|
||||
requestType := BuildRequestType()
|
||||
namespaceType := BuildNamespaceType()
|
||||
envs := make(variableDeclEnvs, 4) // since the number of variable combinations is small, pre-build a environment for each
|
||||
for _, hasParams := range []bool{false, true} {
|
||||
for _, hasAuthorizer := range []bool{false, true} {
|
||||
|
|
@ -183,6 +235,7 @@ func mustBuildEnvs(baseEnv *environment.EnvSet) variableDeclEnvs {
|
|||
envOpts = append(envOpts,
|
||||
cel.Variable(ObjectVarName, cel.DynType),
|
||||
cel.Variable(OldObjectVarName, cel.DynType),
|
||||
cel.Variable(NamespaceVarName, namespaceType.CelType()),
|
||||
cel.Variable(RequestVarName, requestType.CelType()))
|
||||
|
||||
extended, err := baseEnv.Extend(
|
||||
|
|
@ -192,6 +245,7 @@ func mustBuildEnvs(baseEnv *environment.EnvSet) variableDeclEnvs {
|
|||
IntroducedVersion: version.MajorMinor(1, 0),
|
||||
EnvOptions: envOpts,
|
||||
DeclTypes: []*apiservercel.DeclType{
|
||||
namespaceType,
|
||||
requestType,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -49,6 +49,11 @@ func TestCompileValidatingPolicyExpression(t *testing.T) {
|
|||
expressions: []string{"object.foo < params.x"},
|
||||
hasParams: true,
|
||||
},
|
||||
{
|
||||
name: "namespaceObject",
|
||||
expressions: []string{"namespaceObject.metadata.name.startsWith('test')"},
|
||||
hasParams: true,
|
||||
},
|
||||
{
|
||||
name: "without params",
|
||||
errorExpressions: map[string]string{"object.foo < params.x": "undeclared reference to 'params'"},
|
||||
|
|
@ -135,6 +140,41 @@ func TestCompileValidatingPolicyExpression(t *testing.T) {
|
|||
},
|
||||
envType: environment.NewExpressions,
|
||||
},
|
||||
{
|
||||
name: "valid namespaceObject",
|
||||
expressions: []string{
|
||||
"namespaceObject.metadata != null",
|
||||
"namespaceObject.metadata.name == 'test'",
|
||||
"namespaceObject.metadata.generateName == 'test'",
|
||||
"namespaceObject.metadata.namespace == 'testns'",
|
||||
"'test' in namespaceObject.metadata.labels",
|
||||
"'test' in namespaceObject.metadata.annotations",
|
||||
"namespaceObject.metadata.UID == '12345'",
|
||||
"type(namespaceObject.metadata.creationTimestamp) == google.protobuf.Timestamp",
|
||||
"type(namespaceObject.metadata.deletionTimestamp) == google.protobuf.Timestamp",
|
||||
"namespaceObject.metadata.deletionGracePeriodSeconds == 5",
|
||||
"namespaceObject.metadata.generation == 2",
|
||||
"namespaceObject.metadata.resourceVersion == 'v1'",
|
||||
"namespaceObject.metadata.finalizers[0] == 'testEnv'",
|
||||
"namespaceObject.spec.finalizers[0] == 'testEnv'",
|
||||
"namespaceObject.status.phase == 'Active'",
|
||||
"namespaceObject.status.conditions[0].status == 'True'",
|
||||
"namespaceObject.status.conditions[0].type == 'NamespaceDeletionDiscoveryFailure'",
|
||||
"type(namespaceObject.status.conditions[0].lastTransitionTime) == google.protobuf.Timestamp",
|
||||
"namespaceObject.status.conditions[0].message == 'Unknow'",
|
||||
"namespaceObject.status.conditions[0].reason == 'Invalid'",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid namespaceObject",
|
||||
errorExpressions: map[string]string{
|
||||
"namespaceObject.foo1 == 'nope'": "undefined field 'foo1'",
|
||||
"namespaceObject.metadata.foo2 == 'nope'": "undefined field 'foo2'",
|
||||
"namespaceObject.spec.foo3 == 'nope'": "undefined field 'foo3'",
|
||||
"namespaceObject.status.foo4 == 'nope'": "undefined field 'foo4'",
|
||||
"namespaceObject.status.conditions[0].foo5 == 'nope'": "undefined field 'foo5'",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Include the test library, which includes the test() function in the storage environment during test
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/google/cel-go/common/types/ref"
|
||||
|
||||
v1 "k8s.io/api/admission/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
|
|
@ -149,9 +150,9 @@ func (c *compositionContext) Variables(activation any) ref.Val {
|
|||
return lazyMap
|
||||
}
|
||||
|
||||
func (f *CompositedFilter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
|
||||
func (f *CompositedFilter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
|
||||
ctx = f.compositionEnv.CreateContext(ctx)
|
||||
return f.Filter.ForInput(ctx, versionedAttr, request, optionalVars, runtimeCELCostBudget)
|
||||
return f.Filter.ForInput(ctx, versionedAttr, request, optionalVars, namespace, runtimeCELCostBudget)
|
||||
}
|
||||
|
||||
func (c *compositionContext) reportCost(cost int64) {
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ func TestCompositedPolicies(t *testing.T) {
|
|||
if costBudget == 0 {
|
||||
costBudget = celconfig.RuntimeCELCostBudget
|
||||
}
|
||||
result, _, err := f.ForInput(context.Background(), versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, costBudget)
|
||||
result, _, err := f.ForInput(context.Background(), versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, nil, costBudget)
|
||||
if !tc.expectErr && err != nil {
|
||||
t.Fatalf("failed evaluation: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
|
@ -46,7 +47,7 @@ func NewFilterCompiler(env *environment.EnvSet) FilterCompiler {
|
|||
}
|
||||
|
||||
type evaluationActivation struct {
|
||||
object, oldObject, params, request, authorizer, requestResourceAuthorizer, variables interface{}
|
||||
object, oldObject, params, request, namespace, authorizer, requestResourceAuthorizer, variables interface{}
|
||||
}
|
||||
|
||||
// ResolveName returns a value from the activation by qualified name, or false if the name
|
||||
|
|
@ -61,6 +62,8 @@ func (a *evaluationActivation) ResolveName(name string) (interface{}, bool) {
|
|||
return a.params, true // params may be null
|
||||
case RequestVarName:
|
||||
return a.request, true
|
||||
case NamespaceVarName:
|
||||
return a.namespace, true
|
||||
case AuthorizerVarName:
|
||||
return a.authorizer, a.authorizer != nil
|
||||
case RequestResourceAuthorizerVarName:
|
||||
|
|
@ -126,7 +129,7 @@ func objectToResolveVal(r runtime.Object) (interface{}, error) {
|
|||
// ForInput evaluates the compiled CEL expressions converting them into CELEvaluations
|
||||
// errors per evaluation are returned on the Evaluation object
|
||||
// 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 (f *filter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs OptionalVariableBindings, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
|
||||
func (f *filter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs OptionalVariableBindings, namespace *v1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
|
||||
// TODO: replace unstructured with ref.Val for CEL variables when native type support is available
|
||||
evaluations := make([]EvaluationResult, len(f.compilationResults))
|
||||
var err error
|
||||
|
|
@ -156,11 +159,16 @@ func (f *filter) ForInput(ctx context.Context, versionedAttr *admission.Versione
|
|||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
namespaceVal, err := objectToResolveVal(namespace)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
va := &evaluationActivation{
|
||||
object: objectVal,
|
||||
oldObject: oldObjectVal,
|
||||
params: paramsVal,
|
||||
request: requestVal.Object,
|
||||
namespace: namespaceVal,
|
||||
authorizer: authorizerVal,
|
||||
requestResourceAuthorizer: requestResourceAuthorizerVal,
|
||||
}
|
||||
|
|
@ -307,6 +315,33 @@ func CreateAdmissionRequest(attr admission.Attributes) *admissionv1.AdmissionReq
|
|||
}
|
||||
}
|
||||
|
||||
// CreateNamespaceObject creates a Namespace object that is suitable for the CEL evaluation.
|
||||
// If the namespace is nil, CreateNamespaceObject returns nil
|
||||
func CreateNamespaceObject(namespace *v1.Namespace) *v1.Namespace {
|
||||
if namespace == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &v1.Namespace{
|
||||
Status: namespace.Status,
|
||||
Spec: namespace.Spec,
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespace.Name,
|
||||
GenerateName: namespace.GenerateName,
|
||||
Namespace: namespace.Namespace,
|
||||
UID: namespace.UID,
|
||||
ResourceVersion: namespace.ResourceVersion,
|
||||
Generation: namespace.Generation,
|
||||
CreationTimestamp: namespace.CreationTimestamp,
|
||||
DeletionTimestamp: namespace.DeletionTimestamp,
|
||||
DeletionGracePeriodSeconds: namespace.DeletionGracePeriodSeconds,
|
||||
Labels: namespace.Labels,
|
||||
Annotations: namespace.Annotations,
|
||||
Finalizers: namespace.Finalizers,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CompilationErrors returns a list of all the errors from the compilation of the evaluator
|
||||
func (e *filter) CompilationErrors() []error {
|
||||
compilationErrors := []error{}
|
||||
|
|
|
|||
|
|
@ -149,6 +149,28 @@ func TestFilter(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
nsObject := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Labels: map[string]string{
|
||||
"env": "test",
|
||||
"foo": "demo",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"annotation1": "testAnnotation1",
|
||||
},
|
||||
Finalizers: []string{"f1"},
|
||||
},
|
||||
Spec: corev1.NamespaceSpec{
|
||||
Finalizers: []corev1.FinalizerName{
|
||||
corev1.FinalizerKubernetes,
|
||||
},
|
||||
},
|
||||
Status: corev1.NamespaceStatus{
|
||||
Phase: corev1.NamespaceActive,
|
||||
},
|
||||
}
|
||||
|
||||
var nilUnstructured *unstructured.Unstructured
|
||||
cases := []struct {
|
||||
name string
|
||||
|
|
@ -159,6 +181,7 @@ func TestFilter(t *testing.T) {
|
|||
hasParamKind bool
|
||||
authorizer authorizer.Authorizer
|
||||
testPerCallLimit uint64
|
||||
namespaceObject *corev1.Namespace
|
||||
}{
|
||||
{
|
||||
name: "valid syntax for object",
|
||||
|
|
@ -674,6 +697,64 @@ func TestFilter(t *testing.T) {
|
|||
params: crdParams,
|
||||
testPerCallLimit: 1,
|
||||
},
|
||||
{
|
||||
name: "test namespaceObject",
|
||||
validations: []ExpressionAccessor{
|
||||
&condition{
|
||||
Expression: "namespaceObject.metadata.name == 'test'",
|
||||
},
|
||||
&condition{
|
||||
Expression: "'env' in namespaceObject.metadata.labels && namespaceObject.metadata.labels.env == 'test'",
|
||||
},
|
||||
&condition{
|
||||
Expression: "('fake' in namespaceObject.metadata.labels) && namespaceObject.metadata.labels.fake == 'test'",
|
||||
},
|
||||
&condition{
|
||||
Expression: "namespaceObject.spec.finalizers[0] == 'kubernetes'",
|
||||
},
|
||||
&condition{
|
||||
Expression: "namespaceObject.status.phase == 'Active'",
|
||||
},
|
||||
&condition{
|
||||
Expression: "size(namespaceObject.metadata.managedFields) == 1",
|
||||
},
|
||||
&condition{
|
||||
Expression: "size(namespaceObject.metadata.ownerReferences) == 1",
|
||||
},
|
||||
&condition{
|
||||
Expression: "'env' in namespaceObject.metadata.annotations",
|
||||
},
|
||||
},
|
||||
attributes: newValidAttribute(&podObject, false),
|
||||
results: []EvaluationResult{
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
},
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
},
|
||||
{
|
||||
EvalResult: celtypes.False,
|
||||
},
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
},
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
},
|
||||
{
|
||||
Error: errors.New("undefined field 'managedFields'"),
|
||||
},
|
||||
{
|
||||
Error: errors.New("undefined field 'ownerReferences'"),
|
||||
},
|
||||
{
|
||||
EvalResult: celtypes.False,
|
||||
},
|
||||
},
|
||||
hasParamKind: false,
|
||||
namespaceObject: nsObject,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
|
@ -706,7 +787,7 @@ func TestFilter(t *testing.T) {
|
|||
|
||||
optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer}
|
||||
ctx := context.TODO()
|
||||
evalResults, _, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, celconfig.RuntimeCELCostBudget)
|
||||
evalResults, _, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, CreateNamespaceObject(tc.namespaceObject), celconfig.RuntimeCELCostBudget)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -852,7 +933,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
|||
}
|
||||
optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer}
|
||||
ctx := context.TODO()
|
||||
evalResults, remaining, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, tc.testRuntimeCELCostBudget)
|
||||
evalResults, remaining, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, nil, tc.testRuntimeCELCostBudget)
|
||||
if tc.exceedBudget && err == nil {
|
||||
t.Errorf("Expected RuntimeCELCostBudge to be exceeded but got nil")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/google/cel-go/common/types/ref"
|
||||
|
||||
v1 "k8s.io/api/admission/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
|
|
@ -87,7 +88,7 @@ type Filter interface {
|
|||
// ForInput converts compiled CEL-typed values into evaluated CEL-typed value.
|
||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
// If cost budget is calculated, the filter should return the remaining budget.
|
||||
ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error)
|
||||
ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error)
|
||||
|
||||
// CompilationErrors returns a list of errors from the compilation of the evaluator
|
||||
CompilationErrors() []error
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ type fakeFilter struct {
|
|||
keyId string
|
||||
}
|
||||
|
||||
func (f *fakeFilter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs cel.OptionalVariableBindings, runtimeCELCostBudget int64) ([]cel.EvaluationResult, int64, error) {
|
||||
func (f *fakeFilter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs cel.OptionalVariableBindings, namespace *v1.Namespace, runtimeCELCostBudget int64) ([]cel.EvaluationResult, int64, error) {
|
||||
return []cel.EvaluationResult{}, 0, nil
|
||||
}
|
||||
|
||||
|
|
@ -265,10 +265,10 @@ var _ Validator = &fakeValidator{}
|
|||
|
||||
type fakeValidator struct {
|
||||
validationFilter, auditAnnotationFilter, messageFilter *fakeFilter
|
||||
ValidateFunc func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult
|
||||
ValidateFunc func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, 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, authz authorizer.Authorizer) ValidateResult) {
|
||||
func (f *fakeValidator) RegisterDefinition(definition *v1alpha1.ValidatingAdmissionPolicy, validateFunc func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, 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
|
||||
var key string
|
||||
if len(definition.Spec.Validations) > 0 {
|
||||
|
|
@ -285,8 +285,8 @@ func (f *fakeValidator) RegisterDefinition(definition *v1alpha1.ValidatingAdmiss
|
|||
validatorMap[key] = f
|
||||
}
|
||||
|
||||
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, authz)
|
||||
func (f *fakeValidator) Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
return f.ValidateFunc(ctx, versionedAttr, versionedParams, namespace, runtimeCELCostBudget, authz)
|
||||
}
|
||||
|
||||
var _ Matcher = &fakeMatcher{}
|
||||
|
|
@ -295,6 +295,10 @@ func (f *fakeMatcher) ValidateInitialization() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeMatcher) GetNamespace(name string) (*v1.Namespace, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type fakeMatcher struct {
|
||||
DefaultMatch bool
|
||||
DefinitionMatchFuncs map[namespacedName]func(*v1alpha1.ValidatingAdmissionPolicy, admission.Attributes) bool
|
||||
|
|
@ -770,7 +774,7 @@ func TestBasicPolicyDefinitionFailure(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
{
|
||||
|
|
@ -840,7 +844,7 @@ func TestDefinitionDoesntMatch(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
{
|
||||
|
|
@ -953,7 +957,7 @@ func TestReconfigureBinding(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
{
|
||||
|
|
@ -1063,7 +1067,7 @@ func TestRemoveDefinition(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
{
|
||||
|
|
@ -1132,7 +1136,7 @@ func TestRemoveBinding(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
{
|
||||
|
|
@ -1242,7 +1246,7 @@ func TestInvalidParamSourceInstanceName(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
{
|
||||
|
|
@ -1310,7 +1314,7 @@ func TestEmptyParamSource(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
{
|
||||
|
|
@ -1412,7 +1416,7 @@ func TestMultiplePoliciesSharedParamType(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
validator1.RegisterDefinition(&policy1, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
validator1.RegisterDefinition(&policy1, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
evaluations1.Add(1)
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
|
|
@ -1431,7 +1435,7 @@ func TestMultiplePoliciesSharedParamType(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
validator2.RegisterDefinition(&policy2, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
validator2.RegisterDefinition(&policy2, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
evaluations2.Add(1)
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
|
|
@ -1541,7 +1545,7 @@ func TestNativeTypeParam(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
validator.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
evaluations.Add(1)
|
||||
if _, ok := versionedParams.(*v1.ConfigMap); ok {
|
||||
return ValidateResult{
|
||||
|
|
@ -1623,7 +1627,7 @@ func TestAuditValidationAction(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
{
|
||||
|
|
@ -1694,7 +1698,7 @@ func TestWarnValidationAction(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
{
|
||||
|
|
@ -1753,7 +1757,7 @@ func TestAllValidationActions(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
{
|
||||
|
|
@ -1824,7 +1828,7 @@ func TestAuditAnnotations(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
o, err := meta.Accessor(versionedParams)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"time"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
|
@ -319,6 +320,23 @@ func (c *celAdmissionController) Validate(
|
|||
continue
|
||||
}
|
||||
}
|
||||
var namespace *v1.Namespace
|
||||
namespaceName := a.GetNamespace()
|
||||
|
||||
// Special case, the namespace object has the namespace of itself (maybe a bug).
|
||||
// unset it if the incoming object is a namespace
|
||||
if gvk := a.GetKind(); gvk.Kind == "Namespace" && gvk.Version == "v1" && gvk.Group == "" {
|
||||
namespaceName = ""
|
||||
}
|
||||
|
||||
// if it is cluster scoped, namespaceName will be empty
|
||||
// Otherwise, get the Namespace resource.
|
||||
if namespaceName != "" {
|
||||
namespace, err = c.policyController.matcher.GetNamespace(namespaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if versionedAttr == nil {
|
||||
va, err := admission.NewVersionedAttributes(a, matchKind, o)
|
||||
|
|
@ -330,7 +348,7 @@ func (c *celAdmissionController) Validate(
|
|||
versionedAttr = va
|
||||
}
|
||||
|
||||
validationResult := bindingInfo.validator.Validate(ctx, versionedAttr, param, celconfig.RuntimeCELCostBudget, authz)
|
||||
validationResult := bindingInfo.validator.Validate(ctx, versionedAttr, param, namespace, celconfig.RuntimeCELCostBudget, authz)
|
||||
|
||||
for i, decision := range validationResult.Decisions {
|
||||
switch decision.Action {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
celgo "github.com/google/cel-go/cel"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
|
@ -90,6 +91,10 @@ type Matcher interface {
|
|||
// BindingMatches says whether this policy definition matches the provided admission
|
||||
// resource request
|
||||
BindingMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1alpha1.ValidatingAdmissionPolicyBinding) (bool, error)
|
||||
|
||||
// GetNamespace retrieves the Namespace resource by the given name. The name may be empty, in which case
|
||||
// GetNamespace must return nil, nil
|
||||
GetNamespace(name string) (*corev1.Namespace, error)
|
||||
}
|
||||
|
||||
// ValidateResult defines the result of a Validator.Validate operation.
|
||||
|
|
@ -104,5 +109,5 @@ type ValidateResult struct {
|
|||
type Validator interface {
|
||||
// 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.
|
||||
Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult
|
||||
Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *corev1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package validatingadmissionpolicy
|
|||
|
||||
import (
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
|
@ -76,3 +77,7 @@ func (c *matcher) BindingMatches(a admission.Attributes, o admission.ObjectInter
|
|||
isMatch, _, err := c.Matcher.Matches(a, o, &criteria)
|
||||
return isMatch, err
|
||||
}
|
||||
|
||||
func (c *matcher) GetNamespace(name string) (*corev1.Namespace, error) {
|
||||
return c.Matcher.GetNamespace(name)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
|
@ -44,6 +45,10 @@ type Matcher struct {
|
|||
objectMatcher *object.Matcher
|
||||
}
|
||||
|
||||
func (m *Matcher) GetNamespace(name string) (*corev1.Namespace, error) {
|
||||
return m.namespaceMatcher.GetNamespace(name)
|
||||
}
|
||||
|
||||
// NewMatcher initialize the matcher with dependencies requires
|
||||
func NewMatcher(
|
||||
namespaceLister listersv1.NamespaceLister,
|
||||
|
|
|
|||
|
|
@ -347,10 +347,15 @@ func sortGVKList(list []schema.GroupVersionKind) []schema.GroupVersionKind {
|
|||
func buildEnv(hasParams bool, hasAuthorizer bool, types typeOverwrite) (*cel.Env, error) {
|
||||
baseEnv := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())
|
||||
requestType := plugincel.BuildRequestType()
|
||||
namespaceType := plugincel.BuildNamespaceType()
|
||||
|
||||
var varOpts []cel.EnvOption
|
||||
var declTypes []*apiservercel.DeclType
|
||||
|
||||
// namespace, hand-crafted type
|
||||
declTypes = append(declTypes, namespaceType)
|
||||
varOpts = append(varOpts, createVariableOpts(namespaceType, plugincel.NamespaceVarName)...)
|
||||
|
||||
// request, hand-crafted type
|
||||
declTypes = append(declTypes, requestType)
|
||||
varOpts = append(varOpts, createVariableOpts(requestType, plugincel.RequestVarName)...)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
celtypes "github.com/google/cel-go/common/types"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
|
|
@ -70,7 +71,8 @@ func auditAnnotationEvaluationForError(f v1.FailurePolicyType) PolicyAuditAnnota
|
|||
|
||||
// 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.
|
||||
func (v *validator) Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
|
||||
func (v *validator) Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *corev1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
|
||||
var f v1.FailurePolicyType
|
||||
if v.failPolicy == nil {
|
||||
f = v1.Fail
|
||||
|
|
@ -101,7 +103,9 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
|
|||
optionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams, Authorizer: authz}
|
||||
expressionOptionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams}
|
||||
admissionRequest := cel.CreateAdmissionRequest(versionedAttr.Attributes)
|
||||
evalResults, remainingBudget, err := v.validationFilter.ForInput(ctx, versionedAttr, admissionRequest, optionalVars, runtimeCELCostBudget)
|
||||
// Decide which fields are exposed
|
||||
ns := cel.CreateNamespaceObject(namespace)
|
||||
evalResults, remainingBudget, err := v.validationFilter.ForInput(ctx, versionedAttr, admissionRequest, optionalVars, ns, runtimeCELCostBudget)
|
||||
if err != nil {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
|
|
@ -114,7 +118,7 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
|
|||
}
|
||||
}
|
||||
decisions := make([]PolicyDecision, len(evalResults))
|
||||
messageResults, _, err := v.messageFilter.ForInput(ctx, versionedAttr, admissionRequest, expressionOptionalVars, remainingBudget)
|
||||
messageResults, _, err := v.messageFilter.ForInput(ctx, versionedAttr, admissionRequest, expressionOptionalVars, ns, remainingBudget)
|
||||
for i, evalResult := range evalResults {
|
||||
var decision = &decisions[i]
|
||||
// TODO: move this to generics
|
||||
|
|
@ -191,7 +195,7 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
|
|||
}
|
||||
|
||||
options := cel.OptionalVariableBindings{VersionedParams: versionedParams}
|
||||
auditAnnotationEvalResults, _, err := v.auditAnnotationFilter.ForInput(ctx, versionedAttr, cel.CreateAdmissionRequest(versionedAttr.Attributes), options, runtimeCELCostBudget)
|
||||
auditAnnotationEvalResults, _, err := v.auditAnnotationFilter.ForInput(ctx, versionedAttr, cel.CreateAdmissionRequest(versionedAttr.Attributes), options, namespace, runtimeCELCostBudget)
|
||||
if err != nil {
|
||||
return ValidateResult{
|
||||
Decisions: []PolicyDecision{
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
|
@ -47,7 +48,7 @@ type fakeCelFilter struct {
|
|||
throwError bool
|
||||
}
|
||||
|
||||
func (f *fakeCelFilter) ForInput(_ context.Context, _ *admission.VersionedAttributes, _ *admissionv1.AdmissionRequest, _ cel.OptionalVariableBindings, costBudget int64) ([]cel.EvaluationResult, int64, error) {
|
||||
func (f *fakeCelFilter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, optionalVars cel.OptionalVariableBindings, namespace *corev1.Namespace, costBudget int64) ([]cel.EvaluationResult, int64, error) {
|
||||
if costBudget <= 0 { // this filter will cost 1, so cost = 0 means fail.
|
||||
return nil, -1, &apiservercel.Error{
|
||||
Type: apiservercel.ErrorTypeInvalid,
|
||||
|
|
@ -892,7 +893,7 @@ func TestValidate(t *testing.T) {
|
|||
if tc.costBudget != 0 {
|
||||
budget = tc.costBudget
|
||||
}
|
||||
validateResult := v.Validate(ctx, fakeVersionedAttr, nil, budget, nil)
|
||||
validateResult := v.Validate(ctx, fakeVersionedAttr, nil, nil, budget, nil)
|
||||
|
||||
require.Equal(t, len(validateResult.Decisions), len(tc.policyDecision))
|
||||
|
||||
|
|
@ -944,7 +945,7 @@ func TestContextCanceled(t *testing.T) {
|
|||
}
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
cancel()
|
||||
validationResult := v.Validate(ctx, fakeVersionedAttr, nil, celconfig.RuntimeCELCostBudget, nil)
|
||||
validationResult := v.Validate(ctx, fakeVersionedAttr, nil, nil, celconfig.RuntimeCELCostBudget, nil)
|
||||
if len(validationResult.Decisions) != 1 || !strings.Contains(validationResult.Decisions[0].Message, "operation interrupted") {
|
||||
t.Errorf("Expected 'operation interrupted' but got %v", validationResult.Decisions)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ func (m *matcher) Match(ctx context.Context, versionedAttr *admission.VersionedA
|
|||
evalResults, _, err := m.filter.ForInput(ctx, versionedAttr, celplugin.CreateAdmissionRequest(versionedAttr.Attributes), celplugin.OptionalVariableBindings{
|
||||
VersionedParams: versionedParams,
|
||||
Authorizer: authz,
|
||||
}, celconfig.RuntimeCELCostBudgetMatchConditions)
|
||||
}, nil, celconfig.RuntimeCELCostBudgetMatchConditions)
|
||||
|
||||
if err != nil {
|
||||
admissionmetrics.Metrics.ObserveMatchConditionEvaluationTime(ctx, time.Since(t), m.objectName, m.matcherKind, m.matcherType, string(versionedAttr.GetOperation()))
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
|
||||
celtypes "github.com/google/cel-go/common/types"
|
||||
|
|
@ -40,7 +42,7 @@ type fakeCelFilter struct {
|
|||
throwError bool
|
||||
}
|
||||
|
||||
func (f *fakeCelFilter) ForInput(context.Context, *admission.VersionedAttributes, *admissionv1.AdmissionRequest, cel.OptionalVariableBindings, int64) ([]cel.EvaluationResult, int64, error) {
|
||||
func (f *fakeCelFilter) ForInput(context.Context, *admission.VersionedAttributes, *admissionv1.AdmissionRequest, cel.OptionalVariableBindings, *api.Namespace, int64) ([]cel.EvaluationResult, int64, error) {
|
||||
if f.throwError {
|
||||
return nil, 0, errors.New("test error")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
|
@ -42,6 +44,10 @@ type Matcher struct {
|
|||
Client clientset.Interface
|
||||
}
|
||||
|
||||
func (m *Matcher) GetNamespace(name string) (*v1.Namespace, error) {
|
||||
return m.NamespaceLister.Get(name)
|
||||
}
|
||||
|
||||
// Validate checks if the Matcher has a NamespaceLister and Client.
|
||||
func (m *Matcher) Validate() error {
|
||||
var errs []error
|
||||
|
|
|
|||
Loading…
Reference in New Issue