Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(kuma-cp) possible to delete resources on Zone CP #2665

Merged
merged 7 commits into from
Aug 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
parkanzky marked this conversation as resolved.
Show resolved Hide resolved
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