Commit dac3c2e1 authored by cheftako's avatar cheftako Committed by Walter Fender

Admission request/response handling

AdmissionResponse allows mutating webhook to send apiserver a json patch to mutate the object. This reflects the imperative nature of AdmissionReview. It adds AdmissionRequest and AdmissionResponse in place of status/spec. The AdmissionResponse the allows the mutating webhook to send back a json path with the mutated version of the requested object. Fixed the integration test to clean up properly. Switched test image to 1.8v5 to reflect API changes. Make sure to cache test framework client for cleaup test code. Switched to pointer for patch type. Factored in @liggitt's feedback. Factored in @lavalamp's feedback.
parent 3ec7487c
...@@ -19,6 +19,7 @@ go_library( ...@@ -19,6 +19,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
], ],
) )
......
...@@ -19,38 +19,34 @@ package admission ...@@ -19,38 +19,34 @@ package admission
import ( import (
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"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/apis/authentication" "k8s.io/kubernetes/pkg/apis/authentication"
) )
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// AdmissionReview describes an admission request. // AdmissionReview describes an admission review request/response.
type AdmissionReview struct { type AdmissionReview struct {
metav1.TypeMeta metav1.TypeMeta
// Spec describes the attributes for the admission request. // Request describes the attributes for the admission request.
// Since this admission controller is non-mutating the webhook should avoid setting this in its response to avoid the // +optional
// cost of deserializing it. Request *AdmissionRequest
Spec AdmissionReviewSpec
// Status is filled in by the webhook and indicates whether the admission request should be permitted. // Response describes the attributes for the admission response.
Status AdmissionReviewStatus // +optional
Response *AdmissionResponse
} }
// AdmissionReviewSpec describes the admission.Attributes for the admission request. // AdmissionRequest describes the admission.Attributes for the admission request.
type AdmissionReviewSpec struct { type AdmissionRequest struct {
// UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are
// otherwise identical (parallel requests, requests when earlier requests did not modify etc)
// The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request.
// It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.
UID types.UID
// Kind is the type of object being manipulated. For example: Pod // Kind is the type of object being manipulated. For example: Pod
Kind metav1.GroupVersionKind Kind metav1.GroupVersionKind
// Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and
// rely on the server to generate the name. If that is the case, this method will return the empty string.
Name string
// Namespace is the namespace associated with the request (if any).
Namespace string
// Object is the object from the incoming request prior to default values being applied
Object runtime.Object
// OldObject is the existing object. Only populated for UPDATE requests.
OldObject runtime.Object
// Operation is the operation being performed
Operation Operation
// Resource is the name of the resource being requested. This is not the kind. For example: pods // Resource is the name of the resource being requested. This is not the kind. For example: pods
Resource metav1.GroupVersionResource Resource metav1.GroupVersionResource
// SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent // SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent
...@@ -58,21 +54,54 @@ type AdmissionReviewSpec struct { ...@@ -58,21 +54,54 @@ type AdmissionReviewSpec struct {
// /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod" (because status operates on // /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod" (because status operates on
// pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource // pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource
// "binding", and kind "Binding". // "binding", and kind "Binding".
// +optional
SubResource string SubResource string
// Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and
// rely on the server to generate the name. If that is the case, this method will return the empty string.
// +optional
Name string
// Namespace is the namespace associated with the request (if any).
// +optional
Namespace string
// Operation is the operation being performed
Operation Operation
// UserInfo is information about the requesting user // UserInfo is information about the requesting user
UserInfo authentication.UserInfo UserInfo authentication.UserInfo
// Object is the object from the incoming request prior to default values being applied
// +optional
Object runtime.Object
// OldObject is the existing object. Only populated for UPDATE requests.
// +optional
OldObject runtime.Object
} }
// AdmissionReviewStatus describes the status of the admission request. // AdmissionResponse describes an admission response.
type AdmissionReviewStatus struct { type AdmissionResponse struct {
// UID is an identifier for the individual request/response.
// This should be copied over from the corresponding AdmissionRequest.
UID types.UID
// Allowed indicates whether or not the admission request was permitted. // Allowed indicates whether or not the admission request was permitted.
Allowed bool Allowed bool
// Result contains extra details into why an admission request was denied. // Result contains extra details into why an admission request was denied.
// This field IS NOT consulted in any way if "Allowed" is "true". // This field IS NOT consulted in any way if "Allowed" is "true".
// +optional // +optional
Result *metav1.Status Result *metav1.Status
// Patch contains the actual patch. Currently we only support a response in the form of JSONPatch, RFC 6902.
// +optional
Patch []byte
// PatchType indicates the form the Patch will take. Currently we only support "JSONPatch".
// +optional
PatchType *PatchType
} }
// PatchType is the type of patch being used to represent the mutated object
type PatchType string
// PatchType constants.
const (
PatchTypeJSONPatch PatchType = "JSONPatch"
)
// Operation is the type of resource operation being checked for admission control // Operation is the type of resource operation being checked for admission control
type Operation string type Operation string
......
...@@ -21,6 +21,7 @@ go_library( ...@@ -21,6 +21,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
], ],
) )
......
...@@ -26,37 +26,11 @@ import ( ...@@ -26,37 +26,11 @@ import (
) )
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdmissionReview) DeepCopyInto(out *AdmissionReview) { func (in *AdmissionRequest) DeepCopyInto(out *AdmissionRequest) {
*out = *in
out.TypeMeta = in.TypeMeta
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionReview.
func (in *AdmissionReview) DeepCopy() *AdmissionReview {
if in == nil {
return nil
}
out := new(AdmissionReview)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *AdmissionReview) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
} else {
return nil
}
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdmissionReviewSpec) DeepCopyInto(out *AdmissionReviewSpec) {
*out = *in *out = *in
out.Kind = in.Kind out.Kind = in.Kind
out.Resource = in.Resource
in.UserInfo.DeepCopyInto(&out.UserInfo)
if in.Object == nil { if in.Object == nil {
out.Object = nil out.Object = nil
} else { } else {
...@@ -67,23 +41,21 @@ func (in *AdmissionReviewSpec) DeepCopyInto(out *AdmissionReviewSpec) { ...@@ -67,23 +41,21 @@ func (in *AdmissionReviewSpec) DeepCopyInto(out *AdmissionReviewSpec) {
} else { } else {
out.OldObject = in.OldObject.DeepCopyObject() out.OldObject = in.OldObject.DeepCopyObject()
} }
out.Resource = in.Resource
in.UserInfo.DeepCopyInto(&out.UserInfo)
return return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionReviewSpec. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionRequest.
func (in *AdmissionReviewSpec) DeepCopy() *AdmissionReviewSpec { func (in *AdmissionRequest) DeepCopy() *AdmissionRequest {
if in == nil { if in == nil {
return nil return nil
} }
out := new(AdmissionReviewSpec) out := new(AdmissionRequest)
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdmissionReviewStatus) DeepCopyInto(out *AdmissionReviewStatus) { func (in *AdmissionResponse) DeepCopyInto(out *AdmissionResponse) {
*out = *in *out = *in
if in.Result != nil { if in.Result != nil {
in, out := &in.Result, &out.Result in, out := &in.Result, &out.Result
...@@ -94,15 +66,73 @@ func (in *AdmissionReviewStatus) DeepCopyInto(out *AdmissionReviewStatus) { ...@@ -94,15 +66,73 @@ func (in *AdmissionReviewStatus) DeepCopyInto(out *AdmissionReviewStatus) {
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
} }
if in.Patch != nil {
in, out := &in.Patch, &out.Patch
*out = make([]byte, len(*in))
copy(*out, *in)
}
if in.PatchType != nil {
in, out := &in.PatchType, &out.PatchType
if *in == nil {
*out = nil
} else {
*out = new(PatchType)
**out = **in
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionResponse.
func (in *AdmissionResponse) DeepCopy() *AdmissionResponse {
if in == nil {
return nil
}
out := new(AdmissionResponse)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdmissionReview) DeepCopyInto(out *AdmissionReview) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Request != nil {
in, out := &in.Request, &out.Request
if *in == nil {
*out = nil
} else {
*out = new(AdmissionRequest)
(*in).DeepCopyInto(*out)
}
}
if in.Response != nil {
in, out := &in.Response, &out.Response
if *in == nil {
*out = nil
} else {
*out = new(AdmissionResponse)
(*in).DeepCopyInto(*out)
}
}
return return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionReviewStatus. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionReview.
func (in *AdmissionReviewStatus) DeepCopy() *AdmissionReviewStatus { func (in *AdmissionReview) DeepCopy() *AdmissionReview {
if in == nil { if in == nil {
return nil return nil
} }
out := new(AdmissionReviewStatus) out := new(AdmissionReview)
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *AdmissionReview) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
} else {
return nil
}
}
...@@ -18,6 +18,7 @@ go_library( ...@@ -18,6 +18,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
], ],
) )
......
...@@ -30,33 +30,27 @@ import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; ...@@ -30,33 +30,27 @@ import "k8s.io/apimachinery/pkg/util/intstr/generated.proto";
// Package-wide variables from generator "generated". // Package-wide variables from generator "generated".
option go_package = "v1alpha1"; option go_package = "v1alpha1";
// AdmissionReview describes an admission request. // AdmissionRequest describes the admission.Attributes for the admission request.
message AdmissionReview { message AdmissionRequest {
// Spec describes the attributes for the admission request. // UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are
// Since this admission controller is non-mutating the webhook should avoid setting this in its response to avoid the // otherwise identical (parallel requests, requests when earlier requests did not modify etc)
// cost of deserializing it. // The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request.
// +optional // It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.
optional AdmissionReviewSpec spec = 1; optional string uid = 1;
// Status is filled in by the webhook and indicates whether the admission request should be permitted.
// +optional
optional AdmissionReviewStatus status = 2;
}
// AdmissionReviewSpec describes the admission.Attributes for the admission request.
message AdmissionReviewSpec {
// Kind is the type of object being manipulated. For example: Pod // Kind is the type of object being manipulated. For example: Pod
optional k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionKind kind = 1; optional k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionKind kind = 2;
// Object is the object from the incoming request prior to default values being applied // Resource is the name of the resource being requested. This is not the kind. For example: pods
optional k8s.io.apimachinery.pkg.runtime.RawExtension object = 2; optional k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionResource resource = 3;
// OldObject is the existing object. Only populated for UPDATE requests. // SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent
// resource, but it may have a different kind. For instance, /pods has the resource "pods" and the kind "Pod", while
// /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod" (because status operates on
// pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource
// "binding", and kind "Binding".
// +optional // +optional
optional k8s.io.apimachinery.pkg.runtime.RawExtension oldObject = 3; optional string subResource = 4;
// Operation is the operation being performed
optional string operation = 4;
// Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and // Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and
// rely on the server to generate the name. If that is the case, this method will return the empty string. // rely on the server to generate the name. If that is the case, this method will return the empty string.
...@@ -67,29 +61,52 @@ message AdmissionReviewSpec { ...@@ -67,29 +61,52 @@ message AdmissionReviewSpec {
// +optional // +optional
optional string namespace = 6; optional string namespace = 6;
// Resource is the name of the resource being requested. This is not the kind. For example: pods // Operation is the operation being performed
optional k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionResource resource = 7; optional string operation = 7;
// SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent // UserInfo is information about the requesting user
// resource, but it may have a different kind. For instance, /pods has the resource "pods" and the kind "Pod", while optional k8s.io.api.authentication.v1.UserInfo userInfo = 8;
// /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod" (because status operates on
// pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource // Object is the object from the incoming request prior to default values being applied
// "binding", and kind "Binding".
// +optional // +optional
optional string subResource = 8; optional k8s.io.apimachinery.pkg.runtime.RawExtension object = 9;
// UserInfo is information about the requesting user // OldObject is the existing object. Only populated for UPDATE requests.
optional k8s.io.api.authentication.v1.UserInfo userInfo = 9; // +optional
optional k8s.io.apimachinery.pkg.runtime.RawExtension oldObject = 10;
} }
// AdmissionReviewStatus describes the status of the admission request. // AdmissionResponse describes an admission response.
message AdmissionReviewStatus { message AdmissionResponse {
// UID is an identifier for the individual request/response.
// This should be copied over from the corresponding AdmissionRequest.
optional string uid = 1;
// Allowed indicates whether or not the admission request was permitted. // Allowed indicates whether or not the admission request was permitted.
optional bool allowed = 1; optional bool allowed = 2;
// Result contains extra details into why an admission request was denied. // Result contains extra details into why an admission request was denied.
// This field IS NOT consulted in any way if "Allowed" is "true". // This field IS NOT consulted in any way if "Allowed" is "true".
// +optional // +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.Status status = 2; optional k8s.io.apimachinery.pkg.apis.meta.v1.Status status = 3;
// The patch body. Currently we only support "JSONPatch" which implements RFC 6902.
// +optional
optional bytes patch = 4;
// The type of Patch. Currently we only allow "JSONPatch".
// +optional
optional string patchType = 5;
}
// AdmissionReview describes an admission review request/response.
message AdmissionReview {
// Request describes the attributes for the admission request.
// +optional
optional AdmissionRequest request = 1;
// Response describes the attributes for the admission response.
// +optional
optional AdmissionResponse response = 2;
} }
...@@ -20,34 +20,40 @@ import ( ...@@ -20,34 +20,40 @@ import (
authenticationv1 "k8s.io/api/authentication/v1" authenticationv1 "k8s.io/api/authentication/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"
"k8s.io/apimachinery/pkg/types"
) )
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// AdmissionReview describes an admission request. // AdmissionReview describes an admission review request/response.
type AdmissionReview struct { type AdmissionReview struct {
metav1.TypeMeta `json:",inline"` metav1.TypeMeta `json:",inline"`
// Spec describes the attributes for the admission request. // Request describes the attributes for the admission request.
// Since this admission controller is non-mutating the webhook should avoid setting this in its response to avoid the
// cost of deserializing it.
// +optional // +optional
Spec AdmissionReviewSpec `json:"spec,omitempty" protobuf:"bytes,1,opt,name=spec"` Request *AdmissionRequest `json:"request,omitempty" protobuf:"bytes,1,opt,name=request"`
// Status is filled in by the webhook and indicates whether the admission request should be permitted. // Response describes the attributes for the admission response.
// +optional // +optional
Status AdmissionReviewStatus `json:"status,omitempty" protobuf:"bytes,2,opt,name=status"` Response *AdmissionResponse `json:"response,omitempty" protobuf:"bytes,2,opt,name=response"`
} }
// AdmissionReviewSpec describes the admission.Attributes for the admission request. // AdmissionRequest describes the admission.Attributes for the admission request.
type AdmissionReviewSpec struct { type AdmissionRequest struct {
// UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are
// otherwise identical (parallel requests, requests when earlier requests did not modify etc)
// The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request.
// It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.
UID types.UID `json:"uid" protobuf:"bytes,1,opt,name=uid"`
// Kind is the type of object being manipulated. For example: Pod // Kind is the type of object being manipulated. For example: Pod
Kind metav1.GroupVersionKind `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"` Kind metav1.GroupVersionKind `json:"kind" protobuf:"bytes,2,opt,name=kind"`
// Object is the object from the incoming request prior to default values being applied // Resource is the name of the resource being requested. This is not the kind. For example: pods
Object runtime.RawExtension `json:"object,omitempty" protobuf:"bytes,2,opt,name=object"` Resource metav1.GroupVersionResource `json:"resource" protobuf:"bytes,3,opt,name=resource"`
// OldObject is the existing object. Only populated for UPDATE requests. // SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent
// resource, but it may have a different kind. For instance, /pods has the resource "pods" and the kind "Pod", while
// /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod" (because status operates on
// pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource
// "binding", and kind "Binding".
// +optional // +optional
OldObject runtime.RawExtension `json:"oldObject,omitempty" protobuf:"bytes,3,opt,name=oldObject"` SubResource string `json:"subResource,omitempty" protobuf:"bytes,4,opt,name=subResource"`
// Operation is the operation being performed
Operation Operation `json:"operation,omitempty" protobuf:"bytes,4,opt,name=operation"`
// Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and // Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and
// rely on the server to generate the name. If that is the case, this method will return the empty string. // rely on the server to generate the name. If that is the case, this method will return the empty string.
// +optional // +optional
...@@ -55,29 +61,49 @@ type AdmissionReviewSpec struct { ...@@ -55,29 +61,49 @@ type AdmissionReviewSpec struct {
// Namespace is the namespace associated with the request (if any). // Namespace is the namespace associated with the request (if any).
// +optional // +optional
Namespace string `json:"namespace,omitempty" protobuf:"bytes,6,opt,name=namespace"` Namespace string `json:"namespace,omitempty" protobuf:"bytes,6,opt,name=namespace"`
// Resource is the name of the resource being requested. This is not the kind. For example: pods // Operation is the operation being performed
Resource metav1.GroupVersionResource `json:"resource,omitempty" protobuf:"bytes,7,opt,name=resource"` Operation Operation `json:"operation" protobuf:"bytes,7,opt,name=operation"`
// SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent
// resource, but it may have a different kind. For instance, /pods has the resource "pods" and the kind "Pod", while
// /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod" (because status operates on
// pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource
// "binding", and kind "Binding".
// +optional
SubResource string `json:"subResource,omitempty" protobuf:"bytes,8,opt,name=subResource"`
// UserInfo is information about the requesting user // UserInfo is information about the requesting user
UserInfo authenticationv1.UserInfo `json:"userInfo,omitempty" protobuf:"bytes,9,opt,name=userInfo"` UserInfo authenticationv1.UserInfo `json:"userInfo" protobuf:"bytes,8,opt,name=userInfo"`
// Object is the object from the incoming request prior to default values being applied
// +optional
Object runtime.RawExtension `json:"object,omitempty" protobuf:"bytes,9,opt,name=object"`
// OldObject is the existing object. Only populated for UPDATE requests.
// +optional
OldObject runtime.RawExtension `json:"oldObject,omitempty" protobuf:"bytes,10,opt,name=oldObject"`
} }
// AdmissionReviewStatus describes the status of the admission request. // AdmissionResponse describes an admission response.
type AdmissionReviewStatus struct { type AdmissionResponse struct {
// UID is an identifier for the individual request/response.
// This should be copied over from the corresponding AdmissionRequest.
UID types.UID `json:"uid" protobuf:"bytes,1,opt,name=uid"`
// Allowed indicates whether or not the admission request was permitted. // Allowed indicates whether or not the admission request was permitted.
Allowed bool `json:"allowed" protobuf:"varint,1,opt,name=allowed"` Allowed bool `json:"allowed" protobuf:"varint,2,opt,name=allowed"`
// Result contains extra details into why an admission request was denied. // Result contains extra details into why an admission request was denied.
// This field IS NOT consulted in any way if "Allowed" is "true". // This field IS NOT consulted in any way if "Allowed" is "true".
// +optional // +optional
Result *metav1.Status `json:"status,omitempty" protobuf:"bytes,2,opt,name=status"` Result *metav1.Status `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
// The patch body. Currently we only support "JSONPatch" which implements RFC 6902.
// +optional
Patch []byte `json:"patch,omitempty" protobuf:"bytes,4,opt,name=patch"`
// The type of Patch. Currently we only allow "JSONPatch".
// +optional
PatchType *PatchType `json:"patchType,omitempty" protobuf:"bytes,5,opt,name=patchType"`
} }
// PatchType is the type of patch being used to represent the mutated object
type PatchType string
// PatchType constants.
const (
PatchTypeJSONPatch PatchType = "JSONPatch"
)
// Operation is the type of resource operation being checked for admission control // Operation is the type of resource operation being checked for admission control
type Operation string type Operation string
......
...@@ -27,41 +27,45 @@ package v1alpha1 ...@@ -27,41 +27,45 @@ package v1alpha1
// Those methods can be generated by using hack/update-generated-swagger-docs.sh // Those methods can be generated by using hack/update-generated-swagger-docs.sh
// AUTO-GENERATED FUNCTIONS START HERE // AUTO-GENERATED FUNCTIONS START HERE
var map_AdmissionReview = map[string]string{ var map_AdmissionRequest = map[string]string{
"": "AdmissionReview describes an admission request.", "": "AdmissionRequest describes the admission.Attributes for the admission request.",
"spec": "Spec describes the attributes for the admission request. Since this admission controller is non-mutating the webhook should avoid setting this in its response to avoid the cost of deserializing it.", "uid": "UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are otherwise identical (parallel requests, requests when earlier requests did not modify etc) The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request. It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.",
"status": "Status is filled in by the webhook and indicates whether the admission request should be permitted.",
}
func (AdmissionReview) SwaggerDoc() map[string]string {
return map_AdmissionReview
}
var map_AdmissionReviewSpec = map[string]string{
"": "AdmissionReviewSpec describes the admission.Attributes for the admission request.",
"kind": "Kind is the type of object being manipulated. For example: Pod", "kind": "Kind is the type of object being manipulated. For example: Pod",
"object": "Object is the object from the incoming request prior to default values being applied",
"oldObject": "OldObject is the existing object. Only populated for UPDATE requests.",
"operation": "Operation is the operation being performed",
"name": "Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and rely on the server to generate the name. If that is the case, this method will return the empty string.",
"namespace": "Namespace is the namespace associated with the request (if any).",
"resource": "Resource is the name of the resource being requested. This is not the kind. For example: pods", "resource": "Resource is the name of the resource being requested. This is not the kind. For example: pods",
"subResource": "SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind. For instance, /pods has the resource \"pods\" and the kind \"Pod\", while /pods/foo/status has the resource \"pods\", the sub resource \"status\", and the kind \"Pod\" (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource \"pods\", subresource \"binding\", and kind \"Binding\".", "subResource": "SubResource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind. For instance, /pods has the resource \"pods\" and the kind \"Pod\", while /pods/foo/status has the resource \"pods\", the sub resource \"status\", and the kind \"Pod\" (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource \"pods\", subresource \"binding\", and kind \"Binding\".",
"name": "Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and rely on the server to generate the name. If that is the case, this method will return the empty string.",
"namespace": "Namespace is the namespace associated with the request (if any).",
"operation": "Operation is the operation being performed",
"userInfo": "UserInfo is information about the requesting user", "userInfo": "UserInfo is information about the requesting user",
"object": "Object is the object from the incoming request prior to default values being applied",
"oldObject": "OldObject is the existing object. Only populated for UPDATE requests.",
}
func (AdmissionRequest) SwaggerDoc() map[string]string {
return map_AdmissionRequest
}
var map_AdmissionResponse = map[string]string{
"": "AdmissionResponse describes an admission response.",
"uid": "UID is an identifier for the individual request/response. This should be copied over from the corresponding AdmissionRequest.",
"allowed": "Allowed indicates whether or not the admission request was permitted.",
"status": "Result contains extra details into why an admission request was denied. This field IS NOT consulted in any way if \"Allowed\" is \"true\".",
"patch": "The patch body. Currently we only support \"JSONPatch\" which implements RFC 6902.",
"patchType": "The type of Patch. Currently we only allow \"JSONPatch\".",
} }
func (AdmissionReviewSpec) SwaggerDoc() map[string]string { func (AdmissionResponse) SwaggerDoc() map[string]string {
return map_AdmissionReviewSpec return map_AdmissionResponse
} }
var map_AdmissionReviewStatus = map[string]string{ var map_AdmissionReview = map[string]string{
"": "AdmissionReviewStatus describes the status of the admission request.", "": "AdmissionReview describes an admission review request/response.",
"allowed": "Allowed indicates whether or not the admission request was permitted.", "request": "Request describes the attributes for the admission request.",
"status": "Result contains extra details into why an admission request was denied. This field IS NOT consulted in any way if \"Allowed\" is \"true\".", "response": "Response describes the attributes for the admission response.",
} }
func (AdmissionReviewStatus) SwaggerDoc() map[string]string { func (AdmissionReview) SwaggerDoc() map[string]string {
return map_AdmissionReviewStatus return map_AdmissionReview
} }
// AUTO-GENERATED FUNCTIONS END HERE // AUTO-GENERATED FUNCTIONS END HERE
...@@ -26,75 +26,105 @@ import ( ...@@ -26,75 +26,105 @@ import (
) )
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdmissionReview) DeepCopyInto(out *AdmissionReview) { func (in *AdmissionRequest) DeepCopyInto(out *AdmissionRequest) {
*out = *in *out = *in
out.TypeMeta = in.TypeMeta out.Kind = in.Kind
in.Spec.DeepCopyInto(&out.Spec) out.Resource = in.Resource
in.Status.DeepCopyInto(&out.Status) in.UserInfo.DeepCopyInto(&out.UserInfo)
in.Object.DeepCopyInto(&out.Object)
in.OldObject.DeepCopyInto(&out.OldObject)
return return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionReview. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionRequest.
func (in *AdmissionReview) DeepCopy() *AdmissionReview { func (in *AdmissionRequest) DeepCopy() *AdmissionRequest {
if in == nil { if in == nil {
return nil return nil
} }
out := new(AdmissionReview) out := new(AdmissionRequest)
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *AdmissionReview) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
} else {
return nil
}
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdmissionReviewSpec) DeepCopyInto(out *AdmissionReviewSpec) { func (in *AdmissionResponse) DeepCopyInto(out *AdmissionResponse) {
*out = *in *out = *in
out.Kind = in.Kind if in.Result != nil {
in.Object.DeepCopyInto(&out.Object) in, out := &in.Result, &out.Result
in.OldObject.DeepCopyInto(&out.OldObject) if *in == nil {
out.Resource = in.Resource *out = nil
in.UserInfo.DeepCopyInto(&out.UserInfo) } else {
*out = new(v1.Status)
(*in).DeepCopyInto(*out)
}
}
if in.Patch != nil {
in, out := &in.Patch, &out.Patch
*out = make([]byte, len(*in))
copy(*out, *in)
}
if in.PatchType != nil {
in, out := &in.PatchType, &out.PatchType
if *in == nil {
*out = nil
} else {
*out = new(PatchType)
**out = **in
}
}
return return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionReviewSpec. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionResponse.
func (in *AdmissionReviewSpec) DeepCopy() *AdmissionReviewSpec { func (in *AdmissionResponse) DeepCopy() *AdmissionResponse {
if in == nil { if in == nil {
return nil return nil
} }
out := new(AdmissionReviewSpec) out := new(AdmissionResponse)
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdmissionReviewStatus) DeepCopyInto(out *AdmissionReviewStatus) { func (in *AdmissionReview) DeepCopyInto(out *AdmissionReview) {
*out = *in *out = *in
if in.Result != nil { out.TypeMeta = in.TypeMeta
in, out := &in.Result, &out.Result if in.Request != nil {
in, out := &in.Request, &out.Request
if *in == nil { if *in == nil {
*out = nil *out = nil
} else { } else {
*out = new(v1.Status) *out = new(AdmissionRequest)
(*in).DeepCopyInto(*out)
}
}
if in.Response != nil {
in, out := &in.Response, &out.Response
if *in == nil {
*out = nil
} else {
*out = new(AdmissionResponse)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
} }
return return
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionReviewStatus. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionReview.
func (in *AdmissionReviewStatus) DeepCopy() *AdmissionReviewStatus { func (in *AdmissionReview) DeepCopy() *AdmissionReview {
if in == nil { if in == nil {
return nil return nil
} }
out := new(AdmissionReviewStatus) out := new(AdmissionReview)
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *AdmissionReview) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
} else {
return nil
}
}
...@@ -13,6 +13,7 @@ go_library( ...@@ -13,6 +13,7 @@ go_library(
"//vendor/k8s.io/api/authentication/v1:go_default_library", "//vendor/k8s.io/api/authentication/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
], ],
) )
......
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
authenticationv1 "k8s.io/api/authentication/v1" authenticationv1 "k8s.io/api/authentication/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"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
) )
...@@ -42,28 +43,29 @@ func CreateAdmissionReview(attr admission.Attributes) admissionv1alpha1.Admissio ...@@ -42,28 +43,29 @@ func CreateAdmissionReview(attr admission.Attributes) admissionv1alpha1.Admissio
} }
return admissionv1alpha1.AdmissionReview{ return admissionv1alpha1.AdmissionReview{
Spec: admissionv1alpha1.AdmissionReviewSpec{ Request: &admissionv1alpha1.AdmissionRequest{
Name: attr.GetName(), UID: uuid.NewUUID(),
Namespace: attr.GetNamespace(), Kind: metav1.GroupVersionKind{
Group: gvk.Group,
Kind: gvk.Kind,
Version: gvk.Version,
},
Resource: metav1.GroupVersionResource{ Resource: metav1.GroupVersionResource{
Group: gvr.Group, Group: gvr.Group,
Resource: gvr.Resource, Resource: gvr.Resource,
Version: gvr.Version, Version: gvr.Version,
}, },
SubResource: attr.GetSubresource(), SubResource: attr.GetSubresource(),
Name: attr.GetName(),
Namespace: attr.GetNamespace(),
Operation: admissionv1alpha1.Operation(attr.GetOperation()), Operation: admissionv1alpha1.Operation(attr.GetOperation()),
UserInfo: userInfo,
Object: runtime.RawExtension{ Object: runtime.RawExtension{
Object: attr.GetObject(), Object: attr.GetObject(),
}, },
OldObject: runtime.RawExtension{ OldObject: runtime.RawExtension{
Object: attr.GetOldObject(), Object: attr.GetOldObject(),
}, },
Kind: metav1.GroupVersionKind{
Group: gvk.Group,
Kind: gvk.Kind,
Version: gvk.Version,
},
UserInfo: userInfo,
}, },
} }
} }
...@@ -308,9 +308,11 @@ func (a *GenericAdmissionWebhook) callHook(ctx context.Context, h *v1alpha1.Webh ...@@ -308,9 +308,11 @@ func (a *GenericAdmissionWebhook) callHook(ctx context.Context, h *v1alpha1.Webh
return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: err} return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
} }
if response.Status.Allowed { if response.Response == nil {
return &webhookerrors.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")}
}
if response.Response.Allowed {
return nil return nil
} }
return webhookerrors.ToStatusErr(h.Name, response.Response.Result)
return webhookerrors.ToStatusErr(h.Name, response.Status.Result)
} }
...@@ -360,6 +360,28 @@ func TestAdmit(t *testing.T) { ...@@ -360,6 +360,28 @@ func TestAdmit(t *testing.T) {
}, },
errorContains: "without explanation", errorContains: "without explanation",
}, },
"absent response and fail open": {
hookSource: fakeHookSource{
hooks: []registrationv1alpha1.Webhook{{
Name: "nilResponse",
ClientConfig: ccfgURL("nilResponse"),
FailurePolicy: &policyIgnore,
Rules: matchEverythingRules,
}},
},
expectAllow: true,
},
"absent response and fail closed": {
hookSource: fakeHookSource{
hooks: []registrationv1alpha1.Webhook{{
Name: "nilResponse",
ClientConfig: ccfgURL("nilResponse"),
FailurePolicy: &policyFail,
Rules: matchEverythingRules,
}},
},
errorContains: "Webhook response was absent",
},
// No need to test everything with the url case, since only the // No need to test everything with the url case, since only the
// connection is different. // connection is different.
} }
...@@ -587,14 +609,14 @@ func webhookHandler(w http.ResponseWriter, r *http.Request) { ...@@ -587,14 +609,14 @@ func webhookHandler(w http.ResponseWriter, r *http.Request) {
case "/disallow": case "/disallow":
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(&v1alpha1.AdmissionReview{ json.NewEncoder(w).Encode(&v1alpha1.AdmissionReview{
Status: v1alpha1.AdmissionReviewStatus{ Response: &v1alpha1.AdmissionResponse{
Allowed: false, Allowed: false,
}, },
}) })
case "/disallowReason": case "/disallowReason":
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(&v1alpha1.AdmissionReview{ json.NewEncoder(w).Encode(&v1alpha1.AdmissionReview{
Status: v1alpha1.AdmissionReviewStatus{ Response: &v1alpha1.AdmissionResponse{
Allowed: false, Allowed: false,
Result: &metav1.Status{ Result: &metav1.Status{
Message: "you shall not pass", Message: "you shall not pass",
...@@ -604,10 +626,13 @@ func webhookHandler(w http.ResponseWriter, r *http.Request) { ...@@ -604,10 +626,13 @@ func webhookHandler(w http.ResponseWriter, r *http.Request) {
case "/allow": case "/allow":
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(&v1alpha1.AdmissionReview{ json.NewEncoder(w).Encode(&v1alpha1.AdmissionReview{
Status: v1alpha1.AdmissionReviewStatus{ Response: &v1alpha1.AdmissionResponse{
Allowed: true, Allowed: true,
}, },
}) })
case "/nilResposne":
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(&v1alpha1.AdmissionReview{})
default: default:
http.NotFound(w, r) http.NotFound(w, r)
} }
......
...@@ -71,19 +71,14 @@ var serverWebhookVersion = utilversion.MustParseSemantic("v1.8.0") ...@@ -71,19 +71,14 @@ var serverWebhookVersion = utilversion.MustParseSemantic("v1.8.0")
var _ = SIGDescribe("AdmissionWebhook", func() { var _ = SIGDescribe("AdmissionWebhook", func() {
var context *certContext var context *certContext
var ns string
var c clientset.Interface
f := framework.NewDefaultFramework("webhook") f := framework.NewDefaultFramework("webhook")
framework.AddCleanupAction(func() {
// Cleanup actions will be called even when the tests are skipped and leaves namespace unset. var client clientset.Interface
if len(ns) > 0 { var namespaceName string
cleanWebhookTest(c, ns)
}
})
BeforeEach(func() { BeforeEach(func() {
c = f.ClientSet client = f.ClientSet
ns = f.Namespace.Name namespaceName = f.Namespace.Name
// Make sure the relevant provider supports admission webhook // Make sure the relevant provider supports admission webhook
framework.SkipUnlessServerVersionGTE(serverWebhookVersion, f.ClientSet.Discovery()) framework.SkipUnlessServerVersionGTE(serverWebhookVersion, f.ClientSet.Discovery())
...@@ -95,14 +90,16 @@ var _ = SIGDescribe("AdmissionWebhook", func() { ...@@ -95,14 +90,16 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
} }
By("Setting up server cert") By("Setting up server cert")
namespaceName := f.Namespace.Name
context = setupServerCert(namespaceName, serviceName) context = setupServerCert(namespaceName, serviceName)
createAuthReaderRoleBinding(f, namespaceName) createAuthReaderRoleBinding(f, namespaceName)
// Note that in 1.9 we will have backwards incompatible change to // 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 // admission webhooks, so the image will be updated to 1.9 sometime in
// the development 1.9 cycle. // the development 1.9 cycle.
deployWebhookAndService(f, "gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v3", context) deployWebhookAndService(f, "gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v5", context)
})
AfterEach(func() {
cleanWebhookTest(client, namespaceName)
}) })
It("Should be able to deny pod and configmap creation", func() { It("Should be able to deny pod and configmap creation", func() {
...@@ -573,6 +570,7 @@ func updateConfigMap(c clientset.Interface, ns, name string, update updateConfig ...@@ -573,6 +570,7 @@ func updateConfigMap(c clientset.Interface, ns, name string, update updateConfig
func cleanWebhookTest(client clientset.Interface, namespaceName string) { func cleanWebhookTest(client clientset.Interface, namespaceName string) {
_ = client.AdmissionregistrationV1alpha1().ValidatingWebhookConfigurations().Delete(webhookConfigName, nil) _ = client.AdmissionregistrationV1alpha1().ValidatingWebhookConfigurations().Delete(webhookConfigName, nil)
_ = client.AdmissionregistrationV1alpha1().ValidatingWebhookConfigurations().Delete(crdWebhookConfigName, nil)
_ = client.CoreV1().Services(namespaceName).Delete(serviceName, nil) _ = client.CoreV1().Services(namespaceName).Delete(serviceName, nil)
_ = client.ExtensionsV1beta1().Deployments(namespaceName).Delete(deploymentName, nil) _ = client.ExtensionsV1beta1().Deployments(namespaceName).Delete(deploymentName, nil)
_ = client.CoreV1().Secrets(namespaceName).Delete(secretName, nil) _ = client.CoreV1().Secrets(namespaceName).Delete(secretName, nil)
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
build: build:
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o webhook . CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o webhook .
docker build --no-cache -t gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v3 . docker build --no-cache -t gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v5 .
rm -rf webhook rm -rf webhook
push: push:
gcloud docker -- push gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v3 gcloud docker -- push gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v5
...@@ -44,8 +44,8 @@ func (c *Config) addFlags() { ...@@ -44,8 +44,8 @@ func (c *Config) addFlags() {
"File containing the default x509 private key matching --tls-cert-file.") "File containing the default x509 private key matching --tls-cert-file.")
} }
func toAdmissionReviewStatus(err error) *v1alpha1.AdmissionReviewStatus { func toAdmissionResponse(err error) *v1alpha1.AdmissionResponse {
return &v1alpha1.AdmissionReviewStatus{ return &v1alpha1.AdmissionResponse{
Result: &metav1.Status{ Result: &metav1.Status{
Message: err.Error(), Message: err.Error(),
}, },
...@@ -53,101 +53,101 @@ func toAdmissionReviewStatus(err error) *v1alpha1.AdmissionReviewStatus { ...@@ -53,101 +53,101 @@ func toAdmissionReviewStatus(err error) *v1alpha1.AdmissionReviewStatus {
} }
// only allow pods to pull images from specific registry. // only allow pods to pull images from specific registry.
func admitPods(ar v1alpha1.AdmissionReview) *v1alpha1.AdmissionReviewStatus { func admitPods(ar v1alpha1.AdmissionReview) *v1alpha1.AdmissionResponse {
glog.V(2).Info("admitting pods") glog.V(2).Info("admitting pods")
podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
if ar.Spec.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)
glog.Error(err) glog.Error(err)
return toAdmissionReviewStatus(err) return toAdmissionResponse(err)
} }
raw := ar.Spec.Object.Raw raw := ar.Request.Object.Raw
pod := corev1.Pod{} pod := corev1.Pod{}
deserializer := codecs.UniversalDeserializer() deserializer := codecs.UniversalDeserializer()
if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil { if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil {
glog.Error(err) glog.Error(err)
return toAdmissionReviewStatus(err) return toAdmissionResponse(err)
} }
reviewStatus := v1alpha1.AdmissionReviewStatus{} reviewResponse := v1alpha1.AdmissionResponse{}
reviewStatus.Allowed = true reviewResponse.Allowed = true
var msg string var msg string
for k, v := range pod.Labels { for k, v := range pod.Labels {
if k == "webhook-e2e-test" && v == "webhook-disallow" { if k == "webhook-e2e-test" && v == "webhook-disallow" {
reviewStatus.Allowed = false reviewResponse.Allowed = false
msg = msg + "the pod contains unwanted label; " msg = msg + "the pod contains unwanted label; "
} }
} }
for _, container := range pod.Spec.Containers { for _, container := range pod.Spec.Containers {
if strings.Contains(container.Name, "webhook-disallow") { if strings.Contains(container.Name, "webhook-disallow") {
reviewStatus.Allowed = false reviewResponse.Allowed = false
msg = msg + "the pod contains unwanted container name; " msg = msg + "the pod contains unwanted container name; "
} }
} }
if !reviewStatus.Allowed { if !reviewResponse.Allowed {
reviewStatus.Result = &metav1.Status{Message: strings.TrimSpace(msg)} reviewResponse.Result = &metav1.Status{Message: strings.TrimSpace(msg)}
} }
return &reviewStatus return &reviewResponse
} }
// deny configmaps with specific key-value pair. // deny configmaps with specific key-value pair.
func admitConfigMaps(ar v1alpha1.AdmissionReview) *v1alpha1.AdmissionReviewStatus { func admitConfigMaps(ar v1alpha1.AdmissionReview) *v1alpha1.AdmissionResponse {
glog.V(2).Info("admitting configmaps") glog.V(2).Info("admitting configmaps")
configMapResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"} configMapResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}
if ar.Spec.Resource != configMapResource { if ar.Request.Resource != configMapResource {
glog.Errorf("expect resource to be %s", configMapResource) glog.Errorf("expect resource to be %s", configMapResource)
return nil return nil
} }
raw := ar.Spec.Object.Raw raw := ar.Request.Object.Raw
configmap := corev1.ConfigMap{} configmap := corev1.ConfigMap{}
deserializer := codecs.UniversalDeserializer() deserializer := codecs.UniversalDeserializer()
if _, _, err := deserializer.Decode(raw, nil, &configmap); err != nil { if _, _, err := deserializer.Decode(raw, nil, &configmap); err != nil {
glog.Error(err) glog.Error(err)
return toAdmissionReviewStatus(err) return toAdmissionResponse(err)
} }
reviewStatus := v1alpha1.AdmissionReviewStatus{} reviewResponse := v1alpha1.AdmissionResponse{}
reviewStatus.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" {
reviewStatus.Allowed = false reviewResponse.Allowed = false
reviewStatus.Result = &metav1.Status{ reviewResponse.Result = &metav1.Status{
Reason: "the configmap contains unwanted key and value", Reason: "the configmap contains unwanted key and value",
} }
} }
} }
return &reviewStatus return &reviewResponse
} }
func admitCRD(ar v1alpha1.AdmissionReview) *v1alpha1.AdmissionReviewStatus { func admitCRD(ar v1alpha1.AdmissionReview) *v1alpha1.AdmissionResponse {
glog.V(2).Info("admitting crd") glog.V(2).Info("admitting crd")
cr := struct { cr := struct {
metav1.ObjectMeta metav1.ObjectMeta
Data map[string]string Data map[string]string
}{} }{}
raw := ar.Spec.Object.Raw raw := ar.Request.Object.Raw
err := json.Unmarshal(raw, &cr) err := json.Unmarshal(raw, &cr)
if err != nil { if err != nil {
glog.Error(err) glog.Error(err)
return toAdmissionReviewStatus(err) return toAdmissionResponse(err)
} }
reviewStatus := v1alpha1.AdmissionReviewStatus{} reviewResponse := v1alpha1.AdmissionResponse{}
reviewStatus.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" {
reviewStatus.Allowed = false reviewResponse.Allowed = false
reviewStatus.Result = &metav1.Status{ reviewResponse.Result = &metav1.Status{
Reason: "the custom resource contains unwanted data", Reason: "the custom resource contains unwanted data",
} }
} }
} }
return &reviewStatus return &reviewResponse
} }
type admitFunc func(v1alpha1.AdmissionReview) *v1alpha1.AdmissionReviewStatus type admitFunc func(v1alpha1.AdmissionReview) *v1alpha1.AdmissionResponse
func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) { func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) {
var body []byte var body []byte
...@@ -164,21 +164,23 @@ func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) { ...@@ -164,21 +164,23 @@ func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) {
return return
} }
var reviewStatus *v1alpha1.AdmissionReviewStatus var reviewResponse *v1alpha1.AdmissionResponse
ar := v1alpha1.AdmissionReview{} ar := v1alpha1.AdmissionReview{}
deserializer := codecs.UniversalDeserializer() deserializer := codecs.UniversalDeserializer()
if _, _, err := deserializer.Decode(body, nil, &ar); err != nil { if _, _, err := deserializer.Decode(body, nil, &ar); err != nil {
glog.Error(err) glog.Error(err)
reviewStatus = toAdmissionReviewStatus(err) reviewResponse = toAdmissionResponse(err)
} else { } else {
reviewStatus = admit(ar) reviewResponse = admit(ar)
} }
if reviewStatus != nil { response := v1alpha1.AdmissionReview{}
ar.Status = *reviewStatus if reviewResponse != nil {
response.Response = reviewResponse
response.Response.UID = ar.Request.UID
} }
resp, err := json.Marshal(ar) resp, err := json.Marshal(response)
if err != nil { if err != nil {
glog.Error(err) glog.Error(err)
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment