mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 20:53:33 +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",
|
name = "go_default_library",
|
||||||
srcs = ["fuzzer.go"],
|
srcs = ["fuzzer.go"],
|
||||||
importpath = "k8s.io/kubernetes/pkg/apis/admission/fuzzer",
|
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(
|
filegroup(
|
||||||
|
@ -17,10 +17,23 @@ limitations under the License.
|
|||||||
package fuzzer
|
package fuzzer
|
||||||
|
|
||||||
import (
|
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"
|
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Funcs returns the fuzzer functions for the admission api group.
|
// Funcs returns the fuzzer functions for the admission api group.
|
||||||
var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
|
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
|
package crdconvwebhook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -27,6 +28,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
certFile string
|
certFile string
|
||||||
keyFile string
|
keyFile string
|
||||||
|
port int
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdCrdConversionWebhook is used by agnhost Cobra.
|
// CmdCrdConversionWebhook is used by agnhost Cobra.
|
||||||
@ -48,6 +50,8 @@ func init() {
|
|||||||
"after server cert.")
|
"after server cert.")
|
||||||
CmdCrdConversionWebhook.Flags().StringVar(&keyFile, "tls-private-key-file", "",
|
CmdCrdConversionWebhook.Flags().StringVar(&keyFile, "tls-private-key-file", "",
|
||||||
"File containing the default x509 private key matching --tls-cert-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.
|
// 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)
|
http.HandleFunc("/crdconvert", converter.ServeExampleConvert)
|
||||||
clientset := getClient()
|
clientset := getClient()
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: ":443",
|
Addr: fmt.Sprintf(":%d", port),
|
||||||
TLSConfig: configTLS(config, clientset),
|
TLSConfig: configTLS(config, clientset),
|
||||||
}
|
}
|
||||||
server.ListenAndServeTLS("", "")
|
err := server.ListenAndServeTLS("", "")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ go_library(
|
|||||||
"alwaysdeny.go",
|
"alwaysdeny.go",
|
||||||
"config.go",
|
"config.go",
|
||||||
"configmap.go",
|
"configmap.go",
|
||||||
|
"convert.go",
|
||||||
"crd.go",
|
"crd.go",
|
||||||
"customresource.go",
|
"customresource.go",
|
||||||
"main.go",
|
"main.go",
|
||||||
@ -17,7 +18,9 @@ go_library(
|
|||||||
importpath = "k8s.io/kubernetes/test/images/agnhost/webhook",
|
importpath = "k8s.io/kubernetes/test/images/agnhost/webhook",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
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/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/admissionregistration/v1beta1:go_default_library",
|
||||||
"//staging/src/k8s.io/api/core/v1: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",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
|
||||||
@ -48,15 +51,22 @@ go_test(
|
|||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = [
|
||||||
"addlabel_test.go",
|
"addlabel_test.go",
|
||||||
|
"convert_test.go",
|
||||||
"patch_test.go",
|
"patch_test.go",
|
||||||
],
|
],
|
||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
deps = [
|
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/admission/v1beta1:go_default_library",
|
||||||
"//staging/src/k8s.io/api/core/v1: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:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured: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: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/evanphx/json-patch:go_default_library",
|
||||||
|
"//vendor/github.com/google/gofuzz:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -19,7 +19,7 @@ package webhook
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"k8s.io/api/admission/v1beta1"
|
"k8s.io/api/admission/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
)
|
)
|
||||||
@ -37,7 +37,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Add a label {"added-label": "yes"} to the object
|
// 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")
|
klog.V(2).Info("calling add-label")
|
||||||
obj := struct {
|
obj := struct {
|
||||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||||
@ -46,13 +46,13 @@ func addLabel(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
|||||||
err := json.Unmarshal(raw, &obj)
|
err := json.Unmarshal(raw, &obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
return toAdmissionResponse(err)
|
return toV1AdmissionResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
reviewResponse := v1beta1.AdmissionResponse{}
|
reviewResponse := v1.AdmissionResponse{}
|
||||||
reviewResponse.Allowed = true
|
reviewResponse.Allowed = true
|
||||||
|
|
||||||
pt := v1beta1.PatchTypeJSONPatch
|
pt := v1.PatchTypeJSONPatch
|
||||||
labelValue, hasLabel := obj.ObjectMeta.Labels["added-label"]
|
labelValue, hasLabel := obj.ObjectMeta.Labels["added-label"]
|
||||||
switch {
|
switch {
|
||||||
case len(obj.ObjectMeta.Labels) == 0:
|
case len(obj.ObjectMeta.Labels) == 0:
|
||||||
|
@ -22,7 +22,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
jsonpatch "github.com/evanphx/json-patch"
|
||||||
"k8s.io/api/admission/v1beta1"
|
"k8s.io/api/admission/v1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
@ -58,7 +58,7 @@ func TestAddLabel(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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)
|
response := addLabel(review)
|
||||||
if response.Patch != nil {
|
if response.Patch != nil {
|
||||||
patchObj, err := jsonpatch.DecodePatch([]byte(response.Patch))
|
patchObj, err := jsonpatch.DecodePatch([]byte(response.Patch))
|
||||||
|
@ -19,17 +19,17 @@ package webhook
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/api/admission/v1beta1"
|
"k8s.io/api/admission/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// alwaysAllowDelayFiveSeconds sleeps for five seconds and allows all requests made to this function.
|
// 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")
|
klog.V(2).Info("always-allow-with-delay sleeping for 5 seconds")
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
klog.V(2).Info("calling always-allow")
|
klog.V(2).Info("calling always-allow")
|
||||||
reviewResponse := v1beta1.AdmissionResponse{}
|
reviewResponse := v1.AdmissionResponse{}
|
||||||
reviewResponse.Allowed = true
|
reviewResponse.Allowed = true
|
||||||
reviewResponse.Result = &metav1.Status{Message: "this webhook allows all requests"}
|
reviewResponse.Result = &metav1.Status{Message: "this webhook allows all requests"}
|
||||||
return &reviewResponse
|
return &reviewResponse
|
||||||
|
@ -17,15 +17,15 @@ limitations under the License.
|
|||||||
package webhook
|
package webhook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/api/admission/v1beta1"
|
"k8s.io/api/admission/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// alwaysDeny all requests made to this function.
|
// 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")
|
klog.V(2).Info("calling always-deny")
|
||||||
reviewResponse := v1beta1.AdmissionResponse{}
|
reviewResponse := v1.AdmissionResponse{}
|
||||||
reviewResponse.Allowed = false
|
reviewResponse.Allowed = false
|
||||||
reviewResponse.Result = &metav1.Status{Message: "this webhook denies all requests"}
|
reviewResponse.Result = &metav1.Status{Message: "this webhook denies all requests"}
|
||||||
return &reviewResponse
|
return &reviewResponse
|
||||||
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
package webhook
|
package webhook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/api/admission/v1beta1"
|
v1 "k8s.io/api/admission/v1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
@ -33,7 +33,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// deny configmaps with specific key-value pair.
|
// 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")
|
klog.V(2).Info("admitting configmaps")
|
||||||
configMapResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}
|
configMapResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}
|
||||||
if ar.Request.Resource != configMapResource {
|
if ar.Request.Resource != configMapResource {
|
||||||
@ -42,7 +42,7 @@ func admitConfigMaps(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var raw []byte
|
var raw []byte
|
||||||
if ar.Request.Operation == v1beta1.Delete {
|
if ar.Request.Operation == v1.Delete {
|
||||||
raw = ar.Request.OldObject.Raw
|
raw = ar.Request.OldObject.Raw
|
||||||
} else {
|
} else {
|
||||||
raw = ar.Request.Object.Raw
|
raw = ar.Request.Object.Raw
|
||||||
@ -51,19 +51,19 @@ func admitConfigMaps(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
|||||||
deserializer := codecs.UniversalDeserializer()
|
deserializer := codecs.UniversalDeserializer()
|
||||||
if _, _, err := deserializer.Decode(raw, nil, &configmap); err != nil {
|
if _, _, err := deserializer.Decode(raw, nil, &configmap); err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
return toAdmissionResponse(err)
|
return toV1AdmissionResponse(err)
|
||||||
}
|
}
|
||||||
reviewResponse := v1beta1.AdmissionResponse{}
|
reviewResponse := v1.AdmissionResponse{}
|
||||||
reviewResponse.Allowed = true
|
reviewResponse.Allowed = true
|
||||||
for k, v := range configmap.Data {
|
for k, v := range configmap.Data {
|
||||||
if k == "webhook-e2e-test" && v == "webhook-disallow" &&
|
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.Allowed = false
|
||||||
reviewResponse.Result = &metav1.Status{
|
reviewResponse.Result = &metav1.Status{
|
||||||
Reason: "the configmap contains unwanted key and value",
|
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.Allowed = false
|
||||||
reviewResponse.Result = &metav1.Status{
|
reviewResponse.Result = &metav1.Status{
|
||||||
Reason: "the configmap cannot be deleted because it contains unwanted key and value",
|
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
|
return &reviewResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
func mutateConfigmaps(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
func mutateConfigmaps(ar v1.AdmissionReview) *v1.AdmissionResponse {
|
||||||
klog.V(2).Info("mutating configmaps")
|
klog.V(2).Info("mutating configmaps")
|
||||||
configMapResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}
|
configMapResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}
|
||||||
if ar.Request.Resource != configMapResource {
|
if ar.Request.Resource != configMapResource {
|
||||||
@ -86,9 +86,9 @@ func mutateConfigmaps(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
|||||||
deserializer := codecs.UniversalDeserializer()
|
deserializer := codecs.UniversalDeserializer()
|
||||||
if _, _, err := deserializer.Decode(raw, nil, &configmap); err != nil {
|
if _, _, err := deserializer.Decode(raw, nil, &configmap); err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
return toAdmissionResponse(err)
|
return toV1AdmissionResponse(err)
|
||||||
}
|
}
|
||||||
reviewResponse := v1beta1.AdmissionResponse{}
|
reviewResponse := v1.AdmissionResponse{}
|
||||||
reviewResponse.Allowed = true
|
reviewResponse.Allowed = true
|
||||||
if configmap.Data["mutation-start"] == "yes" {
|
if configmap.Data["mutation-start"] == "yes" {
|
||||||
reviewResponse.Patch = []byte(configMapPatch1)
|
reviewResponse.Patch = []byte(configMapPatch1)
|
||||||
@ -97,7 +97,7 @@ func mutateConfigmaps(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
|||||||
reviewResponse.Patch = []byte(configMapPatch2)
|
reviewResponse.Patch = []byte(configMapPatch2)
|
||||||
}
|
}
|
||||||
|
|
||||||
pt := v1beta1.PatchTypeJSONPatch
|
pt := v1.PatchTypeJSONPatch
|
||||||
reviewResponse.PatchType = &pt
|
reviewResponse.PatchType = &pt
|
||||||
|
|
||||||
return &reviewResponse
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/api/admission/v1"
|
||||||
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
"k8s.io/api/admission/v1beta1"
|
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This function expects all CRDs submitted to it to be apiextensions.k8s.io/v1beta1
|
// 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.
|
// 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")
|
klog.V(2).Info("admitting crd")
|
||||||
crdResource := metav1.GroupVersionResource{Group: "apiextensions.k8s.io", Version: "v1beta1", Resource: "customresourcedefinitions"}
|
crdResource := metav1.GroupVersionResource{Group: "apiextensions.k8s.io", Version: "v1beta1", Resource: "customresourcedefinitions"}
|
||||||
if ar.Request.Resource != crdResource {
|
if ar.Request.Resource != crdResource {
|
||||||
err := fmt.Errorf("expect resource to be %s", crdResource)
|
err := fmt.Errorf("expect resource to be %s", crdResource)
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
return toAdmissionResponse(err)
|
return toV1AdmissionResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
raw := ar.Request.Object.Raw
|
raw := ar.Request.Object.Raw
|
||||||
@ -42,9 +41,9 @@ func admitCRD(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
|||||||
deserializer := codecs.UniversalDeserializer()
|
deserializer := codecs.UniversalDeserializer()
|
||||||
if _, _, err := deserializer.Decode(raw, nil, &crd); err != nil {
|
if _, _, err := deserializer.Decode(raw, nil, &crd); err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
return toAdmissionResponse(err)
|
return toV1AdmissionResponse(err)
|
||||||
}
|
}
|
||||||
reviewResponse := v1beta1.AdmissionResponse{}
|
reviewResponse := v1.AdmissionResponse{}
|
||||||
reviewResponse.Allowed = true
|
reviewResponse.Allowed = true
|
||||||
|
|
||||||
if v, ok := crd.Labels["webhook-e2e-test"]; ok {
|
if v, ok := crd.Labels["webhook-e2e-test"]; ok {
|
||||||
|
@ -19,9 +19,8 @@ package webhook
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/admission/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
"k8s.io/api/admission/v1beta1"
|
|
||||||
"k8s.io/klog"
|
"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")
|
klog.V(2).Info("mutating custom resource")
|
||||||
cr := struct {
|
cr := struct {
|
||||||
metav1.ObjectMeta
|
metav1.ObjectMeta
|
||||||
@ -45,10 +44,10 @@ func mutateCustomResource(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
|
|||||||
err := json.Unmarshal(raw, &cr)
|
err := json.Unmarshal(raw, &cr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
return toAdmissionResponse(err)
|
return toV1AdmissionResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
reviewResponse := v1beta1.AdmissionResponse{}
|
reviewResponse := v1.AdmissionResponse{}
|
||||||
reviewResponse.Allowed = true
|
reviewResponse.Allowed = true
|
||||||
|
|
||||||
if cr.Data["mutation-start"] == "yes" {
|
if cr.Data["mutation-start"] == "yes" {
|
||||||
@ -57,12 +56,12 @@ func mutateCustomResource(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
|
|||||||
if cr.Data["mutation-stage-1"] == "yes" {
|
if cr.Data["mutation-stage-1"] == "yes" {
|
||||||
reviewResponse.Patch = []byte(customResourcePatch2)
|
reviewResponse.Patch = []byte(customResourcePatch2)
|
||||||
}
|
}
|
||||||
pt := v1beta1.PatchTypeJSONPatch
|
pt := v1.PatchTypeJSONPatch
|
||||||
reviewResponse.PatchType = &pt
|
reviewResponse.PatchType = &pt
|
||||||
return &reviewResponse
|
return &reviewResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
func admitCustomResource(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
func admitCustomResource(ar v1.AdmissionReview) *v1.AdmissionResponse {
|
||||||
klog.V(2).Info("admitting custom resource")
|
klog.V(2).Info("admitting custom resource")
|
||||||
cr := struct {
|
cr := struct {
|
||||||
metav1.ObjectMeta
|
metav1.ObjectMeta
|
||||||
@ -70,7 +69,7 @@ func admitCustomResource(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
|
|||||||
}{}
|
}{}
|
||||||
|
|
||||||
var raw []byte
|
var raw []byte
|
||||||
if ar.Request.Operation == v1beta1.Delete {
|
if ar.Request.Operation == v1.Delete {
|
||||||
raw = ar.Request.OldObject.Raw
|
raw = ar.Request.OldObject.Raw
|
||||||
} else {
|
} else {
|
||||||
raw = ar.Request.Object.Raw
|
raw = ar.Request.Object.Raw
|
||||||
@ -78,20 +77,20 @@ func admitCustomResource(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
|
|||||||
err := json.Unmarshal(raw, &cr)
|
err := json.Unmarshal(raw, &cr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
return toAdmissionResponse(err)
|
return toV1AdmissionResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
reviewResponse := v1beta1.AdmissionResponse{}
|
reviewResponse := v1.AdmissionResponse{}
|
||||||
reviewResponse.Allowed = true
|
reviewResponse.Allowed = true
|
||||||
for k, v := range cr.Data {
|
for k, v := range cr.Data {
|
||||||
if k == "webhook-e2e-test" && v == "webhook-disallow" &&
|
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.Allowed = false
|
||||||
reviewResponse.Result = &metav1.Status{
|
reviewResponse.Result = &metav1.Status{
|
||||||
Reason: "the custom resource contains unwanted data",
|
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.Allowed = false
|
||||||
reviewResponse.Result = &metav1.Status{
|
reviewResponse.Result = &metav1.Status{
|
||||||
Reason: "the custom resource cannot be deleted because it contains unwanted key and value",
|
Reason: "the custom resource cannot be deleted because it contains unwanted key and value",
|
||||||
|
@ -24,8 +24,9 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/admission/v1"
|
||||||
"k8s.io/api/admission/v1beta1"
|
"k8s.io/api/admission/v1beta1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
// TODO: try this library to see if it generates correct json patch
|
// TODO: try this library to see if it generates correct json patch
|
||||||
// https://github.com/mattbaird/jsonpatch
|
// https://github.com/mattbaird/jsonpatch
|
||||||
@ -34,6 +35,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
certFile string
|
certFile string
|
||||||
keyFile string
|
keyFile string
|
||||||
|
port int
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdWebhook is used by agnhost Cobra.
|
// 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).")
|
"File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert).")
|
||||||
CmdWebhook.Flags().StringVar(&keyFile, "tls-private-key-file", "",
|
CmdWebhook.Flags().StringVar(&keyFile, "tls-private-key-file", "",
|
||||||
"File containing the default x509 private key matching --tls-cert-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
|
// admitv1beta1Func handles a v1beta1 admission
|
||||||
// with an embedded error
|
type admitv1beta1Func func(v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
|
||||||
func toAdmissionResponse(err error) *v1beta1.AdmissionResponse {
|
|
||||||
return &v1beta1.AdmissionResponse{
|
// admitv1beta1Func handles a v1 admission
|
||||||
Result: &metav1.Status{
|
type admitv1Func func(v1.AdmissionReview) *v1.AdmissionResponse
|
||||||
Message: err.Error(),
|
|
||||||
},
|
// 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
|
func delegateV1beta1AdmitToV1(f admitv1Func) admitv1beta1Func {
|
||||||
type admitFunc func(v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
|
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
|
// serve handles the http portion of a request prior to handing to an admit
|
||||||
// function
|
// function
|
||||||
func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) {
|
func serve(w http.ResponseWriter, r *http.Request, admit admitHandler) {
|
||||||
var body []byte
|
var body []byte
|
||||||
if r.Body != nil {
|
if r.Body != nil {
|
||||||
if data, err := ioutil.ReadAll(r.Body); err == 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))
|
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()
|
deserializer := codecs.UniversalDeserializer()
|
||||||
if _, _, err := deserializer.Decode(body, nil, &requestedAdmissionReview); err != nil {
|
obj, gvk, err := deserializer.Decode(body, nil, nil)
|
||||||
klog.Error(err)
|
if err != nil {
|
||||||
responseAdmissionReview.Response = toAdmissionResponse(err)
|
msg := fmt.Sprintf("Request could not be decoded: %v", err)
|
||||||
} else {
|
klog.Error(msg)
|
||||||
// pass to admitFunc
|
http.Error(w, msg, http.StatusBadRequest)
|
||||||
responseAdmissionReview.Response = admit(requestedAdmissionReview)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the same UID
|
var responseObj runtime.Object
|
||||||
responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID
|
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))
|
klog.V(2).Info(fmt.Sprintf("sending response: %v", responseObj))
|
||||||
|
respBytes, err := json.Marshal(responseObj)
|
||||||
respBytes, err := json.Marshal(responseAdmissionReview)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Error(err)
|
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 {
|
if _, err := w.Write(respBytes); err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveAlwaysAllowDelayFiveSeconds(w http.ResponseWriter, r *http.Request) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
func serveCRD(w http.ResponseWriter, r *http.Request) {
|
||||||
serve(w, r, admitCRD)
|
serve(w, r, newDelegateToV1AdmitHandler(admitCRD))
|
||||||
}
|
}
|
||||||
|
|
||||||
func main(cmd *cobra.Command, args []string) {
|
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("/mutating-custom-resource", serveMutateCustomResource)
|
||||||
http.HandleFunc("/crd", serveCRD)
|
http.HandleFunc("/crd", serveCRD)
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: ":443",
|
Addr: fmt.Sprintf(":%d", port),
|
||||||
TLSConfig: configTLS(config),
|
TLSConfig: configTLS(config),
|
||||||
}
|
}
|
||||||
server.ListenAndServeTLS("", "")
|
err := server.ListenAndServeTLS("", "")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/api/admission/v1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
"k8s.io/api/admission/v1beta1"
|
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,13 +33,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// only allow pods to pull images from specific registry.
|
// 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")
|
klog.V(2).Info("admitting pods")
|
||||||
podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
|
podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
|
||||||
if ar.Request.Resource != podResource {
|
if ar.Request.Resource != podResource {
|
||||||
err := fmt.Errorf("expect resource to be %s", podResource)
|
err := fmt.Errorf("expect resource to be %s", podResource)
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
return toAdmissionResponse(err)
|
return toV1AdmissionResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
raw := ar.Request.Object.Raw
|
raw := ar.Request.Object.Raw
|
||||||
@ -48,9 +47,9 @@ func admitPods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
|||||||
deserializer := codecs.UniversalDeserializer()
|
deserializer := codecs.UniversalDeserializer()
|
||||||
if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil {
|
if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
return toAdmissionResponse(err)
|
return toV1AdmissionResponse(err)
|
||||||
}
|
}
|
||||||
reviewResponse := v1beta1.AdmissionResponse{}
|
reviewResponse := v1.AdmissionResponse{}
|
||||||
reviewResponse.Allowed = true
|
reviewResponse.Allowed = true
|
||||||
|
|
||||||
var msg string
|
var msg string
|
||||||
@ -77,7 +76,7 @@ func admitPods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
|||||||
return &reviewResponse
|
return &reviewResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
func mutatePods(ar v1.AdmissionReview) *v1.AdmissionResponse {
|
||||||
klog.V(2).Info("mutating pods")
|
klog.V(2).Info("mutating pods")
|
||||||
podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
|
podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
|
||||||
if ar.Request.Resource != podResource {
|
if ar.Request.Resource != podResource {
|
||||||
@ -90,13 +89,13 @@ func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
|||||||
deserializer := codecs.UniversalDeserializer()
|
deserializer := codecs.UniversalDeserializer()
|
||||||
if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil {
|
if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
return toAdmissionResponse(err)
|
return toV1AdmissionResponse(err)
|
||||||
}
|
}
|
||||||
reviewResponse := v1beta1.AdmissionResponse{}
|
reviewResponse := v1.AdmissionResponse{}
|
||||||
reviewResponse.Allowed = true
|
reviewResponse.Allowed = true
|
||||||
if pod.Name == "webhook-to-be-mutated" {
|
if pod.Name == "webhook-to-be-mutated" {
|
||||||
reviewResponse.Patch = []byte(podsInitContainerPatch)
|
reviewResponse.Patch = []byte(podsInitContainerPatch)
|
||||||
pt := v1beta1.PatchTypeJSONPatch
|
pt := v1.PatchTypeJSONPatch
|
||||||
reviewResponse.PatchType = &pt
|
reviewResponse.PatchType = &pt
|
||||||
}
|
}
|
||||||
return &reviewResponse
|
return &reviewResponse
|
||||||
@ -104,21 +103,21 @@ func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
|
|||||||
|
|
||||||
// denySpecificAttachment denies `kubectl attach to-be-attached-pod -i -c=container1"
|
// denySpecificAttachment denies `kubectl attach to-be-attached-pod -i -c=container1"
|
||||||
// or equivalent client requests.
|
// 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")
|
klog.V(2).Info("handling attaching pods")
|
||||||
if ar.Request.Name != "to-be-attached-pod" {
|
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"}
|
podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
|
||||||
if e, a := podResource, ar.Request.Resource; e != a {
|
if e, a := podResource, ar.Request.Resource; e != a {
|
||||||
err := fmt.Errorf("expect resource to be %s, got %s", e, a)
|
err := fmt.Errorf("expect resource to be %s, got %s", e, a)
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
return toAdmissionResponse(err)
|
return toV1AdmissionResponse(err)
|
||||||
}
|
}
|
||||||
if e, a := "attach", ar.Request.SubResource; e != a {
|
if e, a := "attach", ar.Request.SubResource; e != a {
|
||||||
err := fmt.Errorf("expect subresource to be %s, got %s", e, a)
|
err := fmt.Errorf("expect subresource to be %s, got %s", e, a)
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
return toAdmissionResponse(err)
|
return toV1AdmissionResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
raw := ar.Request.Object.Raw
|
raw := ar.Request.Object.Raw
|
||||||
@ -126,13 +125,13 @@ func denySpecificAttachment(ar v1beta1.AdmissionReview) *v1beta1.AdmissionRespon
|
|||||||
deserializer := codecs.UniversalDeserializer()
|
deserializer := codecs.UniversalDeserializer()
|
||||||
if _, _, err := deserializer.Decode(raw, nil, &podAttachOptions); err != nil {
|
if _, _, err := deserializer.Decode(raw, nil, &podAttachOptions); err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
return toAdmissionResponse(err)
|
return toV1AdmissionResponse(err)
|
||||||
}
|
}
|
||||||
klog.V(2).Info(fmt.Sprintf("podAttachOptions=%#v\n", podAttachOptions))
|
klog.V(2).Info(fmt.Sprintf("podAttachOptions=%#v\n", podAttachOptions))
|
||||||
if !podAttachOptions.Stdin || podAttachOptions.Container != "container1" {
|
if !podAttachOptions.Stdin || podAttachOptions.Container != "container1" {
|
||||||
return &v1beta1.AdmissionResponse{Allowed: true}
|
return &v1.AdmissionResponse{Allowed: true}
|
||||||
}
|
}
|
||||||
return &v1beta1.AdmissionResponse{
|
return &v1.AdmissionResponse{
|
||||||
Allowed: false,
|
Allowed: false,
|
||||||
Result: &metav1.Status{
|
Result: &metav1.Status{
|
||||||
Message: "attaching to pod 'to-be-attached-pod' is not allowed",
|
Message: "attaching to pod 'to-be-attached-pod' is not allowed",
|
||||||
|
@ -17,7 +17,9 @@ limitations under the License.
|
|||||||
package webhook
|
package webhook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
admissionv1 "k8s.io/api/admission/v1"
|
||||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||||
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||||
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
@ -36,4 +38,6 @@ func addToScheme(scheme *runtime.Scheme) {
|
|||||||
utilruntime.Must(corev1.AddToScheme(scheme))
|
utilruntime.Must(corev1.AddToScheme(scheme))
|
||||||
utilruntime.Must(admissionv1beta1.AddToScheme(scheme))
|
utilruntime.Must(admissionv1beta1.AddToScheme(scheme))
|
||||||
utilruntime.Must(admissionregistrationv1beta1.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