2020-01-14 10:38:55 +00:00
|
|
|
/*
|
|
|
|
Copyright 2017 The Kubernetes Authors.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package request
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
admissionv1 "k8s.io/api/admission/v1"
|
|
|
|
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
|
|
|
authenticationv1 "k8s.io/api/authentication/v1"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
"k8s.io/apimachinery/pkg/types"
|
|
|
|
"k8s.io/apimachinery/pkg/util/uuid"
|
2023-06-01 16:58:10 +00:00
|
|
|
"k8s.io/apiserver/pkg/admission"
|
2020-01-14 10:38:55 +00:00
|
|
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
|
|
|
)
|
|
|
|
|
|
|
|
// AdmissionResponse contains the fields extracted from an AdmissionReview response
|
|
|
|
type AdmissionResponse struct {
|
|
|
|
AuditAnnotations map[string]string
|
|
|
|
Allowed bool
|
|
|
|
Patch []byte
|
|
|
|
PatchType admissionv1.PatchType
|
|
|
|
Result *metav1.Status
|
2020-12-17 12:28:29 +00:00
|
|
|
Warnings []string
|
2020-01-14 10:38:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// VerifyAdmissionResponse checks the validity of the provided admission review object, and returns the
|
|
|
|
// audit annotations, whether the response allowed the request, any provided patch/patchType/status,
|
|
|
|
// or an error if the provided admission review was not valid.
|
|
|
|
func VerifyAdmissionResponse(uid types.UID, mutating bool, review runtime.Object) (*AdmissionResponse, error) {
|
|
|
|
switch r := review.(type) {
|
|
|
|
case *admissionv1.AdmissionReview:
|
|
|
|
if r.Response == nil {
|
|
|
|
return nil, fmt.Errorf("webhook response was absent")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify UID matches
|
|
|
|
if r.Response.UID != uid {
|
|
|
|
return nil, fmt.Errorf("expected response.uid=%q, got %q", uid, r.Response.UID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify GVK
|
|
|
|
v1GVK := admissionv1.SchemeGroupVersion.WithKind("AdmissionReview")
|
|
|
|
if r.GroupVersionKind() != v1GVK {
|
|
|
|
return nil, fmt.Errorf("expected webhook response of %v, got %v", v1GVK.String(), r.GroupVersionKind().String())
|
|
|
|
}
|
|
|
|
|
|
|
|
patch := []byte(nil)
|
|
|
|
patchType := admissionv1.PatchType("")
|
|
|
|
|
|
|
|
if mutating {
|
|
|
|
// Ensure a mutating webhook provides both patch and patchType together
|
|
|
|
if len(r.Response.Patch) > 0 && r.Response.PatchType == nil {
|
|
|
|
return nil, fmt.Errorf("webhook returned response.patch but not response.patchType")
|
|
|
|
}
|
|
|
|
if len(r.Response.Patch) == 0 && r.Response.PatchType != nil {
|
|
|
|
return nil, fmt.Errorf("webhook returned response.patchType but not response.patch")
|
|
|
|
}
|
|
|
|
patch = r.Response.Patch
|
|
|
|
if r.Response.PatchType != nil {
|
|
|
|
patchType = *r.Response.PatchType
|
|
|
|
if len(patchType) == 0 {
|
|
|
|
return nil, fmt.Errorf("webhook returned invalid response.patchType of %q", patchType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Ensure a validating webhook doesn't return patch or patchType
|
|
|
|
if len(r.Response.Patch) > 0 {
|
|
|
|
return nil, fmt.Errorf("validating webhook may not return response.patch")
|
|
|
|
}
|
|
|
|
if r.Response.PatchType != nil {
|
|
|
|
return nil, fmt.Errorf("validating webhook may not return response.patchType")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &AdmissionResponse{
|
|
|
|
AuditAnnotations: r.Response.AuditAnnotations,
|
|
|
|
Allowed: r.Response.Allowed,
|
|
|
|
Patch: patch,
|
|
|
|
PatchType: patchType,
|
|
|
|
Result: r.Response.Result,
|
2020-12-17 12:28:29 +00:00
|
|
|
Warnings: r.Response.Warnings,
|
2020-01-14 10:38:55 +00:00
|
|
|
}, nil
|
|
|
|
|
|
|
|
case *admissionv1beta1.AdmissionReview:
|
|
|
|
if r.Response == nil {
|
|
|
|
return nil, fmt.Errorf("webhook response was absent")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Response GVK and response.uid were not verified in v1beta1 handling, allow any
|
|
|
|
|
|
|
|
patch := []byte(nil)
|
|
|
|
patchType := admissionv1.PatchType("")
|
|
|
|
if mutating {
|
|
|
|
patch = r.Response.Patch
|
|
|
|
if len(r.Response.Patch) > 0 {
|
|
|
|
// patch type was not verified in v1beta1 admissionreview handling. pin to only supported version if a patch is provided.
|
|
|
|
patchType = admissionv1.PatchTypeJSONPatch
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &AdmissionResponse{
|
|
|
|
AuditAnnotations: r.Response.AuditAnnotations,
|
|
|
|
Allowed: r.Response.Allowed,
|
|
|
|
Patch: patch,
|
|
|
|
PatchType: patchType,
|
|
|
|
Result: r.Response.Result,
|
2020-12-17 12:28:29 +00:00
|
|
|
Warnings: r.Response.Warnings,
|
2020-01-14 10:38:55 +00:00
|
|
|
}, nil
|
|
|
|
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unexpected response type %T", review)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateAdmissionObjects returns the unique request uid, the AdmissionReview object to send the webhook and to decode the response into,
|
|
|
|
// or an error if the webhook does not support receiving any of the admission review versions we know to send
|
2023-06-01 16:58:10 +00:00
|
|
|
func CreateAdmissionObjects(versionedAttributes *admission.VersionedAttributes, invocation *generic.WebhookInvocation) (uid types.UID, request, response runtime.Object, err error) {
|
2020-01-14 10:38:55 +00:00
|
|
|
for _, version := range invocation.Webhook.GetAdmissionReviewVersions() {
|
|
|
|
switch version {
|
|
|
|
case admissionv1.SchemeGroupVersion.Version:
|
|
|
|
uid := types.UID(uuid.NewUUID())
|
|
|
|
request := CreateV1AdmissionReview(uid, versionedAttributes, invocation)
|
|
|
|
response := &admissionv1.AdmissionReview{}
|
|
|
|
return uid, request, response, nil
|
|
|
|
|
|
|
|
case admissionv1beta1.SchemeGroupVersion.Version:
|
|
|
|
uid := types.UID(uuid.NewUUID())
|
|
|
|
request := CreateV1beta1AdmissionReview(uid, versionedAttributes, invocation)
|
|
|
|
response := &admissionv1beta1.AdmissionReview{}
|
|
|
|
return uid, request, response, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", nil, nil, fmt.Errorf("webhook does not accept known AdmissionReview versions (v1, v1beta1)")
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateV1AdmissionReview creates an AdmissionReview for the provided admission.Attributes
|
2023-06-01 16:58:10 +00:00
|
|
|
func CreateV1AdmissionReview(uid types.UID, versionedAttributes *admission.VersionedAttributes, invocation *generic.WebhookInvocation) *admissionv1.AdmissionReview {
|
2020-01-14 10:38:55 +00:00
|
|
|
attr := versionedAttributes.Attributes
|
|
|
|
gvk := invocation.Kind
|
|
|
|
gvr := invocation.Resource
|
|
|
|
subresource := invocation.Subresource
|
|
|
|
requestGVK := attr.GetKind()
|
|
|
|
requestGVR := attr.GetResource()
|
|
|
|
requestSubResource := attr.GetSubresource()
|
|
|
|
aUserInfo := attr.GetUserInfo()
|
|
|
|
userInfo := authenticationv1.UserInfo{
|
|
|
|
Extra: make(map[string]authenticationv1.ExtraValue),
|
|
|
|
Groups: aUserInfo.GetGroups(),
|
|
|
|
UID: aUserInfo.GetUID(),
|
|
|
|
Username: aUserInfo.GetName(),
|
|
|
|
}
|
|
|
|
dryRun := attr.IsDryRun()
|
|
|
|
|
|
|
|
// Convert the extra information in the user object
|
|
|
|
for key, val := range aUserInfo.GetExtra() {
|
|
|
|
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &admissionv1.AdmissionReview{
|
|
|
|
Request: &admissionv1.AdmissionRequest{
|
|
|
|
UID: uid,
|
|
|
|
Kind: metav1.GroupVersionKind{
|
|
|
|
Group: gvk.Group,
|
|
|
|
Kind: gvk.Kind,
|
|
|
|
Version: gvk.Version,
|
|
|
|
},
|
|
|
|
Resource: metav1.GroupVersionResource{
|
|
|
|
Group: gvr.Group,
|
|
|
|
Resource: gvr.Resource,
|
|
|
|
Version: gvr.Version,
|
|
|
|
},
|
|
|
|
SubResource: subresource,
|
|
|
|
RequestKind: &metav1.GroupVersionKind{
|
|
|
|
Group: requestGVK.Group,
|
|
|
|
Kind: requestGVK.Kind,
|
|
|
|
Version: requestGVK.Version,
|
|
|
|
},
|
|
|
|
RequestResource: &metav1.GroupVersionResource{
|
|
|
|
Group: requestGVR.Group,
|
|
|
|
Resource: requestGVR.Resource,
|
|
|
|
Version: requestGVR.Version,
|
|
|
|
},
|
|
|
|
RequestSubResource: requestSubResource,
|
|
|
|
Name: attr.GetName(),
|
|
|
|
Namespace: attr.GetNamespace(),
|
|
|
|
Operation: admissionv1.Operation(attr.GetOperation()),
|
|
|
|
UserInfo: userInfo,
|
|
|
|
Object: runtime.RawExtension{
|
|
|
|
Object: versionedAttributes.VersionedObject,
|
|
|
|
},
|
|
|
|
OldObject: runtime.RawExtension{
|
|
|
|
Object: versionedAttributes.VersionedOldObject,
|
|
|
|
},
|
|
|
|
DryRun: &dryRun,
|
|
|
|
Options: runtime.RawExtension{
|
|
|
|
Object: attr.GetOperationOptions(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateV1beta1AdmissionReview creates an AdmissionReview for the provided admission.Attributes
|
2023-06-01 16:58:10 +00:00
|
|
|
func CreateV1beta1AdmissionReview(uid types.UID, versionedAttributes *admission.VersionedAttributes, invocation *generic.WebhookInvocation) *admissionv1beta1.AdmissionReview {
|
2020-01-14 10:38:55 +00:00
|
|
|
attr := versionedAttributes.Attributes
|
|
|
|
gvk := invocation.Kind
|
|
|
|
gvr := invocation.Resource
|
|
|
|
subresource := invocation.Subresource
|
|
|
|
requestGVK := attr.GetKind()
|
|
|
|
requestGVR := attr.GetResource()
|
|
|
|
requestSubResource := attr.GetSubresource()
|
|
|
|
aUserInfo := attr.GetUserInfo()
|
|
|
|
userInfo := authenticationv1.UserInfo{
|
|
|
|
Extra: make(map[string]authenticationv1.ExtraValue),
|
|
|
|
Groups: aUserInfo.GetGroups(),
|
|
|
|
UID: aUserInfo.GetUID(),
|
|
|
|
Username: aUserInfo.GetName(),
|
|
|
|
}
|
|
|
|
dryRun := attr.IsDryRun()
|
|
|
|
|
|
|
|
// Convert the extra information in the user object
|
|
|
|
for key, val := range aUserInfo.GetExtra() {
|
|
|
|
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &admissionv1beta1.AdmissionReview{
|
|
|
|
Request: &admissionv1beta1.AdmissionRequest{
|
|
|
|
UID: uid,
|
|
|
|
Kind: metav1.GroupVersionKind{
|
|
|
|
Group: gvk.Group,
|
|
|
|
Kind: gvk.Kind,
|
|
|
|
Version: gvk.Version,
|
|
|
|
},
|
|
|
|
Resource: metav1.GroupVersionResource{
|
|
|
|
Group: gvr.Group,
|
|
|
|
Resource: gvr.Resource,
|
|
|
|
Version: gvr.Version,
|
|
|
|
},
|
|
|
|
SubResource: subresource,
|
|
|
|
RequestKind: &metav1.GroupVersionKind{
|
|
|
|
Group: requestGVK.Group,
|
|
|
|
Kind: requestGVK.Kind,
|
|
|
|
Version: requestGVK.Version,
|
|
|
|
},
|
|
|
|
RequestResource: &metav1.GroupVersionResource{
|
|
|
|
Group: requestGVR.Group,
|
|
|
|
Resource: requestGVR.Resource,
|
|
|
|
Version: requestGVR.Version,
|
|
|
|
},
|
|
|
|
RequestSubResource: requestSubResource,
|
|
|
|
Name: attr.GetName(),
|
|
|
|
Namespace: attr.GetNamespace(),
|
|
|
|
Operation: admissionv1beta1.Operation(attr.GetOperation()),
|
|
|
|
UserInfo: userInfo,
|
|
|
|
Object: runtime.RawExtension{
|
|
|
|
Object: versionedAttributes.VersionedObject,
|
|
|
|
},
|
|
|
|
OldObject: runtime.RawExtension{
|
|
|
|
Object: versionedAttributes.VersionedOldObject,
|
|
|
|
},
|
|
|
|
DryRun: &dryRun,
|
|
|
|
Options: runtime.RawExtension{
|
|
|
|
Object: attr.GetOperationOptions(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|