Skip to content

Commit

Permalink
feat(kuma-cp) validate resources on kubernetes
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubdyszkiewicz committed Oct 31, 2019
1 parent 54ef1b9 commit 91b8e37
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2831,3 +2831,31 @@ webhooks:
- CREATE
resources:
- pods
---
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
name: kuma-validating-webhook-configuration
webhooks:
- name: validator.kuma-admission.kuma.io
failurePolicy: Fail
clientConfig:
caBundle: Q0VSVA==
service:
namespace: kuma-system
name: kuma-control-plane
path: /validate-kuma-resource
rules:
- apiGroups:
- kuma.io
apiVersions:
- v1alpha1
operations:
- CREATE
- UPDATE
resources:
- trafficlogs
- trafficpermissions
- dataplanes
- meshes
- proxytemplates
Original file line number Diff line number Diff line change
Expand Up @@ -2832,3 +2832,31 @@ webhooks:
- CREATE
resources:
- pods
---
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
name: kuma-validating-webhook-configuration
webhooks:
- name: validator.kuma-admission.kuma.io
failurePolicy: Fail
clientConfig:
caBundle: QWRtaXNzaW9uQ2VydA==
service:
namespace: kuma
name: kuma-ctrl-plane
path: /validate-kuma-resource
rules:
- apiGroups:
- kuma.io
apiVersions:
- v1alpha1
operations:
- CREATE
- UPDATE
resources:
- trafficlogs
- trafficpermissions
- dataplanes
- meshes
- proxytemplates
28 changes: 28 additions & 0 deletions app/kumactl/data/install/k8s/control-plane/kuma-cp/app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,31 @@ webhooks:
- UPDATE
resources:
- meshes
---
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
name: kuma-validating-webhook-configuration
webhooks:
- name: validator.kuma-admission.kuma.io
failurePolicy: Fail
clientConfig:
caBundle: {{ .AdmissionServerTlsCert | b64enc }}
service:
namespace: {{ .Namespace }}
name: {{ .ControlPlaneServiceName }}
path: /validate-kuma-resource
rules:
- apiGroups:
- kuma.io
apiVersions:
- v1alpha1
operations:
- CREATE
- UPDATE
resources:
- trafficlogs
- trafficpermissions
- dataplanes
- meshes
- proxytemplates
34 changes: 17 additions & 17 deletions app/kumactl/pkg/install/k8s/control-plane/templates_vfsdata.go

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions pkg/plugins/runtime/k8s/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import (
core_plugins "github.com/Kong/kuma/pkg/core/plugins"
mesh_core "github.com/Kong/kuma/pkg/core/resources/apis/mesh"
core_model "github.com/Kong/kuma/pkg/core/resources/model"
core_registry "github.com/Kong/kuma/pkg/core/resources/registry"
core_runtime "github.com/Kong/kuma/pkg/core/runtime"
k8s_resources "github.com/Kong/kuma/pkg/plugins/resources/k8s"
mesh_k8s "github.com/Kong/kuma/pkg/plugins/resources/k8s/native/api/v1alpha1"
k8s_registry "github.com/Kong/kuma/pkg/plugins/resources/k8s/native/pkg/registry"
k8s_controllers "github.com/Kong/kuma/pkg/plugins/runtime/k8s/controllers"
k8s_webhooks "github.com/Kong/kuma/pkg/plugins/runtime/k8s/webhooks"
k8s_runtime "github.com/Kong/kuma/pkg/runtime/k8s"
Expand Down Expand Up @@ -43,6 +45,10 @@ func (p *plugin) Customize(rt core_runtime.Runtime) error {
return err
}

if err := addValidators(mgr); err != nil {
return err
}

return addDefaulters(mgr)
}

Expand Down Expand Up @@ -101,3 +107,14 @@ func addDefaulter(mgr kube_ctrl.Manager, gvk kube_schema.GroupVersionKind, facto
func generateDefaulterPath(gvk kube_schema.GroupVersionKind) string {
return fmt.Sprintf("/default-%s-%s-%s", strings.Replace(gvk.Group, ".", "-", -1), gvk.Version, strings.ToLower(gvk.Kind))
}

func addValidators(mgr kube_ctrl.Manager) error {
handler, err := k8s_webhooks.NewValidatingWebhook(k8s_resources.DefaultConverter(), core_registry.Global(), k8s_registry.Global())
if err != nil {
return err
}
path := "/validate-kuma-resource"
mgr.GetWebhookServer().Register(path, handler)
log.Info("Registering a validation webhook", "path", path)
return nil
}
63 changes: 63 additions & 0 deletions pkg/plugins/runtime/k8s/webhooks/validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package webhooks

import (
"context"
"net/http"

"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

core_model "github.com/Kong/kuma/pkg/core/resources/model"
core_registry "github.com/Kong/kuma/pkg/core/resources/registry"
k8s_resources "github.com/Kong/kuma/pkg/plugins/resources/k8s"
k8s_model "github.com/Kong/kuma/pkg/plugins/resources/k8s/native/pkg/model"
k8s_registry "github.com/Kong/kuma/pkg/plugins/resources/k8s/native/pkg/registry"
)

func NewValidatingWebhook(converter k8s_resources.Converter, coreRegistry core_registry.TypeRegistry, k8sRegistry k8s_registry.TypeRegistry) (*admission.Webhook, error) {
return &admission.Webhook{
Handler: &validatingHandler{
coreRegistry: coreRegistry,
k8sRegistry: k8sRegistry,
converter: converter,
},
}, nil
}

type validatingHandler struct {
coreRegistry core_registry.TypeRegistry
k8sRegistry k8s_registry.TypeRegistry
converter k8s_resources.Converter
decoder *admission.Decoder
}

func (h *validatingHandler) InjectDecoder(d *admission.Decoder) error {
h.decoder = d
return nil
}

func (h *validatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
resType := core_model.ResourceType(req.Kind.Kind)

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

if err := h.converter.ToCoreResource(obj.(k8s_model.KubernetesObject), coreRes); err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}

if err := coreRes.Validate(); err != nil {
return admission.Denied(err.Error())
}

return admission.Allowed("")
}
129 changes: 129 additions & 0 deletions pkg/plugins/runtime/k8s/webhooks/validation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package webhooks_test

import (
"context"

. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"

core_registry "github.com/Kong/kuma/pkg/core/resources/registry"
k8s_resources "github.com/Kong/kuma/pkg/plugins/resources/k8s"
k8s_registry "github.com/Kong/kuma/pkg/plugins/resources/k8s/native/pkg/registry"
sample_k8s "github.com/Kong/kuma/pkg/plugins/resources/k8s/native/test/api/sample/v1alpha1"
"github.com/Kong/kuma/pkg/plugins/runtime/k8s/webhooks"
sample_proto "github.com/Kong/kuma/pkg/test/apis/sample/v1alpha1"
sample_core "github.com/Kong/kuma/pkg/test/resources/apis/sample"

admissionv1beta1 "k8s.io/api/admission/v1beta1"
kube_meta "k8s.io/apimachinery/pkg/apis/meta/v1"
kube_runtime "k8s.io/apimachinery/pkg/runtime"
kube_types "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
kube_admission "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

var _ = Describe("Validation", func() {

var handler *admission.Webhook
var kubeTypes k8s_registry.TypeRegistry

BeforeEach(func() {
kubeTypes = k8s_registry.NewTypeRegistry()
err := kubeTypes.RegisterObjectType(&sample_proto.TrafficRoute{}, &sample_k8s.TrafficRoute{
TypeMeta: kube_meta.TypeMeta{
APIVersion: sample_k8s.GroupVersion.String(),
Kind: "TrafficRoute",
},
})
Expect(err).ToNot(HaveOccurred())

converter := &k8s_resources.SimpleConverter{
KubeFactory: &k8s_resources.SimpleKubeFactory{
KubeTypes: kubeTypes,
},
}

types := core_registry.NewTypeRegistry()
err = types.RegisterType(&sample_core.TrafficRouteResource{})
Expect(err).ToNot(HaveOccurred())

webhook, err := webhooks.NewValidatingWebhook(converter, types, kubeTypes)
Expect(err).ToNot(HaveOccurred())

scheme := kube_runtime.NewScheme()
Expect(sample_k8s.AddToScheme(scheme)).To(Succeed())
Expect(webhook.InjectScheme(scheme)).To(Succeed())
handler = webhook
})

type testCase struct {
obj string
allowed bool
reason string
}
DescribeTable("Validation",
func(given testCase) {
// given
obj, err := kubeTypes.NewObject(&sample_proto.TrafficRoute{})
Expect(err).ToNot(HaveOccurred())
req := kube_admission.Request{
AdmissionRequest: admissionv1beta1.AdmissionRequest{
UID: kube_types.UID("12345"),
Object: kube_runtime.RawExtension{
Raw: []byte(given.obj),
},
Kind: kube_meta.GroupVersionKind{
Group: obj.GetObjectKind().GroupVersionKind().Group,
Version: obj.GetObjectKind().GroupVersionKind().Version,
Kind: obj.GetObjectKind().GroupVersionKind().Kind,
},
},
}

// when
resp := handler.Handle(context.Background(), req)

// then
Expect(resp.Allowed).To(Equal(given.allowed))
Expect(string(resp.Result.Reason)).To(Equal(given.reason))
},
Entry("should pass validation", testCase{
obj: `
{
"apiVersion": "sample.test.kuma.io/v1alpha1",
"kind": "TrafficRoute",
"mesh": "demo",
"metadata": {
"namespace": "example",
"name": "empty",
"creationTimestamp": null
},
"spec": {
"path": "/random"
}
}
`,
allowed: true,
reason: "",
}),
Entry("should fail validation", testCase{
obj: `
{
"apiVersion": "sample.test.kuma.io/v1alpha1",
"kind": "TrafficRoute",
"mesh": "demo",
"metadata": {
"namespace": "example",
"name": "empty",
"creationTimestamp": null
},
"spec": {
}
}
`,
allowed: false,
reason: "validation error: Path cannot be empty",
}),
)
})

0 comments on commit 91b8e37

Please sign in to comment.