2018-01-09 18:57:14 +00:00
/ *
Copyright 2017 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 apimachinery
import (
"fmt"
"reflect"
"strings"
"time"
"k8s.io/api/admissionregistration/v1beta1"
2018-07-18 14:47:22 +00:00
apps "k8s.io/api/apps/v1"
2018-01-09 18:57:14 +00:00
"k8s.io/api/core/v1"
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
2018-07-18 14:47:22 +00:00
crdclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
2018-01-09 18:57:14 +00:00
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
2018-11-26 18:23:56 +00:00
utilversion "k8s.io/apimachinery/pkg/util/version"
2018-01-09 18:57:14 +00:00
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/dynamic"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/test/e2e/framework"
2018-03-06 22:33:18 +00:00
imageutils "k8s.io/kubernetes/test/utils/image"
2018-01-09 18:57:14 +00:00
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
_ "github.com/stretchr/testify/assert"
)
const (
2018-03-06 22:33:18 +00:00
secretName = "sample-webhook-secret"
deploymentName = "sample-webhook-deployment"
serviceName = "e2e-test-webhook"
roleBindingName = "webhook-auth-reader"
// The webhook configuration names should not be reused between test instances.
2018-11-26 18:23:56 +00:00
crWebhookConfigName = "e2e-test-webhook-config-cr"
webhookConfigName = "e2e-test-webhook-config"
attachingPodWebhookConfigName = "e2e-test-webhook-config-attaching-pod"
mutatingWebhookConfigName = "e2e-test-mutating-webhook-config"
podMutatingWebhookConfigName = "e2e-test-mutating-webhook-pod"
crMutatingWebhookConfigName = "e2e-test-mutating-webhook-config-cr"
webhookFailClosedConfigName = "e2e-test-webhook-fail-closed"
validatingWebhookForWebhooksConfigName = "e2e-test-validating-webhook-for-webhooks-config"
mutatingWebhookForWebhooksConfigName = "e2e-test-mutating-webhook-for-webhooks-config"
dummyValidatingWebhookConfigName = "e2e-test-dummy-validating-webhook-config"
dummyMutatingWebhookConfigName = "e2e-test-dummy-mutating-webhook-config"
crdWebhookConfigName = "e2e-test-webhook-config-crd"
2018-03-06 22:33:18 +00:00
skipNamespaceLabelKey = "skip-webhook-admission"
skipNamespaceLabelValue = "yes"
skippedNamespaceName = "exempted-namesapce"
disallowedPodName = "disallowed-pod"
2018-11-26 18:23:56 +00:00
toBeAttachedPodName = "to-be-attached-pod"
2018-03-06 22:33:18 +00:00
hangingPodName = "hanging-pod"
disallowedConfigMapName = "disallowed-configmap"
allowedConfigMapName = "allowed-configmap"
failNamespaceLabelKey = "fail-closed-webhook"
failNamespaceLabelValue = "yes"
failNamespaceName = "fail-closed-namesapce"
2018-11-26 18:23:56 +00:00
addedLabelKey = "added-label"
addedLabelValue = "yes"
2018-01-09 18:57:14 +00:00
)
var serverWebhookVersion = utilversion . MustParseSemantic ( "v1.8.0" )
var _ = SIGDescribe ( "AdmissionWebhook" , func ( ) {
var context * certContext
f := framework . NewDefaultFramework ( "webhook" )
var client clientset . Interface
var namespaceName string
BeforeEach ( func ( ) {
client = f . ClientSet
namespaceName = f . Namespace . Name
// Make sure the relevant provider supports admission webhook
framework . SkipUnlessServerVersionGTE ( serverWebhookVersion , f . ClientSet . Discovery ( ) )
framework . SkipUnlessProviderIs ( "gce" , "gke" , "local" )
_ , err := f . ClientSet . AdmissionregistrationV1beta1 ( ) . ValidatingWebhookConfigurations ( ) . List ( metav1 . ListOptions { } )
if errors . IsNotFound ( err ) {
framework . Skipf ( "dynamic configuration of webhooks requires the admissionregistration.k8s.io group to be enabled" )
}
By ( "Setting up server cert" )
context = setupServerCert ( namespaceName , serviceName )
createAuthReaderRoleBinding ( f , namespaceName )
// Note that in 1.9 we will have backwards incompatible change to
// admission webhooks, so the image will be updated to 1.9 sometime in
// the development 1.9 cycle.
2018-03-06 22:33:18 +00:00
deployWebhookAndService ( f , imageutils . GetE2EImage ( imageutils . AdmissionWebhook ) , context )
2018-01-09 18:57:14 +00:00
} )
2018-03-06 22:33:18 +00:00
2018-01-09 18:57:14 +00:00
AfterEach ( func ( ) {
cleanWebhookTest ( client , namespaceName )
} )
It ( "Should be able to deny pod and configmap creation" , func ( ) {
2018-03-06 22:33:18 +00:00
webhookCleanup := registerWebhook ( f , context )
defer webhookCleanup ( )
2018-01-09 18:57:14 +00:00
testWebhook ( f )
} )
2018-11-26 18:23:56 +00:00
It ( "Should be able to deny attaching pod" , func ( ) {
webhookCleanup := registerWebhookForAttachingPod ( f , context )
defer webhookCleanup ( )
testAttachingPodWebhook ( f )
} )
2018-01-09 18:57:14 +00:00
It ( "Should be able to deny custom resource creation" , func ( ) {
2018-03-06 22:33:18 +00:00
testcrd , err := framework . CreateTestCRD ( f )
if err != nil {
return
}
defer testcrd . CleanUp ( )
2018-07-18 14:47:22 +00:00
webhookCleanup := registerWebhookForCustomResource ( f , context , testcrd )
2018-03-06 22:33:18 +00:00
defer webhookCleanup ( )
2018-11-26 18:23:56 +00:00
testCustomResourceWebhook ( f , testcrd . Crd , testcrd . GetV1DynamicClient ( ) )
2018-01-09 18:57:14 +00:00
} )
It ( "Should unconditionally reject operations on fail closed webhook" , func ( ) {
2018-03-06 22:33:18 +00:00
webhookCleanup := registerFailClosedWebhook ( f , context )
defer webhookCleanup ( )
2018-01-09 18:57:14 +00:00
testFailClosedWebhook ( f )
} )
It ( "Should mutate configmap" , func ( ) {
2018-03-06 22:33:18 +00:00
webhookCleanup := registerMutatingWebhookForConfigMap ( f , context )
defer webhookCleanup ( )
2018-01-09 18:57:14 +00:00
testMutatingConfigMapWebhook ( f )
} )
2018-03-06 22:33:18 +00:00
It ( "Should mutate pod and apply defaults after mutation" , func ( ) {
webhookCleanup := registerMutatingWebhookForPod ( f , context )
defer webhookCleanup ( )
testMutatingPodWebhook ( f )
} )
2018-11-26 18:23:56 +00:00
It ( "Should not be able to mutate or prevent deletion of webhook configuration objects" , func ( ) {
validatingWebhookCleanup := registerValidatingWebhookForWebhookConfigurations ( f , context )
defer validatingWebhookCleanup ( )
mutatingWebhookCleanup := registerMutatingWebhookForWebhookConfigurations ( f , context )
defer mutatingWebhookCleanup ( )
testWebhooksForWebhookConfigurations ( f )
2018-07-18 14:47:22 +00:00
} )
It ( "Should mutate custom resource" , func ( ) {
2018-03-06 22:33:18 +00:00
testcrd , err := framework . CreateTestCRD ( f )
if err != nil {
return
}
defer testcrd . CleanUp ( )
2018-07-18 14:47:22 +00:00
webhookCleanup := registerMutatingWebhookForCustomResource ( f , context , testcrd )
2018-03-06 22:33:18 +00:00
defer webhookCleanup ( )
2018-11-26 18:23:56 +00:00
testMutatingCustomResourceWebhook ( f , testcrd . Crd , testcrd . GetV1DynamicClient ( ) )
2018-07-18 14:47:22 +00:00
} )
It ( "Should deny crd creation" , func ( ) {
crdWebhookCleanup := registerValidatingWebhookForCRD ( f , context )
defer crdWebhookCleanup ( )
testCRDDenyWebhook ( f )
2018-01-09 18:57:14 +00:00
} )
// TODO: add more e2e tests for mutating webhooks
// 1. mutating webhook that mutates pod
// 2. mutating webhook that sends empty patch
// 2.1 and sets status.allowed=true
// 2.2 and sets status.allowed=false
// 3. mutating webhook that sends patch, but also sets status.allowed=false
// 4. mtuating webhook that fail-open v.s. fail-closed
} )
func createAuthReaderRoleBinding ( f * framework . Framework , namespace string ) {
By ( "Create role binding to let webhook read extension-apiserver-authentication" )
client := f . ClientSet
// Create the role binding to allow the webhook read the extension-apiserver-authentication configmap
_ , err := client . RbacV1beta1 ( ) . RoleBindings ( "kube-system" ) . Create ( & rbacv1beta1 . RoleBinding {
ObjectMeta : metav1 . ObjectMeta {
Name : roleBindingName ,
Annotations : map [ string ] string {
rbacv1beta1 . AutoUpdateAnnotationKey : "true" ,
} ,
} ,
RoleRef : rbacv1beta1 . RoleRef {
APIGroup : "" ,
Kind : "Role" ,
Name : "extension-apiserver-authentication-reader" ,
} ,
// Webhook uses the default service account.
Subjects : [ ] rbacv1beta1 . Subject {
{
Kind : "ServiceAccount" ,
Name : "default" ,
Namespace : namespace ,
} ,
} ,
} )
if err != nil && errors . IsAlreadyExists ( err ) {
framework . Logf ( "role binding %s already exists" , roleBindingName )
} else {
framework . ExpectNoError ( err , "creating role binding %s:webhook to access configMap" , namespace )
}
}
func deployWebhookAndService ( f * framework . Framework , image string , context * certContext ) {
By ( "Deploying the webhook pod" )
client := f . ClientSet
// Creating the secret that contains the webhook's cert.
secret := & v1 . Secret {
ObjectMeta : metav1 . ObjectMeta {
Name : secretName ,
} ,
Type : v1 . SecretTypeOpaque ,
Data : map [ string ] [ ] byte {
"tls.crt" : context . cert ,
"tls.key" : context . key ,
} ,
}
namespace := f . Namespace . Name
_ , err := client . CoreV1 ( ) . Secrets ( namespace ) . Create ( secret )
framework . ExpectNoError ( err , "creating secret %q in namespace %q" , secretName , namespace )
// Create the deployment of the webhook
podLabels := map [ string ] string { "app" : "sample-webhook" , "webhook" : "true" }
replicas := int32 ( 1 )
zero := int64 ( 0 )
mounts := [ ] v1 . VolumeMount {
{
Name : "webhook-certs" ,
ReadOnly : true ,
MountPath : "/webhook.local.config/certificates" ,
} ,
}
volumes := [ ] v1 . Volume {
{
Name : "webhook-certs" ,
VolumeSource : v1 . VolumeSource {
Secret : & v1 . SecretVolumeSource { SecretName : secretName } ,
} ,
} ,
}
containers := [ ] v1 . Container {
{
Name : "sample-webhook" ,
VolumeMounts : mounts ,
Args : [ ] string {
"--tls-cert-file=/webhook.local.config/certificates/tls.crt" ,
"--tls-private-key-file=/webhook.local.config/certificates/tls.key" ,
"--alsologtostderr" ,
"-v=4" ,
"2>&1" ,
} ,
Image : image ,
} ,
}
2018-07-18 14:47:22 +00:00
d := & apps . Deployment {
2018-01-09 18:57:14 +00:00
ObjectMeta : metav1 . ObjectMeta {
2018-07-18 14:47:22 +00:00
Name : deploymentName ,
Labels : podLabels ,
2018-01-09 18:57:14 +00:00
} ,
2018-07-18 14:47:22 +00:00
Spec : apps . DeploymentSpec {
2018-01-09 18:57:14 +00:00
Replicas : & replicas ,
2018-07-18 14:47:22 +00:00
Selector : & metav1 . LabelSelector {
MatchLabels : podLabels ,
} ,
Strategy : apps . DeploymentStrategy {
Type : apps . RollingUpdateDeploymentStrategyType ,
2018-01-09 18:57:14 +00:00
} ,
Template : v1 . PodTemplateSpec {
ObjectMeta : metav1 . ObjectMeta {
Labels : podLabels ,
} ,
Spec : v1 . PodSpec {
TerminationGracePeriodSeconds : & zero ,
Containers : containers ,
Volumes : volumes ,
} ,
} ,
} ,
}
2018-07-18 14:47:22 +00:00
deployment , err := client . AppsV1 ( ) . Deployments ( namespace ) . Create ( d )
2018-01-09 18:57:14 +00:00
framework . ExpectNoError ( err , "creating deployment %s in namespace %s" , deploymentName , namespace )
By ( "Wait for the deployment to be ready" )
err = framework . WaitForDeploymentRevisionAndImage ( client , namespace , deploymentName , "1" , image )
framework . ExpectNoError ( err , "waiting for the deployment of image %s in %s in %s to complete" , image , deploymentName , namespace )
err = framework . WaitForDeploymentComplete ( client , deployment )
framework . ExpectNoError ( err , "waiting for the deployment status valid" , image , deploymentName , namespace )
By ( "Deploying the webhook service" )
serviceLabels := map [ string ] string { "webhook" : "true" }
service := & v1 . Service {
ObjectMeta : metav1 . ObjectMeta {
Namespace : namespace ,
Name : serviceName ,
Labels : map [ string ] string { "test" : "webhook" } ,
} ,
Spec : v1 . ServiceSpec {
Selector : serviceLabels ,
Ports : [ ] v1 . ServicePort {
{
Protocol : "TCP" ,
Port : 443 ,
TargetPort : intstr . FromInt ( 443 ) ,
} ,
} ,
} ,
}
_ , err = client . CoreV1 ( ) . Services ( namespace ) . Create ( service )
framework . ExpectNoError ( err , "creating service %s in namespace %s" , serviceName , namespace )
By ( "Verifying the service has paired with the endpoint" )
err = framework . WaitForServiceEndpointsNum ( client , namespace , serviceName , 1 , 1 * time . Second , 30 * time . Second )
framework . ExpectNoError ( err , "waiting for service %s/%s have %d endpoint" , namespace , serviceName , 1 )
}
func strPtr ( s string ) * string { return & s }
2018-03-06 22:33:18 +00:00
func registerWebhook ( f * framework . Framework , context * certContext ) func ( ) {
2018-01-09 18:57:14 +00:00
client := f . ClientSet
By ( "Registering the webhook via the AdmissionRegistration API" )
namespace := f . Namespace . Name
2018-03-06 22:33:18 +00:00
configName := webhookConfigName
2018-01-09 18:57:14 +00:00
// A webhook that cannot talk to server, with fail-open policy
failOpenHook := failingWebhook ( namespace , "fail-open.k8s.io" )
policyIgnore := v1beta1 . Ignore
failOpenHook . FailurePolicy = & policyIgnore
_ , err := client . AdmissionregistrationV1beta1 ( ) . ValidatingWebhookConfigurations ( ) . Create ( & v1beta1 . ValidatingWebhookConfiguration {
ObjectMeta : metav1 . ObjectMeta {
2018-03-06 22:33:18 +00:00
Name : configName ,
2018-01-09 18:57:14 +00:00
} ,
Webhooks : [ ] v1beta1 . Webhook {
{
Name : "deny-unwanted-pod-container-name-and-label.k8s.io" ,
Rules : [ ] v1beta1 . RuleWithOperations { {
Operations : [ ] v1beta1 . OperationType { v1beta1 . Create } ,
Rule : v1beta1 . Rule {
APIGroups : [ ] string { "" } ,
APIVersions : [ ] string { "v1" } ,
Resources : [ ] string { "pods" } ,
} ,
} } ,
ClientConfig : v1beta1 . WebhookClientConfig {
Service : & v1beta1 . ServiceReference {
Namespace : namespace ,
Name : serviceName ,
Path : strPtr ( "/pods" ) ,
} ,
CABundle : context . signingCert ,
} ,
} ,
{
Name : "deny-unwanted-configmap-data.k8s.io" ,
Rules : [ ] v1beta1 . RuleWithOperations { {
Operations : [ ] v1beta1 . OperationType { v1beta1 . Create , v1beta1 . Update } ,
Rule : v1beta1 . Rule {
APIGroups : [ ] string { "" } ,
APIVersions : [ ] string { "v1" } ,
Resources : [ ] string { "configmaps" } ,
} ,
} } ,
// The webhook skips the namespace that has label "skip-webhook-admission":"yes"
NamespaceSelector : & metav1 . LabelSelector {
MatchExpressions : [ ] metav1 . LabelSelectorRequirement {
{
Key : skipNamespaceLabelKey ,
Operator : metav1 . LabelSelectorOpNotIn ,
Values : [ ] string { skipNamespaceLabelValue } ,
} ,
} ,
} ,
ClientConfig : v1beta1 . WebhookClientConfig {
Service : & v1beta1 . ServiceReference {
Namespace : namespace ,
Name : serviceName ,
Path : strPtr ( "/configmaps" ) ,
} ,
CABundle : context . signingCert ,
} ,
} ,
// Server cannot talk to this webhook, so it always fails.
// Because this webhook is configured fail-open, request should be admitted after the call fails.
failOpenHook ,
} ,
} )
2018-03-06 22:33:18 +00:00
framework . ExpectNoError ( err , "registering webhook config %s with namespace %s" , configName , namespace )
2018-01-09 18:57:14 +00:00
2018-07-18 14:47:22 +00:00
// The webhook configuration is honored in 10s.
2018-01-09 18:57:14 +00:00
time . Sleep ( 10 * time . Second )
2018-03-06 22:33:18 +00:00
return func ( ) {
client . AdmissionregistrationV1beta1 ( ) . ValidatingWebhookConfigurations ( ) . Delete ( configName , nil )
}
2018-01-09 18:57:14 +00:00
}
2018-11-26 18:23:56 +00:00
func registerWebhookForAttachingPod ( f * framework . Framework , context * certContext ) func ( ) {
client := f . ClientSet
By ( "Registering the webhook via the AdmissionRegistration API" )
namespace := f . Namespace . Name
configName := attachingPodWebhookConfigName
// A webhook that cannot talk to server, with fail-open policy
failOpenHook := failingWebhook ( namespace , "fail-open.k8s.io" )
policyIgnore := v1beta1 . Ignore
failOpenHook . FailurePolicy = & policyIgnore
_ , err := client . AdmissionregistrationV1beta1 ( ) . ValidatingWebhookConfigurations ( ) . Create ( & v1beta1 . ValidatingWebhookConfiguration {
ObjectMeta : metav1 . ObjectMeta {
Name : configName ,
} ,
Webhooks : [ ] v1beta1 . Webhook {
{
Name : "deny-attaching-pod.k8s.io" ,
Rules : [ ] v1beta1 . RuleWithOperations { {
Operations : [ ] v1beta1 . OperationType { v1beta1 . Connect } ,
Rule : v1beta1 . Rule {
APIGroups : [ ] string { "" } ,
APIVersions : [ ] string { "v1" } ,
Resources : [ ] string { "pods/attach" } ,
} ,
} } ,
ClientConfig : v1beta1 . WebhookClientConfig {
Service : & v1beta1 . ServiceReference {
Namespace : namespace ,
Name : serviceName ,
Path : strPtr ( "/pods/attach" ) ,
} ,
CABundle : context . signingCert ,
} ,
} ,
} ,
} )
framework . ExpectNoError ( err , "registering webhook config %s with namespace %s" , configName , namespace )
// The webhook configuration is honored in 10s.
time . Sleep ( 10 * time . Second )
return func ( ) {
client . AdmissionregistrationV1beta1 ( ) . ValidatingWebhookConfigurations ( ) . Delete ( configName , nil )
}
}
2018-03-06 22:33:18 +00:00
func registerMutatingWebhookForConfigMap ( f * framework . Framework , context * certContext ) func ( ) {
2018-01-09 18:57:14 +00:00
client := f . ClientSet
By ( "Registering the mutating configmap webhook via the AdmissionRegistration API" )
namespace := f . Namespace . Name
2018-03-06 22:33:18 +00:00
configName := mutatingWebhookConfigName
2018-01-09 18:57:14 +00:00
_ , err := client . AdmissionregistrationV1beta1 ( ) . MutatingWebhookConfigurations ( ) . Create ( & v1beta1 . MutatingWebhookConfiguration {
ObjectMeta : metav1 . ObjectMeta {
2018-03-06 22:33:18 +00:00
Name : configName ,
2018-01-09 18:57:14 +00:00
} ,
Webhooks : [ ] v1beta1 . Webhook {
{
Name : "adding-configmap-data-stage-1.k8s.io" ,
Rules : [ ] v1beta1 . RuleWithOperations { {
Operations : [ ] v1beta1 . OperationType { v1beta1 . Create } ,
Rule : v1beta1 . Rule {
APIGroups : [ ] string { "" } ,
APIVersions : [ ] string { "v1" } ,
Resources : [ ] string { "configmaps" } ,
} ,
} } ,
ClientConfig : v1beta1 . WebhookClientConfig {
Service : & v1beta1 . ServiceReference {
Namespace : namespace ,
Name : serviceName ,
Path : strPtr ( "/mutating-configmaps" ) ,
} ,
CABundle : context . signingCert ,
} ,
} ,
{
Name : "adding-configmap-data-stage-2.k8s.io" ,
Rules : [ ] v1beta1 . RuleWithOperations { {
Operations : [ ] v1beta1 . OperationType { v1beta1 . Create } ,
Rule : v1beta1 . Rule {
APIGroups : [ ] string { "" } ,
APIVersions : [ ] string { "v1" } ,
Resources : [ ] string { "configmaps" } ,
} ,
} } ,
ClientConfig : v1beta1 . WebhookClientConfig {
Service : & v1beta1 . ServiceReference {
Namespace : namespace ,
Name : serviceName ,
Path : strPtr ( "/mutating-configmaps" ) ,
} ,
CABundle : context . signingCert ,
} ,
} ,
} ,
} )
2018-03-06 22:33:18 +00:00
framework . ExpectNoError ( err , "registering mutating webhook config %s with namespace %s" , configName , namespace )
2018-01-09 18:57:14 +00:00
2018-07-18 14:47:22 +00:00
// The webhook configuration is honored in 10s.
2018-01-09 18:57:14 +00:00
time . Sleep ( 10 * time . Second )
2018-03-06 22:33:18 +00:00
return func ( ) { client . AdmissionregistrationV1beta1 ( ) . MutatingWebhookConfigurations ( ) . Delete ( configName , nil ) }
2018-01-09 18:57:14 +00:00
}
2018-03-06 22:33:18 +00:00
2018-01-09 18:57:14 +00:00
func testMutatingConfigMapWebhook ( f * framework . Framework ) {
By ( "create a configmap that should be updated by the webhook" )
client := f . ClientSet
configMap := toBeMutatedConfigMap ( f )
mutatedConfigMap , err := client . CoreV1 ( ) . ConfigMaps ( f . Namespace . Name ) . Create ( configMap )
Expect ( err ) . To ( BeNil ( ) )
expectedConfigMapData := map [ string ] string {
"mutation-start" : "yes" ,
"mutation-stage-1" : "yes" ,
"mutation-stage-2" : "yes" ,
}
if ! reflect . DeepEqual ( expectedConfigMapData , mutatedConfigMap . Data ) {
framework . Failf ( "\nexpected %#v\n, got %#v\n" , expectedConfigMapData , mutatedConfigMap . Data )
}
}
2018-03-06 22:33:18 +00:00
func registerMutatingWebhookForPod ( f * framework . Framework , context * certContext ) func ( ) {
client := f . ClientSet
By ( "Registering the mutating pod webhook via the AdmissionRegistration API" )
namespace := f . Namespace . Name
configName := podMutatingWebhookConfigName
_ , err := client . AdmissionregistrationV1beta1 ( ) . MutatingWebhookConfigurations ( ) . Create ( & v1beta1 . MutatingWebhookConfiguration {
ObjectMeta : metav1 . ObjectMeta {
Name : configName ,
} ,
Webhooks : [ ] v1beta1 . Webhook {
{
Name : "adding-init-container.k8s.io" ,
Rules : [ ] v1beta1 . RuleWithOperations { {
Operations : [ ] v1beta1 . OperationType { v1beta1 . Create } ,
Rule : v1beta1 . Rule {
APIGroups : [ ] string { "" } ,
APIVersions : [ ] string { "v1" } ,
Resources : [ ] string { "pods" } ,
} ,
} } ,
ClientConfig : v1beta1 . WebhookClientConfig {
Service : & v1beta1 . ServiceReference {
Namespace : namespace ,
Name : serviceName ,
Path : strPtr ( "/mutating-pods" ) ,
} ,
CABundle : context . signingCert ,
} ,
} ,
} ,
} )
framework . ExpectNoError ( err , "registering mutating webhook config %s with namespace %s" , configName , namespace )
2018-07-18 14:47:22 +00:00
// The webhook configuration is honored in 10s.
2018-03-06 22:33:18 +00:00
time . Sleep ( 10 * time . Second )
return func ( ) { client . AdmissionregistrationV1beta1 ( ) . MutatingWebhookConfigurations ( ) . Delete ( configName , nil ) }
}
func testMutatingPodWebhook ( f * framework . Framework ) {
By ( "create a pod that should be updated by the webhook" )
client := f . ClientSet
configMap := toBeMutatedPod ( f )
mutatedPod , err := client . CoreV1 ( ) . Pods ( f . Namespace . Name ) . Create ( configMap )
Expect ( err ) . To ( BeNil ( ) )
if len ( mutatedPod . Spec . InitContainers ) != 1 {
framework . Failf ( "expect pod to have 1 init container, got %#v" , mutatedPod . Spec . InitContainers )
}
if got , expected := mutatedPod . Spec . InitContainers [ 0 ] . Name , "webhook-added-init-container" ; got != expected {
framework . Failf ( "expect the init container name to be %q, got %q" , expected , got )
}
if got , expected := mutatedPod . Spec . InitContainers [ 0 ] . TerminationMessagePolicy , v1 . TerminationMessageReadFile ; got != expected {
framework . Failf ( "expect the init terminationMessagePolicy to be default to %q, got %q" , expected , got )
}
}
func toBeMutatedPod ( f * framework . Framework ) * v1 . Pod {
return & v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
Name : "webhook-to-be-mutated" ,
} ,
Spec : v1 . PodSpec {
Containers : [ ] v1 . Container {
{
Name : "example" ,
2018-07-18 14:47:22 +00:00
Image : imageutils . GetPauseImageName ( ) ,
2018-03-06 22:33:18 +00:00
} ,
} ,
} ,
}
}
2018-01-09 18:57:14 +00:00
func testWebhook ( f * framework . Framework ) {
By ( "create a pod that should be denied by the webhook" )
client := f . ClientSet
// Creating the pod, the request should be rejected
pod := nonCompliantPod ( f )
_ , err := client . CoreV1 ( ) . Pods ( f . Namespace . Name ) . Create ( pod )
2018-11-26 18:23:56 +00:00
Expect ( err ) . To ( HaveOccurred ( ) , "create pod %s in namespace %s should have been denied by webhook" , pod . Name , f . Namespace . Name )
2018-01-09 18:57:14 +00:00
expectedErrMsg1 := "the pod contains unwanted container name"
if ! strings . Contains ( err . Error ( ) , expectedErrMsg1 ) {
framework . Failf ( "expect error contains %q, got %q" , expectedErrMsg1 , err . Error ( ) )
}
expectedErrMsg2 := "the pod contains unwanted label"
if ! strings . Contains ( err . Error ( ) , expectedErrMsg2 ) {
framework . Failf ( "expect error contains %q, got %q" , expectedErrMsg2 , err . Error ( ) )
}
2018-03-06 22:33:18 +00:00
By ( "create a pod that causes the webhook to hang" )
client = f . ClientSet
// Creating the pod, the request should be rejected
pod = hangingPod ( f )
_ , err = client . CoreV1 ( ) . Pods ( f . Namespace . Name ) . Create ( pod )
2018-11-26 18:23:56 +00:00
Expect ( err ) . To ( HaveOccurred ( ) , "create pod %s in namespace %s should have caused webhook to hang" , pod . Name , f . Namespace . Name )
expectedTimeoutErr := "request did not complete within"
2018-03-06 22:33:18 +00:00
if ! strings . Contains ( err . Error ( ) , expectedTimeoutErr ) {
framework . Failf ( "expect timeout error %q, got %q" , expectedTimeoutErr , err . Error ( ) )
}
2018-01-09 18:57:14 +00:00
By ( "create a configmap that should be denied by the webhook" )
// Creating the configmap, the request should be rejected
configmap := nonCompliantConfigMap ( f )
_ , err = client . CoreV1 ( ) . ConfigMaps ( f . Namespace . Name ) . Create ( configmap )
2018-11-26 18:23:56 +00:00
Expect ( err ) . To ( HaveOccurred ( ) , "create configmap %s in namespace %s should have been denied by the webhook" , configmap . Name , f . Namespace . Name )
2018-01-09 18:57:14 +00:00
expectedErrMsg := "the configmap contains unwanted key and value"
if ! strings . Contains ( err . Error ( ) , expectedErrMsg ) {
framework . Failf ( "expect error contains %q, got %q" , expectedErrMsg , err . Error ( ) )
}
By ( "create a configmap that should be admitted by the webhook" )
// Creating the configmap, the request should be admitted
configmap = & v1 . ConfigMap {
ObjectMeta : metav1 . ObjectMeta {
Name : allowedConfigMapName ,
} ,
Data : map [ string ] string {
"admit" : "this" ,
} ,
}
_ , err = client . CoreV1 ( ) . ConfigMaps ( f . Namespace . Name ) . Create ( configmap )
2018-11-26 18:23:56 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) , "failed to create configmap %s in namespace: %s" , configmap . Name , f . Namespace . Name )
2018-01-09 18:57:14 +00:00
By ( "update (PUT) the admitted configmap to a non-compliant one should be rejected by the webhook" )
toNonCompliantFn := func ( cm * v1 . ConfigMap ) {
if cm . Data == nil {
cm . Data = map [ string ] string { }
}
cm . Data [ "webhook-e2e-test" ] = "webhook-disallow"
}
_ , err = updateConfigMap ( client , f . Namespace . Name , allowedConfigMapName , toNonCompliantFn )
2018-11-26 18:23:56 +00:00
Expect ( err ) . To ( HaveOccurred ( ) , "update (PUT) admitted configmap %s in namespace %s to a non-compliant one should be rejected by webhook" , allowedConfigMapName , f . Namespace . Name )
2018-01-09 18:57:14 +00:00
if ! strings . Contains ( err . Error ( ) , expectedErrMsg ) {
framework . Failf ( "expect error contains %q, got %q" , expectedErrMsg , err . Error ( ) )
}
By ( "update (PATCH) the admitted configmap to a non-compliant one should be rejected by the webhook" )
patch := nonCompliantConfigMapPatch ( )
_ , err = client . CoreV1 ( ) . ConfigMaps ( f . Namespace . Name ) . Patch ( allowedConfigMapName , types . StrategicMergePatchType , [ ] byte ( patch ) )
2018-11-26 18:23:56 +00:00
Expect ( err ) . To ( HaveOccurred ( ) , "update admitted configmap %s in namespace %s by strategic merge patch to a non-compliant one should be rejected by webhook. Patch: %+v" , allowedConfigMapName , f . Namespace . Name , patch )
2018-01-09 18:57:14 +00:00
if ! strings . Contains ( err . Error ( ) , expectedErrMsg ) {
framework . Failf ( "expect error contains %q, got %q" , expectedErrMsg , err . Error ( ) )
}
By ( "create a namespace that bypass the webhook" )
err = createNamespace ( f , & v1 . Namespace { ObjectMeta : metav1 . ObjectMeta {
Name : skippedNamespaceName ,
Labels : map [ string ] string {
skipNamespaceLabelKey : skipNamespaceLabelValue ,
} ,
} } )
framework . ExpectNoError ( err , "creating namespace %q" , skippedNamespaceName )
// clean up the namespace
defer client . CoreV1 ( ) . Namespaces ( ) . Delete ( skippedNamespaceName , nil )
By ( "create a configmap that violates the webhook policy but is in a whitelisted namespace" )
configmap = nonCompliantConfigMap ( f )
_ , err = client . CoreV1 ( ) . ConfigMaps ( skippedNamespaceName ) . Create ( configmap )
2018-11-26 18:23:56 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) , "failed to create configmap %s in namespace: %s" , configmap . Name , skippedNamespaceName )
}
func testAttachingPodWebhook ( f * framework . Framework ) {
By ( "create a pod" )
client := f . ClientSet
pod := toBeAttachedPod ( f )
_ , err := client . CoreV1 ( ) . Pods ( f . Namespace . Name ) . Create ( pod )
Expect ( err ) . NotTo ( HaveOccurred ( ) , "failed to create pod %s in namespace: %s" , pod . Name , f . Namespace . Name )
err = framework . WaitForPodNameRunningInNamespace ( client , pod . Name , f . Namespace . Name )
Expect ( err ) . NotTo ( HaveOccurred ( ) , "error while waiting for pod %s to go to Running phase in namespace: %s" , pod . Name , f . Namespace . Name )
By ( "'kubectl attach' the pod, should be denied by the webhook" )
timer := time . NewTimer ( 30 * time . Second )
defer timer . Stop ( )
_ , err = framework . NewKubectlCommand ( "attach" , fmt . Sprintf ( "--namespace=%v" , f . Namespace . Name ) , pod . Name , "-i" , "-c=container1" ) . WithTimeout ( timer . C ) . Exec ( )
Expect ( err ) . To ( HaveOccurred ( ) , "'kubectl attach' the pod, should be denied by the webhook" )
if e , a := "attaching to pod 'to-be-attached-pod' is not allowed" , err . Error ( ) ; ! strings . Contains ( a , e ) {
framework . Failf ( "unexpected 'kubectl attach' error message. expected to contain %q, got %q" , e , a )
}
2018-01-09 18:57:14 +00:00
}
// failingWebhook returns a webhook with rule of create configmaps,
// but with an invalid client config so that server cannot communicate with it
func failingWebhook ( namespace , name string ) v1beta1 . Webhook {
return v1beta1 . Webhook {
Name : name ,
Rules : [ ] v1beta1 . RuleWithOperations { {
Operations : [ ] v1beta1 . OperationType { v1beta1 . Create } ,
Rule : v1beta1 . Rule {
APIGroups : [ ] string { "" } ,
APIVersions : [ ] string { "v1" } ,
Resources : [ ] string { "configmaps" } ,
} ,
} } ,
ClientConfig : v1beta1 . WebhookClientConfig {
Service : & v1beta1 . ServiceReference {
Namespace : namespace ,
Name : serviceName ,
Path : strPtr ( "/configmaps" ) ,
} ,
// Without CA bundle, the call to webhook always fails
CABundle : nil ,
} ,
}
}
2018-03-06 22:33:18 +00:00
func registerFailClosedWebhook ( f * framework . Framework , context * certContext ) func ( ) {
2018-01-09 18:57:14 +00:00
client := f . ClientSet
By ( "Registering a webhook that server cannot talk to, with fail closed policy, via the AdmissionRegistration API" )
namespace := f . Namespace . Name
2018-03-06 22:33:18 +00:00
configName := webhookFailClosedConfigName
2018-01-09 18:57:14 +00:00
// A webhook that cannot talk to server, with fail-closed policy
policyFail := v1beta1 . Fail
hook := failingWebhook ( namespace , "fail-closed.k8s.io" )
hook . FailurePolicy = & policyFail
hook . NamespaceSelector = & metav1 . LabelSelector {
MatchExpressions : [ ] metav1 . LabelSelectorRequirement {
{
Key : failNamespaceLabelKey ,
Operator : metav1 . LabelSelectorOpIn ,
Values : [ ] string { failNamespaceLabelValue } ,
} ,
} ,
}
_ , err := client . AdmissionregistrationV1beta1 ( ) . ValidatingWebhookConfigurations ( ) . Create ( & v1beta1 . ValidatingWebhookConfiguration {
ObjectMeta : metav1 . ObjectMeta {
2018-03-06 22:33:18 +00:00
Name : configName ,
2018-01-09 18:57:14 +00:00
} ,
Webhooks : [ ] v1beta1 . Webhook {
// Server cannot talk to this webhook, so it always fails.
// Because this webhook is configured fail-closed, request should be rejected after the call fails.
hook ,
} ,
} )
2018-03-06 22:33:18 +00:00
framework . ExpectNoError ( err , "registering webhook config %s with namespace %s" , configName , namespace )
2018-01-09 18:57:14 +00:00
// The webhook configuration is honored in 10s.
time . Sleep ( 10 * time . Second )
2018-03-06 22:33:18 +00:00
return func ( ) {
f . ClientSet . AdmissionregistrationV1beta1 ( ) . ValidatingWebhookConfigurations ( ) . Delete ( configName , nil )
}
2018-01-09 18:57:14 +00:00
}
func testFailClosedWebhook ( f * framework . Framework ) {
client := f . ClientSet
By ( "create a namespace for the webhook" )
err := createNamespace ( f , & v1 . Namespace { ObjectMeta : metav1 . ObjectMeta {
Name : failNamespaceName ,
Labels : map [ string ] string {
failNamespaceLabelKey : failNamespaceLabelValue ,
} ,
} } )
framework . ExpectNoError ( err , "creating namespace %q" , failNamespaceName )
defer client . CoreV1 ( ) . Namespaces ( ) . Delete ( failNamespaceName , nil )
By ( "create a configmap should be unconditionally rejected by the webhook" )
configmap := & v1 . ConfigMap {
ObjectMeta : metav1 . ObjectMeta {
Name : "foo" ,
} ,
}
_ , err = client . CoreV1 ( ) . ConfigMaps ( failNamespaceName ) . Create ( configmap )
2018-11-26 18:23:56 +00:00
Expect ( err ) . To ( HaveOccurred ( ) , "create configmap in namespace: %s should be unconditionally rejected by the webhook" , failNamespaceName )
2018-01-09 18:57:14 +00:00
if ! errors . IsInternalError ( err ) {
framework . Failf ( "expect an internal error, got %#v" , err )
}
}
2018-11-26 18:23:56 +00:00
func registerValidatingWebhookForWebhookConfigurations ( f * framework . Framework , context * certContext ) func ( ) {
2018-07-18 14:47:22 +00:00
var err error
client := f . ClientSet
2018-11-26 18:23:56 +00:00
By ( "Registering a validating webhook on ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects, via the AdmissionRegistration API" )
2018-07-18 14:47:22 +00:00
namespace := f . Namespace . Name
2018-11-26 18:23:56 +00:00
configName := validatingWebhookForWebhooksConfigName
2018-07-18 14:47:22 +00:00
failurePolicy := v1beta1 . Fail
2018-11-26 18:23:56 +00:00
// This webhook denies all requests to Delete validating webhook configuration and
// mutating webhook configuration objects. It should never be called, however, because
// dynamic admission webhooks should not be called on requests involving webhook configuration objects.
2018-07-18 14:47:22 +00:00
_ , err = client . AdmissionregistrationV1beta1 ( ) . ValidatingWebhookConfigurations ( ) . Create ( & v1beta1 . ValidatingWebhookConfiguration {
ObjectMeta : metav1 . ObjectMeta {
Name : configName ,
} ,
Webhooks : [ ] v1beta1 . Webhook {
{
Name : "deny-webhook-configuration-deletions.k8s.io" ,
Rules : [ ] v1beta1 . RuleWithOperations { {
Operations : [ ] v1beta1 . OperationType { v1beta1 . Delete } ,
Rule : v1beta1 . Rule {
APIGroups : [ ] string { "admissionregistration.k8s.io" } ,
APIVersions : [ ] string { "*" } ,
Resources : [ ] string {
"validatingwebhookconfigurations" ,
"mutatingwebhookconfigurations" ,
} ,
} ,
} } ,
ClientConfig : v1beta1 . WebhookClientConfig {
Service : & v1beta1 . ServiceReference {
Namespace : namespace ,
Name : serviceName ,
Path : strPtr ( "/always-deny" ) ,
} ,
CABundle : context . signingCert ,
} ,
FailurePolicy : & failurePolicy ,
} ,
} ,
} )
framework . ExpectNoError ( err , "registering webhook config %s with namespace %s" , configName , namespace )
// The webhook configuration is honored in 10s.
time . Sleep ( 10 * time . Second )
return func ( ) {
err := client . AdmissionregistrationV1beta1 ( ) . ValidatingWebhookConfigurations ( ) . Delete ( configName , nil )
framework . ExpectNoError ( err , "deleting webhook config %s with namespace %s" , configName , namespace )
}
}
2018-11-26 18:23:56 +00:00
func registerMutatingWebhookForWebhookConfigurations ( f * framework . Framework , context * certContext ) func ( ) {
var err error
client := f . ClientSet
By ( "Registering a mutating webhook on ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects, via the AdmissionRegistration API" )
namespace := f . Namespace . Name
configName := mutatingWebhookForWebhooksConfigName
failurePolicy := v1beta1 . Fail
// This webhook adds a label to all requests create to validating webhook configuration and
// mutating webhook configuration objects. It should never be called, however, because
// dynamic admission webhooks should not be called on requests involving webhook configuration objects.
_ , err = client . AdmissionregistrationV1beta1 ( ) . MutatingWebhookConfigurations ( ) . Create ( & v1beta1 . MutatingWebhookConfiguration {
ObjectMeta : metav1 . ObjectMeta {
Name : configName ,
} ,
Webhooks : [ ] v1beta1 . Webhook {
{
Name : "add-label-to-webhook-configurations.k8s.io" ,
Rules : [ ] v1beta1 . RuleWithOperations { {
Operations : [ ] v1beta1 . OperationType { v1beta1 . Create } ,
Rule : v1beta1 . Rule {
APIGroups : [ ] string { "admissionregistration.k8s.io" } ,
APIVersions : [ ] string { "*" } ,
Resources : [ ] string {
"validatingwebhookconfigurations" ,
"mutatingwebhookconfigurations" ,
} ,
} ,
} } ,
ClientConfig : v1beta1 . WebhookClientConfig {
Service : & v1beta1 . ServiceReference {
Namespace : namespace ,
Name : serviceName ,
Path : strPtr ( "/add-label" ) ,
} ,
CABundle : context . signingCert ,
} ,
FailurePolicy : & failurePolicy ,
} ,
} ,
} )
framework . ExpectNoError ( err , "registering webhook config %s with namespace %s" , configName , namespace )
// The webhook configuration is honored in 10s.
time . Sleep ( 10 * time . Second )
return func ( ) {
err := client . AdmissionregistrationV1beta1 ( ) . MutatingWebhookConfigurations ( ) . Delete ( configName , nil )
framework . ExpectNoError ( err , "deleting webhook config %s with namespace %s" , configName , namespace )
}
}
2018-07-18 14:47:22 +00:00
// This test assumes that the deletion-rejecting webhook defined in
2018-11-26 18:23:56 +00:00
// registerValidatingWebhookForWebhookConfigurations and the webhook-config-mutating
// webhook defined in registerMutatingWebhookForWebhookConfigurations already exist.
func testWebhooksForWebhookConfigurations ( f * framework . Framework ) {
2018-07-18 14:47:22 +00:00
var err error
client := f . ClientSet
2018-11-26 18:23:56 +00:00
By ( "Creating a dummy validating-webhook-configuration object" )
2018-07-18 14:47:22 +00:00
namespace := f . Namespace . Name
failurePolicy := v1beta1 . Ignore
2018-11-26 18:23:56 +00:00
mutatedValidatingWebhookConfiguration , err := client . AdmissionregistrationV1beta1 ( ) . ValidatingWebhookConfigurations ( ) . Create ( & v1beta1 . ValidatingWebhookConfiguration {
2018-07-18 14:47:22 +00:00
ObjectMeta : metav1 . ObjectMeta {
2018-11-26 18:23:56 +00:00
Name : dummyValidatingWebhookConfigName ,
2018-07-18 14:47:22 +00:00
} ,
Webhooks : [ ] v1beta1 . Webhook {
{
2018-11-26 18:23:56 +00:00
Name : "dummy-validating-webhook.k8s.io" ,
2018-07-18 14:47:22 +00:00
Rules : [ ] v1beta1 . RuleWithOperations { {
Operations : [ ] v1beta1 . OperationType { v1beta1 . Create } ,
// This will not match any real resources so this webhook should never be called.
Rule : v1beta1 . Rule {
APIGroups : [ ] string { "" } ,
APIVersions : [ ] string { "v1" } ,
Resources : [ ] string { "invalid" } ,
} ,
} } ,
ClientConfig : v1beta1 . WebhookClientConfig {
Service : & v1beta1 . ServiceReference {
Namespace : namespace ,
Name : serviceName ,
// This path not recognized by the webhook service,
// so the call to this webhook will always fail,
// but because the failure policy is ignore, it will
// have no effect on admission requests.
Path : strPtr ( "" ) ,
} ,
CABundle : nil ,
} ,
FailurePolicy : & failurePolicy ,
} ,
} ,
} )
2018-11-26 18:23:56 +00:00
framework . ExpectNoError ( err , "registering webhook config %s with namespace %s" , dummyValidatingWebhookConfigName , namespace )
if mutatedValidatingWebhookConfiguration . ObjectMeta . Labels != nil && mutatedValidatingWebhookConfiguration . ObjectMeta . Labels [ addedLabelKey ] == addedLabelValue {
framework . Failf ( "expected %s not to be mutated by mutating webhooks but it was" , dummyValidatingWebhookConfigName )
}
2018-07-18 14:47:22 +00:00
// The webhook configuration is honored in 10s.
time . Sleep ( 10 * time . Second )
By ( "Deleting the validating-webhook-configuration, which should be possible to remove" )
2018-11-26 18:23:56 +00:00
err = client . AdmissionregistrationV1beta1 ( ) . ValidatingWebhookConfigurations ( ) . Delete ( dummyValidatingWebhookConfigName , nil )
framework . ExpectNoError ( err , "deleting webhook config %s with namespace %s" , dummyValidatingWebhookConfigName , namespace )
2018-07-18 14:47:22 +00:00
2018-11-26 18:23:56 +00:00
By ( "Creating a dummy mutating-webhook-configuration object" )
2018-07-18 14:47:22 +00:00
2018-11-26 18:23:56 +00:00
mutatedMutatingWebhookConfiguration , err := client . AdmissionregistrationV1beta1 ( ) . MutatingWebhookConfigurations ( ) . Create ( & v1beta1 . MutatingWebhookConfiguration {
2018-07-18 14:47:22 +00:00
ObjectMeta : metav1 . ObjectMeta {
2018-11-26 18:23:56 +00:00
Name : dummyMutatingWebhookConfigName ,
2018-07-18 14:47:22 +00:00
} ,
Webhooks : [ ] v1beta1 . Webhook {
{
2018-11-26 18:23:56 +00:00
Name : "dummy-mutating-webhook.k8s.io" ,
2018-07-18 14:47:22 +00:00
Rules : [ ] v1beta1 . RuleWithOperations { {
Operations : [ ] v1beta1 . OperationType { v1beta1 . Create } ,
// This will not match any real resources so this webhook should never be called.
Rule : v1beta1 . Rule {
APIGroups : [ ] string { "" } ,
APIVersions : [ ] string { "v1" } ,
Resources : [ ] string { "invalid" } ,
} ,
} } ,
ClientConfig : v1beta1 . WebhookClientConfig {
Service : & v1beta1 . ServiceReference {
Namespace : namespace ,
Name : serviceName ,
// This path not recognized by the webhook service,
// so the call to this webhook will always fail,
// but because the failure policy is ignore, it will
// have no effect on admission requests.
Path : strPtr ( "" ) ,
} ,
CABundle : nil ,
} ,
FailurePolicy : & failurePolicy ,
} ,
} ,
} )
2018-11-26 18:23:56 +00:00
framework . ExpectNoError ( err , "registering webhook config %s with namespace %s" , dummyMutatingWebhookConfigName , namespace )
if mutatedMutatingWebhookConfiguration . ObjectMeta . Labels != nil && mutatedMutatingWebhookConfiguration . ObjectMeta . Labels [ addedLabelKey ] == addedLabelValue {
framework . Failf ( "expected %s not to be mutated by mutating webhooks but it was" , dummyMutatingWebhookConfigName )
}
2018-07-18 14:47:22 +00:00
// The webhook configuration is honored in 10s.
time . Sleep ( 10 * time . Second )
By ( "Deleting the mutating-webhook-configuration, which should be possible to remove" )
2018-11-26 18:23:56 +00:00
err = client . AdmissionregistrationV1beta1 ( ) . MutatingWebhookConfigurations ( ) . Delete ( dummyMutatingWebhookConfigName , nil )
framework . ExpectNoError ( err , "deleting webhook config %s with namespace %s" , dummyMutatingWebhookConfigName , namespace )
2018-07-18 14:47:22 +00:00
}
2018-01-09 18:57:14 +00:00
func createNamespace ( f * framework . Framework , ns * v1 . Namespace ) error {
return wait . PollImmediate ( 100 * time . Millisecond , 30 * time . Second , func ( ) ( bool , error ) {
_ , err := f . ClientSet . CoreV1 ( ) . Namespaces ( ) . Create ( ns )
if err != nil {
if strings . HasPrefix ( err . Error ( ) , "object is being deleted:" ) {
return false , nil
}
return false , err
}
return true , nil
} )
}
func nonCompliantPod ( f * framework . Framework ) * v1 . Pod {
return & v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
Name : disallowedPodName ,
Labels : map [ string ] string {
"webhook-e2e-test" : "webhook-disallow" ,
} ,
} ,
Spec : v1 . PodSpec {
Containers : [ ] v1 . Container {
{
Name : "webhook-disallow" ,
2018-07-18 14:47:22 +00:00
Image : imageutils . GetPauseImageName ( ) ,
2018-01-09 18:57:14 +00:00
} ,
} ,
} ,
}
}
2018-03-06 22:33:18 +00:00
func hangingPod ( f * framework . Framework ) * v1 . Pod {
return & v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
Name : hangingPodName ,
Labels : map [ string ] string {
"webhook-e2e-test" : "wait-forever" ,
} ,
} ,
Spec : v1 . PodSpec {
Containers : [ ] v1 . Container {
{
Name : "wait-forever" ,
2018-07-18 14:47:22 +00:00
Image : imageutils . GetPauseImageName ( ) ,
2018-03-06 22:33:18 +00:00
} ,
} ,
} ,
}
}
2018-11-26 18:23:56 +00:00
func toBeAttachedPod ( f * framework . Framework ) * v1 . Pod {
return & v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
Name : toBeAttachedPodName ,
} ,
Spec : v1 . PodSpec {
Containers : [ ] v1 . Container {
{
Name : "container1" ,
Image : imageutils . GetPauseImageName ( ) ,
} ,
} ,
} ,
}
}
2018-01-09 18:57:14 +00:00
func nonCompliantConfigMap ( f * framework . Framework ) * v1 . ConfigMap {
return & v1 . ConfigMap {
ObjectMeta : metav1 . ObjectMeta {
Name : disallowedConfigMapName ,
} ,
Data : map [ string ] string {
"webhook-e2e-test" : "webhook-disallow" ,
} ,
}
}
func toBeMutatedConfigMap ( f * framework . Framework ) * v1 . ConfigMap {
return & v1 . ConfigMap {
ObjectMeta : metav1 . ObjectMeta {
Name : "to-be-mutated" ,
} ,
Data : map [ string ] string {
"mutation-start" : "yes" ,
} ,
}
}
func nonCompliantConfigMapPatch ( ) string {
return fmt . Sprint ( ` { "data": { "webhook-e2e-test":"webhook-disallow"}} ` )
}
type updateConfigMapFn func ( cm * v1 . ConfigMap )
func updateConfigMap ( c clientset . Interface , ns , name string , update updateConfigMapFn ) ( * v1 . ConfigMap , error ) {
var cm * v1 . ConfigMap
pollErr := wait . PollImmediate ( 2 * time . Second , 1 * time . Minute , func ( ) ( bool , error ) {
var err error
if cm , err = c . CoreV1 ( ) . ConfigMaps ( ns ) . Get ( name , metav1 . GetOptions { } ) ; err != nil {
return false , err
}
update ( cm )
if cm , err = c . CoreV1 ( ) . ConfigMaps ( ns ) . Update ( cm ) ; err == nil {
return true , nil
}
// Only retry update on conflict
if ! errors . IsConflict ( err ) {
return false , err
}
return false , nil
} )
return cm , pollErr
}
func cleanWebhookTest ( client clientset . Interface , namespaceName string ) {
_ = client . CoreV1 ( ) . Services ( namespaceName ) . Delete ( serviceName , nil )
2018-07-18 14:47:22 +00:00
_ = client . AppsV1 ( ) . Deployments ( namespaceName ) . Delete ( deploymentName , nil )
2018-01-09 18:57:14 +00:00
_ = client . CoreV1 ( ) . Secrets ( namespaceName ) . Delete ( secretName , nil )
_ = client . RbacV1beta1 ( ) . RoleBindings ( "kube-system" ) . Delete ( roleBindingName , nil )
}
2018-07-18 14:47:22 +00:00
func registerWebhookForCustomResource ( f * framework . Framework , context * certContext , testcrd * framework . TestCrd ) func ( ) {
2018-01-09 18:57:14 +00:00
client := f . ClientSet
2018-07-18 14:47:22 +00:00
By ( "Registering the custom resource webhook via the AdmissionRegistration API" )
2018-01-09 18:57:14 +00:00
namespace := f . Namespace . Name
2018-07-18 14:47:22 +00:00
configName := crWebhookConfigName
2018-01-09 18:57:14 +00:00
_ , err := client . AdmissionregistrationV1beta1 ( ) . ValidatingWebhookConfigurations ( ) . Create ( & v1beta1 . ValidatingWebhookConfiguration {
ObjectMeta : metav1 . ObjectMeta {
2018-03-06 22:33:18 +00:00
Name : configName ,
2018-01-09 18:57:14 +00:00
} ,
Webhooks : [ ] v1beta1 . Webhook {
{
2018-07-18 14:47:22 +00:00
Name : "deny-unwanted-custom-resource-data.k8s.io" ,
2018-01-09 18:57:14 +00:00
Rules : [ ] v1beta1 . RuleWithOperations { {
Operations : [ ] v1beta1 . OperationType { v1beta1 . Create } ,
Rule : v1beta1 . Rule {
2018-03-06 22:33:18 +00:00
APIGroups : [ ] string { testcrd . ApiGroup } ,
2018-11-26 18:23:56 +00:00
APIVersions : testcrd . GetAPIVersions ( ) ,
2018-03-06 22:33:18 +00:00
Resources : [ ] string { testcrd . GetPluralName ( ) } ,
2018-01-09 18:57:14 +00:00
} ,
} } ,
ClientConfig : v1beta1 . WebhookClientConfig {
Service : & v1beta1 . ServiceReference {
Namespace : namespace ,
Name : serviceName ,
2018-07-18 14:47:22 +00:00
Path : strPtr ( "/custom-resource" ) ,
2018-01-09 18:57:14 +00:00
} ,
CABundle : context . signingCert ,
} ,
} ,
} ,
} )
2018-07-18 14:47:22 +00:00
framework . ExpectNoError ( err , "registering custom resource webhook config %s with namespace %s" , configName , namespace )
2018-01-09 18:57:14 +00:00
2018-07-18 14:47:22 +00:00
// The webhook configuration is honored in 10s.
2018-01-09 18:57:14 +00:00
time . Sleep ( 10 * time . Second )
2018-03-06 22:33:18 +00:00
return func ( ) {
client . AdmissionregistrationV1beta1 ( ) . ValidatingWebhookConfigurations ( ) . Delete ( configName , nil )
}
2018-01-09 18:57:14 +00:00
}
2018-07-18 14:47:22 +00:00
func registerMutatingWebhookForCustomResource ( f * framework . Framework , context * certContext , testcrd * framework . TestCrd ) func ( ) {
2018-01-09 18:57:14 +00:00
client := f . ClientSet
2018-07-18 14:47:22 +00:00
By ( "Registering the mutating webhook for a custom resource via the AdmissionRegistration API" )
2018-01-09 18:57:14 +00:00
namespace := f . Namespace . Name
2018-07-18 14:47:22 +00:00
configName := crMutatingWebhookConfigName
2018-01-09 18:57:14 +00:00
_ , err := client . AdmissionregistrationV1beta1 ( ) . MutatingWebhookConfigurations ( ) . Create ( & v1beta1 . MutatingWebhookConfiguration {
ObjectMeta : metav1 . ObjectMeta {
2018-03-06 22:33:18 +00:00
Name : configName ,
2018-01-09 18:57:14 +00:00
} ,
Webhooks : [ ] v1beta1 . Webhook {
{
2018-07-18 14:47:22 +00:00
Name : "mutate-custom-resource-data-stage-1.k8s.io" ,
2018-01-09 18:57:14 +00:00
Rules : [ ] v1beta1 . RuleWithOperations { {
Operations : [ ] v1beta1 . OperationType { v1beta1 . Create } ,
Rule : v1beta1 . Rule {
2018-03-06 22:33:18 +00:00
APIGroups : [ ] string { testcrd . ApiGroup } ,
2018-11-26 18:23:56 +00:00
APIVersions : testcrd . GetAPIVersions ( ) ,
2018-03-06 22:33:18 +00:00
Resources : [ ] string { testcrd . GetPluralName ( ) } ,
2018-01-09 18:57:14 +00:00
} ,
} } ,
ClientConfig : v1beta1 . WebhookClientConfig {
Service : & v1beta1 . ServiceReference {
Namespace : namespace ,
Name : serviceName ,
2018-07-18 14:47:22 +00:00
Path : strPtr ( "/mutating-custom-resource" ) ,
2018-01-09 18:57:14 +00:00
} ,
CABundle : context . signingCert ,
} ,
} ,
{
2018-07-18 14:47:22 +00:00
Name : "mutate-custom-resource-data-stage-2.k8s.io" ,
2018-01-09 18:57:14 +00:00
Rules : [ ] v1beta1 . RuleWithOperations { {
Operations : [ ] v1beta1 . OperationType { v1beta1 . Create } ,
Rule : v1beta1 . Rule {
2018-03-06 22:33:18 +00:00
APIGroups : [ ] string { testcrd . ApiGroup } ,
2018-11-26 18:23:56 +00:00
APIVersions : testcrd . GetAPIVersions ( ) ,
2018-03-06 22:33:18 +00:00
Resources : [ ] string { testcrd . GetPluralName ( ) } ,
2018-01-09 18:57:14 +00:00
} ,
} } ,
ClientConfig : v1beta1 . WebhookClientConfig {
Service : & v1beta1 . ServiceReference {
Namespace : namespace ,
Name : serviceName ,
2018-07-18 14:47:22 +00:00
Path : strPtr ( "/mutating-custom-resource" ) ,
2018-01-09 18:57:14 +00:00
} ,
CABundle : context . signingCert ,
} ,
} ,
} ,
} )
2018-07-18 14:47:22 +00:00
framework . ExpectNoError ( err , "registering custom resource webhook config %s with namespace %s" , configName , namespace )
2018-01-09 18:57:14 +00:00
2018-07-18 14:47:22 +00:00
// The webhook configuration is honored in 10s.
2018-01-09 18:57:14 +00:00
time . Sleep ( 10 * time . Second )
2018-03-06 22:33:18 +00:00
return func ( ) { client . AdmissionregistrationV1beta1 ( ) . MutatingWebhookConfigurations ( ) . Delete ( configName , nil ) }
2018-01-09 18:57:14 +00:00
}
2018-07-18 14:47:22 +00:00
func testCustomResourceWebhook ( f * framework . Framework , crd * apiextensionsv1beta1 . CustomResourceDefinition , customResourceClient dynamic . ResourceInterface ) {
2018-01-09 18:57:14 +00:00
By ( "Creating a custom resource that should be denied by the webhook" )
2018-11-26 18:23:56 +00:00
crInstanceName := "cr-instance-1"
2018-01-09 18:57:14 +00:00
crInstance := & unstructured . Unstructured {
Object : map [ string ] interface { } {
"kind" : crd . Spec . Names . Kind ,
"apiVersion" : crd . Spec . Group + "/" + crd . Spec . Version ,
"metadata" : map [ string ] interface { } {
2018-11-26 18:23:56 +00:00
"name" : crInstanceName ,
2018-01-09 18:57:14 +00:00
"namespace" : f . Namespace . Name ,
} ,
"data" : map [ string ] interface { } {
"webhook-e2e-test" : "webhook-disallow" ,
} ,
} ,
}
2018-11-26 18:23:56 +00:00
_ , err := customResourceClient . Create ( crInstance , metav1 . CreateOptions { } )
Expect ( err ) . To ( HaveOccurred ( ) , "create custom resource %s in namespace %s should be denied by webhook" , crInstanceName , f . Namespace . Name )
2018-01-09 18:57:14 +00:00
expectedErrMsg := "the custom resource contains unwanted data"
if ! strings . Contains ( err . Error ( ) , expectedErrMsg ) {
framework . Failf ( "expect error contains %q, got %q" , expectedErrMsg , err . Error ( ) )
}
}
2018-07-18 14:47:22 +00:00
func testMutatingCustomResourceWebhook ( f * framework . Framework , crd * apiextensionsv1beta1 . CustomResourceDefinition , customResourceClient dynamic . ResourceInterface ) {
2018-01-09 18:57:14 +00:00
By ( "Creating a custom resource that should be mutated by the webhook" )
2018-11-26 18:23:56 +00:00
crName := "cr-instance-1"
2018-01-09 18:57:14 +00:00
cr := & unstructured . Unstructured {
Object : map [ string ] interface { } {
"kind" : crd . Spec . Names . Kind ,
"apiVersion" : crd . Spec . Group + "/" + crd . Spec . Version ,
"metadata" : map [ string ] interface { } {
2018-11-26 18:23:56 +00:00
"name" : crName ,
2018-01-09 18:57:14 +00:00
"namespace" : f . Namespace . Name ,
} ,
"data" : map [ string ] interface { } {
"mutation-start" : "yes" ,
} ,
} ,
}
2018-11-26 18:23:56 +00:00
mutatedCR , err := customResourceClient . Create ( cr , metav1 . CreateOptions { } )
Expect ( err ) . NotTo ( HaveOccurred ( ) , "failed to create custom resource %s in namespace: %s" , crName , f . Namespace . Name )
2018-01-09 18:57:14 +00:00
expectedCRData := map [ string ] interface { } {
"mutation-start" : "yes" ,
"mutation-stage-1" : "yes" ,
"mutation-stage-2" : "yes" ,
}
if ! reflect . DeepEqual ( expectedCRData , mutatedCR . Object [ "data" ] ) {
framework . Failf ( "\nexpected %#v\n, got %#v\n" , expectedCRData , mutatedCR . Object [ "data" ] )
}
}
2018-07-18 14:47:22 +00:00
func registerValidatingWebhookForCRD ( f * framework . Framework , context * certContext ) func ( ) {
client := f . ClientSet
By ( "Registering the crd webhook via the AdmissionRegistration API" )
namespace := f . Namespace . Name
configName := crdWebhookConfigName
// This webhook will deny the creation of CustomResourceDefinitions which have the
// label "webhook-e2e-test":"webhook-disallow"
// NOTE: Because tests are run in parallel and in an unpredictable order, it is critical
// that no other test attempts to create CRD with that label.
_ , err := client . AdmissionregistrationV1beta1 ( ) . ValidatingWebhookConfigurations ( ) . Create ( & v1beta1 . ValidatingWebhookConfiguration {
ObjectMeta : metav1 . ObjectMeta {
Name : configName ,
} ,
Webhooks : [ ] v1beta1 . Webhook {
{
Name : "deny-crd-with-unwanted-label.k8s.io" ,
Rules : [ ] v1beta1 . RuleWithOperations { {
Operations : [ ] v1beta1 . OperationType { v1beta1 . Create } ,
Rule : v1beta1 . Rule {
APIGroups : [ ] string { "apiextensions.k8s.io" } ,
APIVersions : [ ] string { "*" } ,
Resources : [ ] string { "customresourcedefinitions" } ,
} ,
} } ,
ClientConfig : v1beta1 . WebhookClientConfig {
Service : & v1beta1 . ServiceReference {
Namespace : namespace ,
Name : serviceName ,
Path : strPtr ( "/crd" ) ,
} ,
CABundle : context . signingCert ,
} ,
} ,
} ,
} )
framework . ExpectNoError ( err , "registering crd webhook config %s with namespace %s" , configName , namespace )
// The webhook configuration is honored in 10s.
time . Sleep ( 10 * time . Second )
return func ( ) {
client . AdmissionregistrationV1beta1 ( ) . ValidatingWebhookConfigurations ( ) . Delete ( configName , nil )
}
}
func testCRDDenyWebhook ( f * framework . Framework ) {
By ( "Creating a custom resource definition that should be denied by the webhook" )
name := fmt . Sprintf ( "e2e-test-%s-%s-crd" , f . BaseName , "deny" )
kind := fmt . Sprintf ( "E2e-test-%s-%s-crd" , f . BaseName , "deny" )
group := fmt . Sprintf ( "%s-crd-test.k8s.io" , f . BaseName )
2018-11-26 18:23:56 +00:00
apiVersions := [ ] apiextensionsv1beta1 . CustomResourceDefinitionVersion {
{
Name : "v1" ,
Served : true ,
Storage : true ,
} ,
}
2018-07-18 14:47:22 +00:00
testcrd := & framework . TestCrd {
2018-11-26 18:23:56 +00:00
Name : name ,
Kind : kind ,
ApiGroup : group ,
Versions : apiVersions ,
2018-07-18 14:47:22 +00:00
}
// Creating a custom resource definition for use by assorted tests.
config , err := framework . LoadConfig ( )
if err != nil {
framework . Failf ( "failed to load config: %v" , err )
return
}
apiExtensionClient , err := crdclientset . NewForConfig ( config )
if err != nil {
framework . Failf ( "failed to initialize apiExtensionClient: %v" , err )
return
}
crd := & apiextensionsv1beta1 . CustomResourceDefinition {
ObjectMeta : metav1 . ObjectMeta {
Name : testcrd . GetMetaName ( ) ,
Labels : map [ string ] string {
"webhook-e2e-test" : "webhook-disallow" ,
} ,
} ,
Spec : apiextensionsv1beta1 . CustomResourceDefinitionSpec {
2018-11-26 18:23:56 +00:00
Group : testcrd . ApiGroup ,
Versions : testcrd . Versions ,
2018-07-18 14:47:22 +00:00
Names : apiextensionsv1beta1 . CustomResourceDefinitionNames {
Plural : testcrd . GetPluralName ( ) ,
Singular : testcrd . Name ,
Kind : testcrd . Kind ,
ListKind : testcrd . GetListName ( ) ,
} ,
Scope : apiextensionsv1beta1 . NamespaceScoped ,
} ,
}
// create CRD
_ , err = apiExtensionClient . ApiextensionsV1beta1 ( ) . CustomResourceDefinitions ( ) . Create ( crd )
2018-11-26 18:23:56 +00:00
Expect ( err ) . To ( HaveOccurred ( ) , "create custom resource definition %s should be denied by webhook" , testcrd . GetMetaName ( ) )
2018-07-18 14:47:22 +00:00
expectedErrMsg := "the crd contains unwanted label"
if ! strings . Contains ( err . Error ( ) , expectedErrMsg ) {
framework . Failf ( "expect error contains %q, got %q" , expectedErrMsg , err . Error ( ) )
}
}