diff --git a/keps/sig-api-machinery/00xx-admission-webhooks-to-ga.md b/keps/sig-api-machinery/00xx-admission-webhooks-to-ga.md index a9afcc3d1dc0..ffe332d6344d 100644 --- a/keps/sig-api-machinery/00xx-admission-webhooks-to-ga.md +++ b/keps/sig-api-machinery/00xx-admission-webhooks-to-ga.md @@ -35,6 +35,7 @@ see-also: * [Mutating Plugin ordering](#mutating-plugin-ordering) * [Passing {Operation}Option to Webhook](#passing-operationoption-to-webhook) * [AdmissionReview v1](#admissionreview-v1) + * [Convert to webhook-requested version](#convert-to-webhook-requested-version) * [V1 API](#v1-api) * [V1beta1 changes](#v1beta1-changes) * [Validations](#validations) @@ -68,6 +69,7 @@ Based on the user feedback, These are the planned changes to current feature to * re-run mutating plugins if any webhook changed object to fix the plugin ordering problem * pass OperationOption (such as CreateOption/DeleteOption) to the webhook * make `Webhook.SideEffects` field required in `v1` API (look at [dryRun KEP(https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/0015-dry-run.md#admission-controllers)] for more information on this item) +* convert incoming objects to the webhook-requested group/version ### Non-Goals @@ -303,6 +305,129 @@ type Webhook struct { } ``` +### Convert to webhook-requested version + +Webhooks currently register to intercept particular API group/version/resource combinations. + +Some resources can be accessed via different versions, or even different API +groups (for example, `apps/v1` and `extensions/v1beta1` Deployments). To +intercept a resource effectively, all accessible groups/versions/resources +must be registered for and understood by the webhook. + +When upgrading to a new version of the apiserver, existing resources can be +made available via new versions (or even new groups). Ensuring all webhooks +(and registered webhook configurations) have been updated to be able to +handle the new versions/groups in every upgrade is possible, but easy to +forget to do, or to do incorrectly. In the case of webhooks not authored +by the cluster-administrator, obtaining updated admission plugins that +understand the new versions could require significant effort and time. + +Since the apiserver can convert between all of the versions by which a resource +is made available, this situation can be improved by having the apiserver +convert resources to the group/versions a webhook registered for. + +A `conversionPolicy` field will be added to the webhook configuration object, +allowing a webhook to choose whether the apiserver should ignore these requests +or convert them to a kind corresponding to a resource the webhook registered for +and send them to the webhook. For safety, this field defaults to Convert. + +```golang +// Webhook describes an admission webhook and the resources and operations it applies to. +type Webhook struct { + ... + // ConversionPolicy describes whether the webhook wants to be called when a request is made + // to a resource that does not match "rules", but which modifies the same data + // as a resource that does match "rules". + // Allowed values are Ignore or Convert. + // "Ignore" means the request should be ignored. + // "Convert" means the request should be converted to a kind corresponding to a resource matched by "rules", and sent to the webhook. + // Defaults to Convert. + // +optional + ConversionPolicy *ConversionPolicyType `json:"conversionPolicy,omitempty"` +``` + +The apiserver will do the following: + +1. For each resource, compute the set of other resources that access or affect the same data, and the kind of the expected object. For example: + * `apps,v1,deployments` (`apiVersion: apps/v1, kind: Deployment`) is also available via: + * `apps,v1beta2,deployments` (`apiVersion: apps/v1beta2, kind: Deployment`) + * `apps,v1beta1,deployments` (`apiVersion: apps/v1beta1, kind: Deployment`) + * `extensions,v1beta1,deployments` (`apiVersion: extensions/v1beta1, kind: Deployment`) + * `apps,v1,deployments/scale` (`apiVersion: autoscaling/v1, kind: Scale`) is also available via: + * `apps,v1beta2,deployments/scale` (`apiVersion: apps/v1beta2, kind: Scale`) + * `apps,v1beta1,deployments/scale` (`apiVersion: apps/v1beta1, kind: Scale`) + * `extensions,v1beta1,deployments/scale` (`apiVersion: extensions/v1beta1, kind: Scale`) +2. When evaluating whether to dispatch an incoming request to a webhook with +`conversionPolicy: Convert`, check the request's resource *and* all equivalent +resources against the ones the webhook had registered for. If needed, convert +the incoming object to one the webhook indicated it understood. + +The `AdmissionRequest` sent to a webhook includes the fully-qualified +kind (group/version/kind) and resource (group/version/resource): + +```golang +type AdmissionRequest struct { + ... + // Kind is the type of object being manipulated. For example: Pod + Kind metav1.GroupVersionKind `json:"kind" protobuf:"bytes,2,opt,name=kind"` + // Resource is the name of the resource being requested. This is not the kind. For example: pods + Resource metav1.GroupVersionResource `json:"resource" protobuf:"bytes,3,opt,name=resource"` + // 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,4,opt,name=subResource"` +``` + +Prior to this conversion feature, the resource and kind of the request made to the +API server, and the resource and kind sent in the AdmissionRequest were identical. + +When a conversion occurs and the object we send to the webhook is a different kind +than was sent to the API server, or the resource the webhook registered for is different +than the request made to the API server, we have three options for communicating that to the webhook: +1. Do not expose that fact to the webhook: + * Set AdmissionRequest `kind` to the converted kind + * Set AdmissionRequest `resource` to the registered-for resource +2. Expose that fact to the webhook using the existing fields: + * Set AdmissionRequest `kind` to the API request's kind (not matching the object in the AdmissionRequest) + * Set AdmissionRequest `resource` to the API request's resource (not matching the registered-for resource) +3. Expose that fact to the webhook using new AdmissionRequest fields: + * Set AdmissionRequest `kind` to the converted kind + * Set AdmissionRequest `requestKind` to the API request's kind + * Set AdmissionRequest `resource` to the registered-for resource + * Set AdmissionRequest `requestResource` to the API request's resource + +Option 1 loses information the webhook could use (for example, to enforce different validation or defaulting rules for newer APIs). + +Option 2 risks breaking webhook logic by sending it resources it did not register for, and kinds it did not expect. + +Option 3 is preferred, and is the safest option that preserves information for use by the webhook. + +To support this, three fields will be added to AdmissionRequest, and populated with the original request's kind, resource, and subResource: + +```golang +type AdmissionRequest struct { + ... + // RequestKind is the type of object being manipulated in the original API request. For example: Pod + // If this differs from the value in "kind", conversion was performed. + // +optional + RequestKind *metav1.GroupVersionKind `json:"requestKind,omitempty"` + // RequestResource is the name of the resource being requested in the original API request. This is not the kind. For example: ""/v1/pods + // If this differs from the value in "resource", conversion was performed. + // +optional + RequestResource *metav1.GroupVersionResource `json:"requestResource,omitempty"` + // RequestSubResource is the name of the subresource being requested in the original API request. 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". + // If this differs from the value in "subResource", conversion was performed. + // +optional + RequestSubResource string `json:"requestSubResource,omitempty"` +``` + ## V1 API The currently planned `v1` API is described in this section. @@ -357,6 +482,17 @@ type Rule struct { Scope ScopeType `json:"scope,omitempty" protobuf:"bytes,3,opt,name=scope"` } +type ConversionPolicyType string + +const ( + // ConversionIgnore means that requests that do not match a webhook's rules but could be + // converted to a resource the webhook registered for, should be ignored. + ConversionIgnore ConversionPolicyType = "Ignore" + // ConversionConvert means that requests that do not match a webhook's rules but could be + // converted to a resource the webhook registered for, should be converted and sent to the webhook. + ConversionConvert ConversionPolicyType = "Convert" +) + type FailurePolicyType string const ( @@ -463,6 +599,16 @@ type Webhook struct { // on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects. Rules []RuleWithOperations `json:"rules,omitempty" protobuf:"bytes,3,rep,name=rules"` + // ConversionPolicy describes whether the webhook wants to be called when a request is made + // to a resource that does not match "rules", but which modifies the same data + // as a resource that does match "rules". + // Allowed values are Ignore or Convert. + // "Ignore" means the request should be ignored. + // "Convert" means the request should be converted to a kind corresponding to a resource matched by "rules", and sent to the webhook. + // Defaults to Convert. + // +optional + ConversionPolicy *ConversionPolicyType `json:"conversionPolicy,omitempty"` + // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - // allowed values are Ignore or Fail. Defaults to Ignore. // +optional