mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 19:31:44 +00:00
Add integration test.
Co-authored-by: Kermit Alexander II <kermitalexandr@google.com>
This commit is contained in:
parent
46f97d4662
commit
d86cfa9854
27
test/integration/apiserver/cel/main_test.go
Normal file
27
test/integration/apiserver/cel/main_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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 cel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/test/integration/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
framework.EtcdMain(m.Run)
|
||||||
|
}
|
645
test/integration/apiserver/cel/validatingadmissionpolicy_test.go
Normal file
645
test/integration/apiserver/cel/validatingadmissionpolicy_test.go
Normal file
@ -0,0 +1,645 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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 cel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
|
apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
||||||
|
"k8s.io/kubernetes/test/integration/framework"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
|
||||||
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||||
|
admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
|
||||||
|
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test_ValidateNamespace_NoParams tests a ValidatingAdmissionPolicy that validates creation of a Namespace with no params.
|
||||||
|
func Test_ValidateNamespace_NoParams(t *testing.T) {
|
||||||
|
forbiddenReason := metav1.StatusReasonForbidden
|
||||||
|
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy
|
||||||
|
policyBinding *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding
|
||||||
|
namespace *v1.Namespace
|
||||||
|
err string
|
||||||
|
failureReason metav1.StatusReason
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "namespace name contains suffix enforced by validating admission policy, using object metadata fields",
|
||||||
|
policy: withValidations([]admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "object.metadata.name.endsWith('k8s')",
|
||||||
|
},
|
||||||
|
}, withFailurePolicy(admissionregistrationv1alpha1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix")))),
|
||||||
|
policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""),
|
||||||
|
namespace: &v1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-k8s",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "namespace name does NOT contain suffix enforced by validating admission policyusing, object metadata fields",
|
||||||
|
policy: withValidations([]admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "object.metadata.name.endsWith('k8s')",
|
||||||
|
},
|
||||||
|
}, withFailurePolicy(admissionregistrationv1alpha1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix")))),
|
||||||
|
policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""),
|
||||||
|
namespace: &v1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-foobar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "namespaces \"test-foobar\" is forbidden: ValidatingAdmissionPolicy 'validate-namespace-suffix' with binding 'validate-namespace-suffix-binding' denied request: failed expression: object.metadata.name.endsWith('k8s')",
|
||||||
|
failureReason: metav1.StatusReasonInvalid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "namespace name does NOT contain suffix enforced by validating admission policy using object metadata fields, AND validating expression returns StatusReasonForbidden",
|
||||||
|
policy: withValidations([]admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "object.metadata.name.endsWith('k8s')",
|
||||||
|
Reason: &forbiddenReason,
|
||||||
|
},
|
||||||
|
}, withFailurePolicy(admissionregistrationv1alpha1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix")))),
|
||||||
|
policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""),
|
||||||
|
namespace: &v1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "forbidden-test-foobar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "namespaces \"forbidden-test-foobar\" is forbidden: ValidatingAdmissionPolicy 'validate-namespace-suffix' with binding 'validate-namespace-suffix-binding' denied request: failed expression: object.metadata.name.endsWith('k8s')",
|
||||||
|
failureReason: metav1.StatusReasonForbidden,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "namespace name contains suffix enforced by validating admission policy, using request field",
|
||||||
|
policy: withValidations([]admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "request.name.endsWith('k8s')",
|
||||||
|
},
|
||||||
|
}, withFailurePolicy(admissionregistrationv1alpha1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix")))),
|
||||||
|
policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""),
|
||||||
|
namespace: &v1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-k8s",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "namespace name does NOT contains suffix enforced by validating admission policy, using request field",
|
||||||
|
policy: withValidations([]admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "request.name.endsWith('k8s')",
|
||||||
|
},
|
||||||
|
}, withFailurePolicy(admissionregistrationv1alpha1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix")))),
|
||||||
|
policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""),
|
||||||
|
namespace: &v1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-k8s",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "runtime error when validating namespace, but failurePolicy=Ignore",
|
||||||
|
policy: withValidations([]admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "object.nonExistentProperty == 'someval'",
|
||||||
|
},
|
||||||
|
}, withFailurePolicy(admissionregistrationv1alpha1.Ignore, withNamespaceMatch(makePolicy("validate-namespace-suffix")))),
|
||||||
|
policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""),
|
||||||
|
namespace: &v1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-k8s",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "runtime error when validating namespace, but failurePolicy=Fail",
|
||||||
|
policy: withValidations([]admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "object.nonExistentProperty == 'someval'",
|
||||||
|
},
|
||||||
|
}, withFailurePolicy(admissionregistrationv1alpha1.Fail, withNamespaceMatch(makePolicy("validate-namespace-suffix")))),
|
||||||
|
policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", ""),
|
||||||
|
namespace: &v1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-k8s",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "namespaces \"test-k8s\" is forbidden: ValidatingAdmissionPolicy 'validate-namespace-suffix' with binding 'validate-namespace-suffix-binding' denied request: expression 'object.nonExistentProperty == 'someval'' resulted in error: no such key: nonExistentProperty",
|
||||||
|
failureReason: metav1.StatusReasonInvalid,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testcase := range testcases {
|
||||||
|
t.Run(testcase.name, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)()
|
||||||
|
server, err := apiservertesting.StartTestServer(t, nil, []string{
|
||||||
|
"--enable-admission-plugins", "ValidatingAdmissionPolicy",
|
||||||
|
}, framework.SharedEtcd())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer server.TearDownFn()
|
||||||
|
|
||||||
|
config := server.ClientConfig
|
||||||
|
|
||||||
|
client, err := clientset.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
policy := withWaitReadyConstraintAndExpression(testcase.policy)
|
||||||
|
if _, err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := createAndWaitReady(t, client, testcase.policyBinding, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.CoreV1().Namespaces().Create(context.TODO(), testcase.namespace, metav1.CreateOptions{})
|
||||||
|
if err == nil && testcase.err == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && testcase.err != "" {
|
||||||
|
t.Logf("actual error: %v", err)
|
||||||
|
t.Logf("expected error: %v", testcase.err)
|
||||||
|
t.Fatal("got nil error but expected an error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && testcase.err == "" {
|
||||||
|
t.Logf("actual error: %v", err)
|
||||||
|
t.Logf("expected error: %v", testcase.err)
|
||||||
|
t.Fatal("got error but expected none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err.Error() != testcase.err {
|
||||||
|
t.Logf("actual validation error: %v", err)
|
||||||
|
t.Logf("expected validation error: %v", testcase.err)
|
||||||
|
t.Error("unexpected validation error")
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFailureReason(t, err, testcase.failureReason)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test_ValidateNamespace_WithConfigMapParams tests a ValidatingAdmissionPolicy that validates creation of a Namespace,
|
||||||
|
// using ConfigMap as a param reference.
|
||||||
|
func Test_ValidateNamespace_WithConfigMapParams(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy
|
||||||
|
policyBinding *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding
|
||||||
|
configMap *v1.ConfigMap
|
||||||
|
namespace *v1.Namespace
|
||||||
|
err string
|
||||||
|
failureReason metav1.StatusReason
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "namespace name contains suffix enforced by validating admission policy",
|
||||||
|
policy: withValidations([]admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "object.metadata.name.endsWith(params.data.namespaceSuffix)",
|
||||||
|
},
|
||||||
|
}, withFailurePolicy(admissionregistrationv1alpha1.Fail, withParams(configParamKind(), withNamespaceMatch(makePolicy("validate-namespace-suffix"))))),
|
||||||
|
policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", "validate-namespace-suffix-param"),
|
||||||
|
configMap: makeConfigParams("validate-namespace-suffix-param", map[string]string{
|
||||||
|
"namespaceSuffix": "k8s",
|
||||||
|
}),
|
||||||
|
namespace: &v1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-k8s",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "namespace name does NOT contain suffix enforced by validating admission policy",
|
||||||
|
policy: withValidations([]admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "object.metadata.name.endsWith(params.data.namespaceSuffix)",
|
||||||
|
},
|
||||||
|
}, withFailurePolicy(admissionregistrationv1alpha1.Fail, withParams(configParamKind(), withNamespaceMatch(makePolicy("validate-namespace-suffix"))))),
|
||||||
|
policyBinding: makeBinding("validate-namespace-suffix-binding", "validate-namespace-suffix", "validate-namespace-suffix-param"),
|
||||||
|
configMap: makeConfigParams("validate-namespace-suffix-param", map[string]string{
|
||||||
|
"namespaceSuffix": "k8s",
|
||||||
|
}),
|
||||||
|
namespace: &v1.Namespace{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "namespaces \"test-foo\" is forbidden: ValidatingAdmissionPolicy 'validate-namespace-suffix' with binding 'validate-namespace-suffix-binding' denied request: failed expression: object.metadata.name.endsWith(params.data.namespaceSuffix)",
|
||||||
|
failureReason: metav1.StatusReasonInvalid,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testcase := range testcases {
|
||||||
|
t.Run(testcase.name, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)()
|
||||||
|
server, err := apiservertesting.StartTestServer(t, nil, []string{
|
||||||
|
"--enable-admission-plugins", "ValidatingAdmissionPolicy",
|
||||||
|
}, framework.SharedEtcd())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer server.TearDownFn()
|
||||||
|
|
||||||
|
config := server.ClientConfig
|
||||||
|
|
||||||
|
client, err := clientset.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), testcase.configMap, metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
policy := withWaitReadyConstraintAndExpression(testcase.policy)
|
||||||
|
if _, err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := createAndWaitReady(t, client, testcase.policyBinding, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.CoreV1().Namespaces().Create(context.TODO(), testcase.namespace, metav1.CreateOptions{})
|
||||||
|
if err == nil && testcase.err == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && testcase.err != "" {
|
||||||
|
t.Logf("actual error: %v", err)
|
||||||
|
t.Logf("expected error: %v", testcase.err)
|
||||||
|
t.Fatal("got nil error but expected an error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && testcase.err == "" {
|
||||||
|
t.Logf("actual error: %v", err)
|
||||||
|
t.Logf("expected error: %v", testcase.err)
|
||||||
|
t.Fatal("got error but expected none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err.Error() != testcase.err {
|
||||||
|
t.Logf("actual validation error: %v", err)
|
||||||
|
t.Logf("expected validation error: %v", testcase.err)
|
||||||
|
t.Error("unexpected validation error")
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFailureReason(t, err, testcase.failureReason)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiplePolicyBindings(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.CELValidatingAdmission, true)()
|
||||||
|
server, err := apiservertesting.StartTestServer(t, nil, nil, framework.SharedEtcd())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer server.TearDownFn()
|
||||||
|
|
||||||
|
config := server.ClientConfig
|
||||||
|
|
||||||
|
client, err := clientset.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
paramKind := &admissionregistrationv1alpha1.ParamKind{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "ConfigMap",
|
||||||
|
}
|
||||||
|
policy := withPolicyExistsLabels([]string{"paramIdent"}, withParams(paramKind, withPolicyMatch("secrets", withFailurePolicy(admissionregistrationv1alpha1.Fail, makePolicy("test-policy")))))
|
||||||
|
policy.Spec.Validations = []admissionregistrationv1alpha1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "params.data.autofail != 'true' && (params.data.conditional == 'false' || object.metadata.name.startsWith(params.data.check))",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
policy = withWaitReadyConstraintAndExpression(policy)
|
||||||
|
if _, err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), policy, metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
autoFailParams := makeConfigParams("autofail-params", map[string]string{
|
||||||
|
"autofail": "true",
|
||||||
|
})
|
||||||
|
if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), autoFailParams, metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
autofailBinding := withBindingExistsLabels([]string{"autofail-binding-label"}, policy, makeBinding("autofail-binding", "test-policy", "autofail-params"))
|
||||||
|
if err := createAndWaitReady(t, client, autofailBinding, map[string]string{"paramIdent": "true", "autofail-binding-label": "true"}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
autoPassParams := makeConfigParams("autopass-params", map[string]string{
|
||||||
|
"autofail": "false",
|
||||||
|
"conditional": "false",
|
||||||
|
})
|
||||||
|
if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), autoPassParams, metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
autopassBinding := withBindingExistsLabels([]string{"autopass-binding-label"}, policy, makeBinding("autopass-binding", "test-policy", "autopass-params"))
|
||||||
|
if err := createAndWaitReady(t, client, autopassBinding, map[string]string{"paramIdent": "true", "autopass-binding-label": "true"}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
condpassParams := makeConfigParams("condpass-params", map[string]string{
|
||||||
|
"autofail": "false",
|
||||||
|
"conditional": "true",
|
||||||
|
"check": "prefix-",
|
||||||
|
})
|
||||||
|
if _, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), condpassParams, metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
condpassBinding := withBindingExistsLabels([]string{"condpass-binding-label"}, policy, makeBinding("condpass-binding", "test-policy", "condpass-params"))
|
||||||
|
if err := createAndWaitReady(t, client, condpassBinding, map[string]string{"paramIdent": "true", "condpass-binding-label": "true"}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
autofailingSecret := &v1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "autofailing-secret",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"paramIdent": "someVal",
|
||||||
|
"autofail-binding-label": "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = client.CoreV1().Secrets("default").Create(context.TODO(), autofailingSecret, metav1.CreateOptions{})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected secret creation to fail due to autofail-binding")
|
||||||
|
}
|
||||||
|
checkForFailedRule(t, err)
|
||||||
|
checkFailureReason(t, err, metav1.StatusReasonInvalid)
|
||||||
|
|
||||||
|
autopassingSecret := &v1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "autopassing-secret",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"paramIdent": "someVal",
|
||||||
|
"autopass-binding-label": "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if _, err := client.CoreV1().Secrets("default").Create(context.TODO(), autopassingSecret, metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Fatalf("expected secret creation to succeed, got: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
condpassingSecret := &v1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "prefix-condpassing-secret",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"paramIdent": "someVal",
|
||||||
|
"condpass-binding-label": "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if _, err := client.CoreV1().Secrets("default").Create(context.TODO(), condpassingSecret, metav1.CreateOptions{}); err != nil {
|
||||||
|
t.Fatalf("expected secret creation to succeed, got: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
condfailingSecret := &v1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "condfailing-secret",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"paramIdent": "someVal",
|
||||||
|
"condpass-binding-label": "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = client.CoreV1().Secrets("default").Create(context.TODO(), condfailingSecret, metav1.CreateOptions{})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected secret creation to fail due to autofail-binding")
|
||||||
|
}
|
||||||
|
checkForFailedRule(t, err)
|
||||||
|
checkFailureReason(t, err, metav1.StatusReasonInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withWaitReadyConstraintAndExpression(policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy {
|
||||||
|
policy = policy.DeepCopy()
|
||||||
|
policy.Spec.MatchConstraints.ResourceRules = append(policy.Spec.MatchConstraints.ResourceRules, admissionregistrationv1alpha1.NamedRuleWithOperations{
|
||||||
|
ResourceNames: []string{"test-marker"},
|
||||||
|
RuleWithOperations: admissionregistrationv1alpha1.RuleWithOperations{
|
||||||
|
Operations: []admissionregistrationv1.OperationType{
|
||||||
|
"UPDATE",
|
||||||
|
},
|
||||||
|
Rule: admissionregistrationv1.Rule{
|
||||||
|
APIGroups: []string{
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
APIVersions: []string{
|
||||||
|
"v1",
|
||||||
|
},
|
||||||
|
Resources: []string{
|
||||||
|
"endpoints",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
policy.Spec.Validations = append([]admissionregistrationv1alpha1.Validation{{
|
||||||
|
Expression: "object.metadata.name != 'test-marker'",
|
||||||
|
Message: "marker denied; policy is ready",
|
||||||
|
}}, policy.Spec.Validations...)
|
||||||
|
return policy
|
||||||
|
}
|
||||||
|
|
||||||
|
func createAndWaitReady(t *testing.T, client *clientset.Clientset, binding *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding, matchLabels map[string]string) error {
|
||||||
|
marker := &v1.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "test-marker", Namespace: "default", Labels: matchLabels}}
|
||||||
|
defer func() {
|
||||||
|
err := client.CoreV1().Endpoints("default").Delete(context.TODO(), marker.Name, metav1.DeleteOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("error deleting marker: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
marker, err := client.CoreV1().Endpoints("default").Create(context.TODO(), marker, metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Create(context.TODO(), binding, metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if waitErr := wait.PollImmediate(time.Millisecond*5, wait.ForeverTestTimeout, func() (bool, error) {
|
||||||
|
_, err := client.CoreV1().Endpoints("default").Patch(context.TODO(), marker.Name, types.JSONPatchType, []byte("[]"), metav1.PatchOptions{})
|
||||||
|
if err != nil && strings.Contains(err.Error(), "marker denied; policy is ready") {
|
||||||
|
return true, nil
|
||||||
|
} else {
|
||||||
|
t.Logf("waiting for policy to be ready. Marker: %v, Last marker patch response: %v", marker, err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}); waitErr != nil {
|
||||||
|
return waitErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePolicy(name string) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy {
|
||||||
|
return &admissionregistrationv1alpha1.ValidatingAdmissionPolicy{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: name},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withParams(params *admissionregistrationv1alpha1.ParamKind, policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy {
|
||||||
|
policy.Spec.ParamKind = params
|
||||||
|
return policy
|
||||||
|
}
|
||||||
|
|
||||||
|
func configParamKind() *admissionregistrationv1alpha1.ParamKind {
|
||||||
|
return &admissionregistrationv1alpha1.ParamKind{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "ConfigMap",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withFailurePolicy(failure admissionregistrationv1alpha1.FailurePolicyType, policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy {
|
||||||
|
policy.Spec.FailurePolicy = &failure
|
||||||
|
return policy
|
||||||
|
}
|
||||||
|
|
||||||
|
func withNamespaceMatch(policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy {
|
||||||
|
return withPolicyMatch("namespaces", policy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withPolicyMatch(resource string, policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy {
|
||||||
|
policy.Spec.MatchConstraints = &admissionregistrationv1alpha1.MatchResources{
|
||||||
|
ResourceRules: []admissionregistrationv1alpha1.NamedRuleWithOperations{
|
||||||
|
{
|
||||||
|
RuleWithOperations: admissionregistrationv1alpha1.RuleWithOperations{
|
||||||
|
Operations: []admissionregistrationv1.OperationType{
|
||||||
|
"CREATE",
|
||||||
|
},
|
||||||
|
Rule: admissionregistrationv1.Rule{
|
||||||
|
APIGroups: []string{
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
APIVersions: []string{
|
||||||
|
"*",
|
||||||
|
},
|
||||||
|
Resources: []string{
|
||||||
|
resource,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return policy
|
||||||
|
}
|
||||||
|
|
||||||
|
func withPolicyExistsLabels(labels []string, policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy {
|
||||||
|
if policy.Spec.MatchConstraints == nil {
|
||||||
|
policy.Spec.MatchConstraints = &admissionregistrationv1alpha1.MatchResources{}
|
||||||
|
}
|
||||||
|
matchExprs := buildExistsSelector(labels)
|
||||||
|
policy.Spec.MatchConstraints.ObjectSelector = &metav1.LabelSelector{
|
||||||
|
MatchExpressions: matchExprs,
|
||||||
|
}
|
||||||
|
return policy
|
||||||
|
}
|
||||||
|
|
||||||
|
func withValidations(validations []admissionregistrationv1alpha1.Validation, policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy) *admissionregistrationv1alpha1.ValidatingAdmissionPolicy {
|
||||||
|
policy.Spec.Validations = validations
|
||||||
|
return policy
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeBinding(name, policyName, paramName string) *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding {
|
||||||
|
var paramRef *admissionregistrationv1alpha1.ParamRef
|
||||||
|
if paramName != "" {
|
||||||
|
paramRef = &admissionregistrationv1alpha1.ParamRef{
|
||||||
|
Name: paramName,
|
||||||
|
Namespace: "default",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: name},
|
||||||
|
Spec: admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec{
|
||||||
|
PolicyName: policyName,
|
||||||
|
ParamRef: paramRef,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withBindingExistsLabels(labels []string, policy *admissionregistrationv1alpha1.ValidatingAdmissionPolicy, binding *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding) *admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding {
|
||||||
|
if policy != nil {
|
||||||
|
// shallow copy
|
||||||
|
constraintsCopy := *policy.Spec.MatchConstraints
|
||||||
|
binding.Spec.MatchResources = &constraintsCopy
|
||||||
|
}
|
||||||
|
matchExprs := buildExistsSelector(labels)
|
||||||
|
binding.Spec.MatchResources.ObjectSelector = &metav1.LabelSelector{
|
||||||
|
MatchExpressions: matchExprs,
|
||||||
|
}
|
||||||
|
return binding
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildExistsSelector(labels []string) []metav1.LabelSelectorRequirement {
|
||||||
|
matchExprs := make([]metav1.LabelSelectorRequirement, len(labels))
|
||||||
|
for i := 0; i < len(labels); i++ {
|
||||||
|
matchExprs[i].Key = labels[i]
|
||||||
|
matchExprs[i].Operator = metav1.LabelSelectorOpExists
|
||||||
|
}
|
||||||
|
return matchExprs
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeConfigParams(name string, data map[string]string) *v1.ConfigMap {
|
||||||
|
return &v1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: name},
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkForFailedRule(t *testing.T, err error) {
|
||||||
|
if !strings.Contains(err.Error(), "failed expression") {
|
||||||
|
t.Fatalf("unexpected error (expected to find \"failed expression\"): %s", err)
|
||||||
|
}
|
||||||
|
if strings.Contains(err.Error(), "evaluation error") {
|
||||||
|
t.Fatalf("CEL rule evaluation failed: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkFailureReason(t *testing.T, err error, expectedReason metav1.StatusReason) {
|
||||||
|
reason := err.(apierrors.APIStatus).Status().Reason
|
||||||
|
if reason != expectedReason {
|
||||||
|
t.Logf("actual error reason: %v", reason)
|
||||||
|
t.Logf("expected failure reason: %v", expectedReason)
|
||||||
|
t.Error("unexpected error reason")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user