mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Upgrade AdmissionReview e2e test image to also support v1
This commit is contained in:
parent
f4e39afea0
commit
4f7543e42a
@ -9,7 +9,12 @@ go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["fuzzer.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/apis/admission/fuzzer",
|
||||
deps = ["//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library"],
|
||||
deps = [
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||
"//vendor/github.com/google/gofuzz:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
|
@ -17,10 +17,23 @@ limitations under the License.
|
||||
package fuzzer
|
||||
|
||||
import (
|
||||
fuzz "github.com/google/gofuzz"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
)
|
||||
|
||||
// Funcs returns the fuzzer functions for the admission api group.
|
||||
var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
|
||||
return []interface{}{}
|
||||
return []interface{}{
|
||||
func(s *runtime.RawExtension, c fuzz.Continue) {
|
||||
u := &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "unknown.group/unknown",
|
||||
"kind": "Something",
|
||||
"somekey": "somevalue",
|
||||
}}
|
||||
s.Object = u
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
2.4
|
||||
2.5
|
||||
|
BIN
test/images/agnhost/agnhost
Executable file
BIN
test/images/agnhost/agnhost
Executable file
Binary file not shown.
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package crdconvwebhook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@ -27,6 +28,7 @@ import (
|
||||
var (
|
||||
certFile string
|
||||
keyFile string
|
||||
port int
|
||||
)
|
||||
|
||||
// CmdCrdConversionWebhook is used by agnhost Cobra.
|
||||
@ -48,6 +50,8 @@ func init() {
|
||||
"after server cert.")
|
||||
CmdCrdConversionWebhook.Flags().StringVar(&keyFile, "tls-private-key-file", "",
|
||||
"File containing the default x509 private key matching --tls-cert-file.")
|
||||
CmdCrdConversionWebhook.Flags().IntVar(&port, "port", 443,
|
||||
"Secure port that the webhook listens on")
|
||||
}
|
||||
|
||||
// Config contains the server (the webhook) cert and key.
|
||||
@ -62,8 +66,11 @@ func main(cmd *cobra.Command, args []string) {
|
||||
http.HandleFunc("/crdconvert", converter.ServeExampleConvert)
|
||||
clientset := getClient()
|
||||
server := &http.Server{
|
||||
Addr: ":443",
|
||||
Addr: fmt.Sprintf(":%d", port),
|
||||
TLSConfig: configTLS(config, clientset),
|
||||
}
|
||||
server.ListenAndServeTLS("", "")
|
||||
err := server.ListenAndServeTLS("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ go_library(
|
||||
"alwaysdeny.go",
|
||||
"config.go",
|
||||
"configmap.go",
|
||||
"convert.go",
|
||||
"crd.go",
|
||||
"customresource.go",
|
||||
"main.go",
|
||||
@ -17,7 +18,9 @@ go_library(
|
||||
importpath = "k8s.io/kubernetes/test/images/agnhost/webhook",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//staging/src/k8s.io/api/admission/v1:go_default_library",
|
||||
"//staging/src/k8s.io/api/admission/v1beta1:go_default_library",
|
||||
"//staging/src/k8s.io/api/admissionregistration/v1:go_default_library",
|
||||
"//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
|
||||
@ -48,15 +51,22 @@ go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"addlabel_test.go",
|
||||
"convert_test.go",
|
||||
"patch_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/admission/fuzzer:go_default_library",
|
||||
"//staging/src/k8s.io/api/admission/v1:go_default_library",
|
||||
"//staging/src/k8s.io/api/admission/v1beta1:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/api/apitesting/fuzzer:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
||||
"//vendor/github.com/evanphx/json-patch:go_default_library",
|
||||
"//vendor/github.com/google/gofuzz:go_default_library",
|
||||
],
|
||||
)
|
||||
|
@ -19,7 +19,7 @@ package webhook
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/api/admission/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
@ -37,7 +37,7 @@ const (
|
||||
)
|
||||
|
||||
// Add a label {"added-label": "yes"} to the object
|
||||
func addLabel(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
func addLabel(ar v1.AdmissionReview) *v1.AdmissionResponse {
|
||||
klog.V(2).Info("calling add-label")
|
||||
obj := struct {
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
@ -46,13 +46,13 @@ func addLabel(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
err := json.Unmarshal(raw, &obj)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return toAdmissionResponse(err)
|
||||
return toV1AdmissionResponse(err)
|
||||
}
|
||||
|
||||
reviewResponse := v1beta1.AdmissionResponse{}
|
||||
reviewResponse := v1.AdmissionResponse{}
|
||||
reviewResponse.Allowed = true
|
||||
|
||||
pt := v1beta1.PatchTypeJSONPatch
|
||||
pt := v1.PatchTypeJSONPatch
|
||||
labelValue, hasLabel := obj.ObjectMeta.Labels["added-label"]
|
||||
switch {
|
||||
case len(obj.ObjectMeta.Labels) == 0:
|
||||
|
@ -22,7 +22,7 @@ import (
|
||||
"testing"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/api/admission/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@ -58,7 +58,7 @@ func TestAddLabel(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
review := v1beta1.AdmissionReview{Request: &v1beta1.AdmissionRequest{Object: runtime.RawExtension{Raw: raw}}}
|
||||
review := v1.AdmissionReview{Request: &v1.AdmissionRequest{Object: runtime.RawExtension{Raw: raw}}}
|
||||
response := addLabel(review)
|
||||
if response.Patch != nil {
|
||||
patchObj, err := jsonpatch.DecodePatch([]byte(response.Patch))
|
||||
|
@ -19,17 +19,17 @@ package webhook
|
||||
import (
|
||||
"time"
|
||||
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/api/admission/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// alwaysAllowDelayFiveSeconds sleeps for five seconds and allows all requests made to this function.
|
||||
func alwaysAllowDelayFiveSeconds(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
func alwaysAllowDelayFiveSeconds(ar v1.AdmissionReview) *v1.AdmissionResponse {
|
||||
klog.V(2).Info("always-allow-with-delay sleeping for 5 seconds")
|
||||
time.Sleep(5 * time.Second)
|
||||
klog.V(2).Info("calling always-allow")
|
||||
reviewResponse := v1beta1.AdmissionResponse{}
|
||||
reviewResponse := v1.AdmissionResponse{}
|
||||
reviewResponse.Allowed = true
|
||||
reviewResponse.Result = &metav1.Status{Message: "this webhook allows all requests"}
|
||||
return &reviewResponse
|
||||
|
@ -17,15 +17,15 @@ limitations under the License.
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/api/admission/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// alwaysDeny all requests made to this function.
|
||||
func alwaysDeny(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
func alwaysDeny(ar v1.AdmissionReview) *v1.AdmissionResponse {
|
||||
klog.V(2).Info("calling always-deny")
|
||||
reviewResponse := v1beta1.AdmissionResponse{}
|
||||
reviewResponse := v1.AdmissionResponse{}
|
||||
reviewResponse.Allowed = false
|
||||
reviewResponse.Result = &metav1.Status{Message: "this webhook denies all requests"}
|
||||
return &reviewResponse
|
||||
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
v1 "k8s.io/api/admission/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/klog"
|
||||
@ -33,7 +33,7 @@ const (
|
||||
)
|
||||
|
||||
// deny configmaps with specific key-value pair.
|
||||
func admitConfigMaps(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
func admitConfigMaps(ar v1.AdmissionReview) *v1.AdmissionResponse {
|
||||
klog.V(2).Info("admitting configmaps")
|
||||
configMapResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}
|
||||
if ar.Request.Resource != configMapResource {
|
||||
@ -42,7 +42,7 @@ func admitConfigMaps(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
}
|
||||
|
||||
var raw []byte
|
||||
if ar.Request.Operation == v1beta1.Delete {
|
||||
if ar.Request.Operation == v1.Delete {
|
||||
raw = ar.Request.OldObject.Raw
|
||||
} else {
|
||||
raw = ar.Request.Object.Raw
|
||||
@ -51,19 +51,19 @@ func admitConfigMaps(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
deserializer := codecs.UniversalDeserializer()
|
||||
if _, _, err := deserializer.Decode(raw, nil, &configmap); err != nil {
|
||||
klog.Error(err)
|
||||
return toAdmissionResponse(err)
|
||||
return toV1AdmissionResponse(err)
|
||||
}
|
||||
reviewResponse := v1beta1.AdmissionResponse{}
|
||||
reviewResponse := v1.AdmissionResponse{}
|
||||
reviewResponse.Allowed = true
|
||||
for k, v := range configmap.Data {
|
||||
if k == "webhook-e2e-test" && v == "webhook-disallow" &&
|
||||
(ar.Request.Operation == v1beta1.Create || ar.Request.Operation == v1beta1.Update) {
|
||||
(ar.Request.Operation == v1.Create || ar.Request.Operation == v1.Update) {
|
||||
reviewResponse.Allowed = false
|
||||
reviewResponse.Result = &metav1.Status{
|
||||
Reason: "the configmap contains unwanted key and value",
|
||||
}
|
||||
}
|
||||
if k == "webhook-e2e-test" && v == "webhook-nondeletable" && ar.Request.Operation == v1beta1.Delete {
|
||||
if k == "webhook-e2e-test" && v == "webhook-nondeletable" && ar.Request.Operation == v1.Delete {
|
||||
reviewResponse.Allowed = false
|
||||
reviewResponse.Result = &metav1.Status{
|
||||
Reason: "the configmap cannot be deleted because it contains unwanted key and value",
|
||||
@ -73,7 +73,7 @@ func admitConfigMaps(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
return &reviewResponse
|
||||
}
|
||||
|
||||
func mutateConfigmaps(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
func mutateConfigmaps(ar v1.AdmissionReview) *v1.AdmissionResponse {
|
||||
klog.V(2).Info("mutating configmaps")
|
||||
configMapResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}
|
||||
if ar.Request.Resource != configMapResource {
|
||||
@ -86,9 +86,9 @@ func mutateConfigmaps(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
deserializer := codecs.UniversalDeserializer()
|
||||
if _, _, err := deserializer.Decode(raw, nil, &configmap); err != nil {
|
||||
klog.Error(err)
|
||||
return toAdmissionResponse(err)
|
||||
return toV1AdmissionResponse(err)
|
||||
}
|
||||
reviewResponse := v1beta1.AdmissionResponse{}
|
||||
reviewResponse := v1.AdmissionResponse{}
|
||||
reviewResponse.Allowed = true
|
||||
if configmap.Data["mutation-start"] == "yes" {
|
||||
reviewResponse.Patch = []byte(configMapPatch1)
|
||||
@ -97,7 +97,7 @@ func mutateConfigmaps(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
reviewResponse.Patch = []byte(configMapPatch2)
|
||||
}
|
||||
|
||||
pt := v1beta1.PatchTypeJSONPatch
|
||||
pt := v1.PatchTypeJSONPatch
|
||||
reviewResponse.PatchType = &pt
|
||||
|
||||
return &reviewResponse
|
||||
|
103
test/images/agnhost/webhook/convert.go
Normal file
103
test/images/agnhost/webhook/convert.go
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
Copyright 2019 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 webhook
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func convertAdmissionRequestToV1(r *v1beta1.AdmissionRequest) *v1.AdmissionRequest {
|
||||
return &v1.AdmissionRequest{
|
||||
Kind: r.Kind,
|
||||
Namespace: r.Namespace,
|
||||
Name: r.Name,
|
||||
Object: r.Object,
|
||||
Resource: r.Resource,
|
||||
Operation: v1.Operation(r.Operation),
|
||||
UID: r.UID,
|
||||
DryRun: r.DryRun,
|
||||
OldObject: r.OldObject,
|
||||
Options: r.Options,
|
||||
RequestKind: r.RequestKind,
|
||||
RequestResource: r.RequestResource,
|
||||
RequestSubResource: r.RequestSubResource,
|
||||
SubResource: r.SubResource,
|
||||
UserInfo: r.UserInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func convertAdmissionRequestToV1beta1(r *v1.AdmissionRequest) *v1beta1.AdmissionRequest {
|
||||
return &v1beta1.AdmissionRequest{
|
||||
Kind: r.Kind,
|
||||
Namespace: r.Namespace,
|
||||
Name: r.Name,
|
||||
Object: r.Object,
|
||||
Resource: r.Resource,
|
||||
Operation: v1beta1.Operation(r.Operation),
|
||||
UID: r.UID,
|
||||
DryRun: r.DryRun,
|
||||
OldObject: r.OldObject,
|
||||
Options: r.Options,
|
||||
RequestKind: r.RequestKind,
|
||||
RequestResource: r.RequestResource,
|
||||
RequestSubResource: r.RequestSubResource,
|
||||
SubResource: r.SubResource,
|
||||
UserInfo: r.UserInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func convertAdmissionResponseToV1(r *v1beta1.AdmissionResponse) *v1.AdmissionResponse {
|
||||
var pt *v1.PatchType
|
||||
if r.PatchType != nil {
|
||||
t := v1.PatchType(*r.PatchType)
|
||||
pt = &t
|
||||
}
|
||||
return &v1.AdmissionResponse{
|
||||
UID: r.UID,
|
||||
Allowed: r.Allowed,
|
||||
AuditAnnotations: r.AuditAnnotations,
|
||||
Patch: r.Patch,
|
||||
PatchType: pt,
|
||||
Result: r.Result,
|
||||
}
|
||||
}
|
||||
|
||||
func convertAdmissionResponseToV1beta1(r *v1.AdmissionResponse) *v1beta1.AdmissionResponse {
|
||||
var pt *v1beta1.PatchType
|
||||
if r.PatchType != nil {
|
||||
t := v1beta1.PatchType(*r.PatchType)
|
||||
pt = &t
|
||||
}
|
||||
return &v1beta1.AdmissionResponse{
|
||||
UID: r.UID,
|
||||
Allowed: r.Allowed,
|
||||
AuditAnnotations: r.AuditAnnotations,
|
||||
Patch: r.Patch,
|
||||
PatchType: pt,
|
||||
Result: r.Result,
|
||||
}
|
||||
}
|
||||
|
||||
func toV1AdmissionResponse(err error) *v1.AdmissionResponse {
|
||||
return &v1.AdmissionResponse{
|
||||
Result: &metav1.Status{
|
||||
Message: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
64
test/images/agnhost/webhook/convert_test.go
Normal file
64
test/images/agnhost/webhook/convert_test.go
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
Copyright 2019 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 webhook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
fuzz "github.com/google/gofuzz"
|
||||
|
||||
v1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
admissionfuzzer "k8s.io/kubernetes/pkg/apis/admission/fuzzer"
|
||||
)
|
||||
|
||||
func TestConvertAdmissionRequestToV1(t *testing.T) {
|
||||
f := fuzzer.FuzzerFor(admissionfuzzer.Funcs, rand.NewSource(rand.Int63()), serializer.NewCodecFactory(runtime.NewScheme()))
|
||||
for i := 0; i < 100; i++ {
|
||||
t.Run(fmt.Sprintf("Run %d/100", i), func(t *testing.T) {
|
||||
orig := &v1beta1.AdmissionRequest{}
|
||||
f.Fuzz(orig)
|
||||
converted := convertAdmissionRequestToV1(orig)
|
||||
rt := convertAdmissionRequestToV1beta1(converted)
|
||||
if !reflect.DeepEqual(orig, rt) {
|
||||
t.Errorf("expected all request fields to be in converted object but found unaccounted for differences, diff:\n%s", diff.ObjectReflectDiff(orig, converted))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertAdmissionResponseToV1beta1(t *testing.T) {
|
||||
f := fuzz.New()
|
||||
for i := 0; i < 100; i++ {
|
||||
t.Run(fmt.Sprintf("Run %d/100", i), func(t *testing.T) {
|
||||
orig := &v1.AdmissionResponse{}
|
||||
f.Fuzz(orig)
|
||||
converted := convertAdmissionResponseToV1beta1(orig)
|
||||
rt := convertAdmissionResponseToV1(converted)
|
||||
if !reflect.DeepEqual(orig, rt) {
|
||||
t.Errorf("expected all fields to be in converted object but found unaccounted for differences, diff:\n%s", diff.ObjectReflectDiff(orig, converted))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -19,22 +19,21 @@ package webhook
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/admission/v1"
|
||||
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// This function expects all CRDs submitted to it to be apiextensions.k8s.io/v1beta1
|
||||
// TODO: When apiextensions.k8s.io/v1 is added we will need to update this function.
|
||||
func admitCRD(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
func admitCRD(ar v1.AdmissionReview) *v1.AdmissionResponse {
|
||||
klog.V(2).Info("admitting crd")
|
||||
crdResource := metav1.GroupVersionResource{Group: "apiextensions.k8s.io", Version: "v1beta1", Resource: "customresourcedefinitions"}
|
||||
if ar.Request.Resource != crdResource {
|
||||
err := fmt.Errorf("expect resource to be %s", crdResource)
|
||||
klog.Error(err)
|
||||
return toAdmissionResponse(err)
|
||||
return toV1AdmissionResponse(err)
|
||||
}
|
||||
|
||||
raw := ar.Request.Object.Raw
|
||||
@ -42,9 +41,9 @@ func admitCRD(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
deserializer := codecs.UniversalDeserializer()
|
||||
if _, _, err := deserializer.Decode(raw, nil, &crd); err != nil {
|
||||
klog.Error(err)
|
||||
return toAdmissionResponse(err)
|
||||
return toV1AdmissionResponse(err)
|
||||
}
|
||||
reviewResponse := v1beta1.AdmissionResponse{}
|
||||
reviewResponse := v1.AdmissionResponse{}
|
||||
reviewResponse.Allowed = true
|
||||
|
||||
if v, ok := crd.Labels["webhook-e2e-test"]; ok {
|
||||
|
@ -19,9 +19,8 @@ package webhook
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
v1 "k8s.io/api/admission/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
@ -34,7 +33,7 @@ const (
|
||||
]`
|
||||
)
|
||||
|
||||
func mutateCustomResource(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
func mutateCustomResource(ar v1.AdmissionReview) *v1.AdmissionResponse {
|
||||
klog.V(2).Info("mutating custom resource")
|
||||
cr := struct {
|
||||
metav1.ObjectMeta
|
||||
@ -45,10 +44,10 @@ func mutateCustomResource(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
|
||||
err := json.Unmarshal(raw, &cr)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return toAdmissionResponse(err)
|
||||
return toV1AdmissionResponse(err)
|
||||
}
|
||||
|
||||
reviewResponse := v1beta1.AdmissionResponse{}
|
||||
reviewResponse := v1.AdmissionResponse{}
|
||||
reviewResponse.Allowed = true
|
||||
|
||||
if cr.Data["mutation-start"] == "yes" {
|
||||
@ -57,12 +56,12 @@ func mutateCustomResource(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
|
||||
if cr.Data["mutation-stage-1"] == "yes" {
|
||||
reviewResponse.Patch = []byte(customResourcePatch2)
|
||||
}
|
||||
pt := v1beta1.PatchTypeJSONPatch
|
||||
pt := v1.PatchTypeJSONPatch
|
||||
reviewResponse.PatchType = &pt
|
||||
return &reviewResponse
|
||||
}
|
||||
|
||||
func admitCustomResource(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
func admitCustomResource(ar v1.AdmissionReview) *v1.AdmissionResponse {
|
||||
klog.V(2).Info("admitting custom resource")
|
||||
cr := struct {
|
||||
metav1.ObjectMeta
|
||||
@ -70,7 +69,7 @@ func admitCustomResource(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
|
||||
}{}
|
||||
|
||||
var raw []byte
|
||||
if ar.Request.Operation == v1beta1.Delete {
|
||||
if ar.Request.Operation == v1.Delete {
|
||||
raw = ar.Request.OldObject.Raw
|
||||
} else {
|
||||
raw = ar.Request.Object.Raw
|
||||
@ -78,20 +77,20 @@ func admitCustomResource(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
|
||||
err := json.Unmarshal(raw, &cr)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return toAdmissionResponse(err)
|
||||
return toV1AdmissionResponse(err)
|
||||
}
|
||||
|
||||
reviewResponse := v1beta1.AdmissionResponse{}
|
||||
reviewResponse := v1.AdmissionResponse{}
|
||||
reviewResponse.Allowed = true
|
||||
for k, v := range cr.Data {
|
||||
if k == "webhook-e2e-test" && v == "webhook-disallow" &&
|
||||
(ar.Request.Operation == v1beta1.Create || ar.Request.Operation == v1beta1.Update) {
|
||||
(ar.Request.Operation == v1.Create || ar.Request.Operation == v1.Update) {
|
||||
reviewResponse.Allowed = false
|
||||
reviewResponse.Result = &metav1.Status{
|
||||
Reason: "the custom resource contains unwanted data",
|
||||
}
|
||||
}
|
||||
if k == "webhook-e2e-test" && v == "webhook-nondeletable" && ar.Request.Operation == v1beta1.Delete {
|
||||
if k == "webhook-e2e-test" && v == "webhook-nondeletable" && ar.Request.Operation == v1.Delete {
|
||||
reviewResponse.Allowed = false
|
||||
reviewResponse.Result = &metav1.Status{
|
||||
Reason: "the custom resource cannot be deleted because it contains unwanted key and value",
|
||||
|
@ -24,8 +24,9 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
v1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/klog"
|
||||
// TODO: try this library to see if it generates correct json patch
|
||||
// https://github.com/mattbaird/jsonpatch
|
||||
@ -34,6 +35,7 @@ import (
|
||||
var (
|
||||
certFile string
|
||||
keyFile string
|
||||
port int
|
||||
)
|
||||
|
||||
// CmdWebhook is used by agnhost Cobra.
|
||||
@ -52,24 +54,40 @@ func init() {
|
||||
"File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert).")
|
||||
CmdWebhook.Flags().StringVar(&keyFile, "tls-private-key-file", "",
|
||||
"File containing the default x509 private key matching --tls-cert-file.")
|
||||
CmdWebhook.Flags().IntVar(&port, "port", 443,
|
||||
"Secure port that the webhook listens on")
|
||||
}
|
||||
|
||||
// toAdmissionResponse is a helper function to create an AdmissionResponse
|
||||
// with an embedded error
|
||||
func toAdmissionResponse(err error) *v1beta1.AdmissionResponse {
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Result: &metav1.Status{
|
||||
Message: err.Error(),
|
||||
},
|
||||
// admitv1beta1Func handles a v1beta1 admission
|
||||
type admitv1beta1Func func(v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
|
||||
|
||||
// admitv1beta1Func handles a v1 admission
|
||||
type admitv1Func func(v1.AdmissionReview) *v1.AdmissionResponse
|
||||
|
||||
// admitHandler is a handler, for both validators and mutators, that supports multiple admission review versions
|
||||
type admitHandler struct {
|
||||
v1beta1 admitv1beta1Func
|
||||
v1 admitv1Func
|
||||
}
|
||||
|
||||
func newDelegateToV1AdmitHandler(f admitv1Func) admitHandler {
|
||||
return admitHandler{
|
||||
v1beta1: delegateV1beta1AdmitToV1(f),
|
||||
v1: f,
|
||||
}
|
||||
}
|
||||
|
||||
// admitFunc is the type we use for all of our validators and mutators
|
||||
type admitFunc func(v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
|
||||
func delegateV1beta1AdmitToV1(f admitv1Func) admitv1beta1Func {
|
||||
return func(review v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
in := v1.AdmissionReview{Request: convertAdmissionRequestToV1(review.Request)}
|
||||
out := f(in)
|
||||
return convertAdmissionResponseToV1beta1(out)
|
||||
}
|
||||
}
|
||||
|
||||
// serve handles the http portion of a request prior to handing to an admit
|
||||
// function
|
||||
func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) {
|
||||
func serve(w http.ResponseWriter, r *http.Request, admit admitHandler) {
|
||||
var body []byte
|
||||
if r.Body != nil {
|
||||
if data, err := ioutil.ReadAll(r.Body); err == nil {
|
||||
@ -86,77 +104,101 @@ func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) {
|
||||
|
||||
klog.V(2).Info(fmt.Sprintf("handling request: %s", body))
|
||||
|
||||
// The AdmissionReview that was sent to the webhook
|
||||
requestedAdmissionReview := v1beta1.AdmissionReview{}
|
||||
|
||||
// The AdmissionReview that will be returned
|
||||
responseAdmissionReview := v1beta1.AdmissionReview{}
|
||||
|
||||
deserializer := codecs.UniversalDeserializer()
|
||||
if _, _, err := deserializer.Decode(body, nil, &requestedAdmissionReview); err != nil {
|
||||
klog.Error(err)
|
||||
responseAdmissionReview.Response = toAdmissionResponse(err)
|
||||
} else {
|
||||
// pass to admitFunc
|
||||
responseAdmissionReview.Response = admit(requestedAdmissionReview)
|
||||
obj, gvk, err := deserializer.Decode(body, nil, nil)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Request could not be decoded: %v", err)
|
||||
klog.Error(msg)
|
||||
http.Error(w, msg, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Return the same UID
|
||||
responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID
|
||||
var responseObj runtime.Object
|
||||
switch *gvk {
|
||||
case v1beta1.SchemeGroupVersion.WithKind("AdmissionReview"):
|
||||
requestedAdmissionReview, ok := obj.(*v1beta1.AdmissionReview)
|
||||
if !ok {
|
||||
klog.Errorf("Expected v1beta1.AdmissionReview but got: %T", obj)
|
||||
return
|
||||
}
|
||||
responseAdmissionReview := &v1beta1.AdmissionReview{}
|
||||
responseAdmissionReview.SetGroupVersionKind(*gvk)
|
||||
responseAdmissionReview.Response = admit.v1beta1(*requestedAdmissionReview)
|
||||
responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID
|
||||
responseObj = responseAdmissionReview
|
||||
case v1.SchemeGroupVersion.WithKind("AdmissionReview"):
|
||||
requestedAdmissionReview, ok := obj.(*v1.AdmissionReview)
|
||||
if !ok {
|
||||
klog.Errorf("Expected v1.AdmissionReview but got: %T", obj)
|
||||
return
|
||||
}
|
||||
responseAdmissionReview := &v1.AdmissionReview{}
|
||||
responseAdmissionReview.SetGroupVersionKind(*gvk)
|
||||
responseAdmissionReview.Response = admit.v1(*requestedAdmissionReview)
|
||||
responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID
|
||||
responseObj = responseAdmissionReview
|
||||
default:
|
||||
msg := fmt.Sprintf("Unsupported group version kind: %v", gvk)
|
||||
klog.Error(msg)
|
||||
http.Error(w, msg, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
klog.V(2).Info(fmt.Sprintf("sending response: %v", responseAdmissionReview.Response))
|
||||
|
||||
respBytes, err := json.Marshal(responseAdmissionReview)
|
||||
klog.V(2).Info(fmt.Sprintf("sending response: %v", responseObj))
|
||||
respBytes, err := json.Marshal(responseObj)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if _, err := w.Write(respBytes); err != nil {
|
||||
klog.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func serveAlwaysAllowDelayFiveSeconds(w http.ResponseWriter, r *http.Request) {
|
||||
serve(w, r, alwaysAllowDelayFiveSeconds)
|
||||
serve(w, r, newDelegateToV1AdmitHandler(alwaysAllowDelayFiveSeconds))
|
||||
}
|
||||
|
||||
func serveAlwaysDeny(w http.ResponseWriter, r *http.Request) {
|
||||
serve(w, r, alwaysDeny)
|
||||
serve(w, r, newDelegateToV1AdmitHandler(alwaysDeny))
|
||||
}
|
||||
|
||||
func serveAddLabel(w http.ResponseWriter, r *http.Request) {
|
||||
serve(w, r, addLabel)
|
||||
serve(w, r, newDelegateToV1AdmitHandler(addLabel))
|
||||
}
|
||||
|
||||
func servePods(w http.ResponseWriter, r *http.Request) {
|
||||
serve(w, r, admitPods)
|
||||
serve(w, r, newDelegateToV1AdmitHandler(admitPods))
|
||||
}
|
||||
|
||||
func serveAttachingPods(w http.ResponseWriter, r *http.Request) {
|
||||
serve(w, r, denySpecificAttachment)
|
||||
serve(w, r, newDelegateToV1AdmitHandler(denySpecificAttachment))
|
||||
}
|
||||
|
||||
func serveMutatePods(w http.ResponseWriter, r *http.Request) {
|
||||
serve(w, r, mutatePods)
|
||||
serve(w, r, newDelegateToV1AdmitHandler(mutatePods))
|
||||
}
|
||||
|
||||
func serveConfigmaps(w http.ResponseWriter, r *http.Request) {
|
||||
serve(w, r, admitConfigMaps)
|
||||
serve(w, r, newDelegateToV1AdmitHandler(admitConfigMaps))
|
||||
}
|
||||
|
||||
func serveMutateConfigmaps(w http.ResponseWriter, r *http.Request) {
|
||||
serve(w, r, mutateConfigmaps)
|
||||
serve(w, r, newDelegateToV1AdmitHandler(mutateConfigmaps))
|
||||
}
|
||||
|
||||
func serveCustomResource(w http.ResponseWriter, r *http.Request) {
|
||||
serve(w, r, admitCustomResource)
|
||||
serve(w, r, newDelegateToV1AdmitHandler(admitCustomResource))
|
||||
}
|
||||
|
||||
func serveMutateCustomResource(w http.ResponseWriter, r *http.Request) {
|
||||
serve(w, r, mutateCustomResource)
|
||||
serve(w, r, newDelegateToV1AdmitHandler(mutateCustomResource))
|
||||
}
|
||||
|
||||
func serveCRD(w http.ResponseWriter, r *http.Request) {
|
||||
serve(w, r, admitCRD)
|
||||
serve(w, r, newDelegateToV1AdmitHandler(admitCRD))
|
||||
}
|
||||
|
||||
func main(cmd *cobra.Command, args []string) {
|
||||
@ -177,8 +219,11 @@ func main(cmd *cobra.Command, args []string) {
|
||||
http.HandleFunc("/mutating-custom-resource", serveMutateCustomResource)
|
||||
http.HandleFunc("/crd", serveCRD)
|
||||
server := &http.Server{
|
||||
Addr: ":443",
|
||||
Addr: fmt.Sprintf(":%d", port),
|
||||
TLSConfig: configTLS(config),
|
||||
}
|
||||
server.ListenAndServeTLS("", "")
|
||||
err := server.ListenAndServeTLS("", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -20,10 +20,9 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/api/admission/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
@ -34,13 +33,13 @@ const (
|
||||
)
|
||||
|
||||
// only allow pods to pull images from specific registry.
|
||||
func admitPods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
func admitPods(ar v1.AdmissionReview) *v1.AdmissionResponse {
|
||||
klog.V(2).Info("admitting pods")
|
||||
podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
|
||||
if ar.Request.Resource != podResource {
|
||||
err := fmt.Errorf("expect resource to be %s", podResource)
|
||||
klog.Error(err)
|
||||
return toAdmissionResponse(err)
|
||||
return toV1AdmissionResponse(err)
|
||||
}
|
||||
|
||||
raw := ar.Request.Object.Raw
|
||||
@ -48,9 +47,9 @@ func admitPods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
deserializer := codecs.UniversalDeserializer()
|
||||
if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil {
|
||||
klog.Error(err)
|
||||
return toAdmissionResponse(err)
|
||||
return toV1AdmissionResponse(err)
|
||||
}
|
||||
reviewResponse := v1beta1.AdmissionResponse{}
|
||||
reviewResponse := v1.AdmissionResponse{}
|
||||
reviewResponse.Allowed = true
|
||||
|
||||
var msg string
|
||||
@ -77,7 +76,7 @@ func admitPods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
return &reviewResponse
|
||||
}
|
||||
|
||||
func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
func mutatePods(ar v1.AdmissionReview) *v1.AdmissionResponse {
|
||||
klog.V(2).Info("mutating pods")
|
||||
podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
|
||||
if ar.Request.Resource != podResource {
|
||||
@ -90,13 +89,13 @@ func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
deserializer := codecs.UniversalDeserializer()
|
||||
if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil {
|
||||
klog.Error(err)
|
||||
return toAdmissionResponse(err)
|
||||
return toV1AdmissionResponse(err)
|
||||
}
|
||||
reviewResponse := v1beta1.AdmissionResponse{}
|
||||
reviewResponse := v1.AdmissionResponse{}
|
||||
reviewResponse.Allowed = true
|
||||
if pod.Name == "webhook-to-be-mutated" {
|
||||
reviewResponse.Patch = []byte(podsInitContainerPatch)
|
||||
pt := v1beta1.PatchTypeJSONPatch
|
||||
pt := v1.PatchTypeJSONPatch
|
||||
reviewResponse.PatchType = &pt
|
||||
}
|
||||
return &reviewResponse
|
||||
@ -104,21 +103,21 @@ func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
|
||||
// denySpecificAttachment denies `kubectl attach to-be-attached-pod -i -c=container1"
|
||||
// or equivalent client requests.
|
||||
func denySpecificAttachment(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
||||
func denySpecificAttachment(ar v1.AdmissionReview) *v1.AdmissionResponse {
|
||||
klog.V(2).Info("handling attaching pods")
|
||||
if ar.Request.Name != "to-be-attached-pod" {
|
||||
return &v1beta1.AdmissionResponse{Allowed: true}
|
||||
return &v1.AdmissionResponse{Allowed: true}
|
||||
}
|
||||
podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
|
||||
if e, a := podResource, ar.Request.Resource; e != a {
|
||||
err := fmt.Errorf("expect resource to be %s, got %s", e, a)
|
||||
klog.Error(err)
|
||||
return toAdmissionResponse(err)
|
||||
return toV1AdmissionResponse(err)
|
||||
}
|
||||
if e, a := "attach", ar.Request.SubResource; e != a {
|
||||
err := fmt.Errorf("expect subresource to be %s, got %s", e, a)
|
||||
klog.Error(err)
|
||||
return toAdmissionResponse(err)
|
||||
return toV1AdmissionResponse(err)
|
||||
}
|
||||
|
||||
raw := ar.Request.Object.Raw
|
||||
@ -126,13 +125,13 @@ func denySpecificAttachment(ar v1beta1.AdmissionReview) *v1beta1.AdmissionRespon
|
||||
deserializer := codecs.UniversalDeserializer()
|
||||
if _, _, err := deserializer.Decode(raw, nil, &podAttachOptions); err != nil {
|
||||
klog.Error(err)
|
||||
return toAdmissionResponse(err)
|
||||
return toV1AdmissionResponse(err)
|
||||
}
|
||||
klog.V(2).Info(fmt.Sprintf("podAttachOptions=%#v\n", podAttachOptions))
|
||||
if !podAttachOptions.Stdin || podAttachOptions.Container != "container1" {
|
||||
return &v1beta1.AdmissionResponse{Allowed: true}
|
||||
return &v1.AdmissionResponse{Allowed: true}
|
||||
}
|
||||
return &v1beta1.AdmissionResponse{
|
||||
return &v1.AdmissionResponse{
|
||||
Allowed: false,
|
||||
Result: &metav1.Status{
|
||||
Message: "attaching to pod 'to-be-attached-pod' is not allowed",
|
||||
|
@ -17,7 +17,9 @@ limitations under the License.
|
||||
package webhook
|
||||
|
||||
import (
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@ -36,4 +38,6 @@ func addToScheme(scheme *runtime.Scheme) {
|
||||
utilruntime.Must(corev1.AddToScheme(scheme))
|
||||
utilruntime.Must(admissionv1beta1.AddToScheme(scheme))
|
||||
utilruntime.Must(admissionregistrationv1beta1.AddToScheme(scheme))
|
||||
utilruntime.Must(admissionv1.AddToScheme(scheme))
|
||||
utilruntime.Must(admissionregistrationv1.AddToScheme(scheme))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user