Skip to content

Commit

Permalink
fix(kuma-cp) possible to delete resources on Zone CP (kumahq#2665)
Browse files Browse the repository at this point in the history
Signed-off-by: Ilya Lobkov <ilya.lobkov@konghq.com>
  • Loading branch information
lobkovilya authored and nikita15p committed Sep 28, 2021
1 parent 0f3a3d2 commit dc40915
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 78 deletions.
101 changes: 52 additions & 49 deletions pkg/plugins/runtime/k8s/webhooks/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
k8s_common "github.com/kumahq/kuma/pkg/plugins/common/k8s"
k8s_model "github.com/kumahq/kuma/pkg/plugins/resources/k8s/native/pkg/model"
k8s_registry "github.com/kumahq/kuma/pkg/plugins/resources/k8s/native/pkg/registry"
"github.com/kumahq/kuma/pkg/version"
)

func NewValidatingWebhook(converter k8s_common.Converter, coreRegistry core_registry.TypeRegistry, k8sRegistry k8s_registry.TypeRegistry, mode core.CpMode, systemNamespace string) k8s_common.AdmissionValidator {
Expand Down Expand Up @@ -48,72 +49,77 @@ func (h *validatingHandler) InjectDecoder(d *admission.Decoder) error {
}

func (h *validatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
if req.Operation == v1beta1.Delete {
return admission.Allowed("")
}

resType := core_model.ResourceType(req.Kind.Kind)

coreRes, err := h.coreRegistry.NewObject(resType)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
obj, err := h.k8sRegistry.NewObject(coreRes.GetSpec())
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
// unmarshal k8s object from the request
if err := h.decoder.Decode(req, obj); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
if resp := h.validateSync(resType, obj, req.UserInfo); !resp.Allowed {
return resp
}
if resp := h.validateResourceLocation(resType, obj); !resp.Allowed {
if resp := h.isOperationAllowed(resType, req.UserInfo, req.Operation); !resp.Allowed {
return resp
}

if err := h.converter.ToCoreResource(obj, coreRes); err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}
switch req.Operation {
case v1beta1.Delete:
return admission.Allowed("")
default:
if resp := h.validateResourceLocation(resType); !resp.Allowed {
return resp
}

if err := core_mesh.ValidateMesh(obj.GetMesh(), coreRes.Descriptor().Scope); err.HasViolations() {
return convertValidationErrorOf(err, obj, obj.GetObjectMeta())
}
coreRes, k8sObj, err := h.decode(req)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}

if err := coreRes.Validate(); err != nil {
if kumaErr, ok := err.(*validators.ValidationError); ok {
// we assume that coreRes.Validate() returns validation errors of the spec
return convertSpecValidationError(kumaErr, obj)
if err := core_mesh.ValidateMesh(k8sObj.GetMesh(), coreRes.Descriptor().Scope); err.HasViolations() {
return convertValidationErrorOf(err, k8sObj, k8sObj.GetObjectMeta())
}
return admission.Denied(err.Error())
}

return admission.Allowed("")
}
if err := coreRes.Validate(); err != nil {
if kumaErr, ok := err.(*validators.ValidationError); ok {
// we assume that coreRes.Validate() returns validation errors of the spec
return convertSpecValidationError(kumaErr, k8sObj)
}
return admission.Denied(err.Error())
}

// Note that this func does not validate ConfigMap and Secret since this webhook does not support those
func (h *validatingHandler) validateSync(resType core_model.ResourceType, obj k8s_model.KubernetesObject, userInfo authenticationv1.UserInfo) admission.Response {
if isDefaultMesh(resType, obj) { // skip validation for the default mesh
return admission.Allowed("")
}
}

func (h *validatingHandler) decode(req admission.Request) (core_model.Resource, k8s_model.KubernetesObject, error) {
coreRes, err := h.coreRegistry.NewObject(core_model.ResourceType(req.Kind.Kind))
if err != nil {
return nil, nil, err
}
k8sObj, err := h.k8sRegistry.NewObject(coreRes.GetSpec())
if err != nil {
return nil, nil, err
}
if err := h.decoder.Decode(req, k8sObj); err != nil {
return nil, nil, err
}
if err := h.converter.ToCoreResource(k8sObj, coreRes); err != nil {
return nil, nil, err
}
return coreRes, k8sObj, nil
}

// Note that this func does not validate ConfigMap and Secret since this webhook does not support those
func (h *validatingHandler) isOperationAllowed(resType core_model.ResourceType, userInfo authenticationv1.UserInfo, op v1beta1.Operation) admission.Response {
if isKumaServiceAccount(userInfo, h.systemNamespace) {
// Assume this means sync from another zone. Not security; protecting user from self.
return admission.Allowed("")
}

descriptor, err := h.coreRegistry.DescriptorFor(resType)
if err != nil {
return syncErrorResponse(resType, h.mode)
return syncErrorResponse(resType, h.mode, op)
}
if (h.mode == core.Global && descriptor.KDSFlags.Has(core_model.ConsumedByGlobal)) || (h.mode == core.Zone && resType != core_mesh.DataplaneType && descriptor.KDSFlags.Has(core_model.ConsumedByZone)) {
return syncErrorResponse(resType, h.mode)
return syncErrorResponse(resType, h.mode, op)
}
return admission.Allowed("")
}

func syncErrorResponse(resType core_model.ResourceType, cpMode core.CpMode) admission.Response {
func syncErrorResponse(resType core_model.ResourceType, cpMode core.CpMode, op v1beta1.Operation) admission.Response {
otherCpMode := ""
if cpMode == core.Zone {
otherCpMode = core.Global
Expand All @@ -124,10 +130,11 @@ func syncErrorResponse(resType core_model.ResourceType, cpMode core.CpMode) admi
AdmissionResponse: v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Status: "Failure",
Message: fmt.Sprintf("You are trying to apply a %s on %s CP. In multizone setup, it should be only applied on %s CP and synced to %s CP.", resType, cpMode, otherCpMode, cpMode),
Reason: "Forbidden",
Code: 403,
Status: "Failure",
Message: fmt.Sprintf("Operation not allowed. %s resources like %s can be updated or deleted only "+
"from the %s control plane and not from a %s control plane.", version.Product, resType, strings.ToUpper(otherCpMode), strings.ToUpper(cpMode)),
Reason: "Forbidden",
Code: 403,
Details: &metav1.StatusDetails{
Causes: []metav1.StatusCause{
{
Expand All @@ -151,12 +158,8 @@ func isKumaServiceAccount(userInfo authenticationv1.UserInfo, systemNamespace st
return false
}

func isDefaultMesh(resType core_model.ResourceType, obj k8s_model.KubernetesObject) bool {
return resType == core_mesh.MeshType && obj.GetName() == core_model.DefaultMesh && len(obj.GetSpec()) == 0
}

// validateResourceLocation validates if resources that suppose to be applied on Global are applied on Global and other way around
func (h *validatingHandler) validateResourceLocation(resType core_model.ResourceType, obj k8s_model.KubernetesObject) admission.Response {
func (h *validatingHandler) validateResourceLocation(resType core_model.ResourceType) admission.Response {
if err := system.ValidateLocation(resType, h.mode); err != nil {
return admission.Response{
AdmissionResponse: v1beta1.AdmissionResponse{
Expand Down
Loading

0 comments on commit dc40915

Please sign in to comment.