mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-08-25 16:07:24 +00:00
feat: emitting events for policies violations
This commit is contained in:
@@ -6,6 +6,7 @@ package webhook
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/client-go/tools/record"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
)
|
||||
@@ -13,7 +14,7 @@ import (
|
||||
type Func func(ctx context.Context, req admission.Request) admission.Response
|
||||
|
||||
type Handler interface {
|
||||
OnCreate(client client.Client, decoder *admission.Decoder) Func
|
||||
OnDelete(client client.Client, decoder *admission.Decoder) Func
|
||||
OnUpdate(client client.Client, decoder *admission.Decoder) Func
|
||||
OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) Func
|
||||
OnDelete(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) Func
|
||||
OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) Func
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
@@ -45,7 +46,7 @@ func Handler() capsulewebhook.Handler {
|
||||
return &handler{}
|
||||
}
|
||||
|
||||
func (r *handler) OnCreate(c client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (r *handler) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
var pod = &corev1.Pod{}
|
||||
|
||||
@@ -77,6 +78,8 @@ func (r *handler) OnCreate(c client.Client, decoder *admission.Decoder) capsulew
|
||||
usedPullPolicy := string(container.ImagePullPolicy)
|
||||
|
||||
if !policy.IsPolicySupported(usedPullPolicy) {
|
||||
recorder.Eventf(&tnt, corev1.EventTypeWarning, "PullPolicy", "Pod %s/%s pull policy %s is not allowed", req.Namespace, req.Name, usedPullPolicy)
|
||||
|
||||
return admission.Denied(NewImagePullPolicyForbidden(usedPullPolicy, container.Name, policy.AllowedPullPolicies()).Error())
|
||||
}
|
||||
}
|
||||
@@ -85,13 +88,13 @@ func (r *handler) OnCreate(c client.Client, decoder *admission.Decoder) capsulew
|
||||
}
|
||||
}
|
||||
|
||||
func (r *handler) OnUpdate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (r *handler) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *handler) OnDelete(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (r *handler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
@@ -8,12 +8,15 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
networkingv1beta1 "k8s.io/api/networking/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
@@ -53,29 +56,29 @@ func Handler(configuration configuration.Configuration) capsulewebhook.Handler {
|
||||
return &handler{configuration: configuration}
|
||||
}
|
||||
|
||||
func (r *handler) OnCreate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (r *handler) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
ingress, err := r.ingressFromRequest(req, decoder)
|
||||
if err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
return r.validateIngress(ctx, client, ingress)
|
||||
return r.validateIngress(ctx, client, ingress, recorder)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *handler) OnUpdate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (r *handler) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
ingress, err := r.ingressFromRequest(req, decoder)
|
||||
if err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
return r.validateIngress(ctx, client, ingress)
|
||||
return r.validateIngress(ctx, client, ingress, recorder)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *handler) OnDelete(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (r *handler) OnDelete(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
@@ -173,7 +176,7 @@ func (r *handler) validateHostnames(tenant v1alpha1.Tenant, hostnames []string)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *handler) validateIngress(ctx context.Context, c client.Client, ingress Ingress) admission.Response {
|
||||
func (r *handler) validateIngress(ctx context.Context, c client.Client, ingress Ingress, recorder record.EventRecorder) admission.Response {
|
||||
tenantList := &v1alpha1.TenantList{}
|
||||
if err := c.List(ctx, tenantList, client.MatchingFieldsSelector{
|
||||
Selector: fields.OneTermEqualSelector(".status.namespaces", ingress.Namespace()),
|
||||
@@ -187,14 +190,24 @@ func (r *handler) validateIngress(ctx context.Context, c client.Client, ingress
|
||||
tenant := tenantList.Items[0]
|
||||
|
||||
if err := r.validateClass(tenant, ingress.IngressClass()); err != nil {
|
||||
if ic := ingress.IngressClass(); ic != nil {
|
||||
recorder.Eventf(&tenant, corev1.EventTypeWarning, "InvalidIngressClass", "Ingress %s/%s class %s is forbidden for the current Tenant", ingress.Namespace(), ingress.Name(), *ic)
|
||||
} else {
|
||||
recorder.Eventf(&tenant, corev1.EventTypeWarning, "MissingIngressClass", "Ingress %s/%s is missing required class for the current Tenant", ingress.Namespace(), ingress.Name())
|
||||
}
|
||||
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
if err := r.validateHostnames(tenant, ingress.Hostnames()); err != nil {
|
||||
recorder.Eventf(&tenant, corev1.EventTypeWarning, "InvalidHostname", "Ingress %s/%s hostnames %s is forbidden for the current Tenant", ingress.Namespace(), ingress.Name(), strings.Join(ingress.Hostnames(), ","))
|
||||
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
if err := r.validateCollision(ctx, c, ingress); err != nil {
|
||||
recorder.Eventf(&tenant, corev1.EventTypeWarning, "HostnameCollision", "Ingress %s/%s hostnames collision for %s ", ingress.Namespace(), ingress.Name(), strings.Join(ingress.Hostnames(), ","))
|
||||
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -42,16 +42,13 @@ func (w *webhook) GetPath() string {
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
recorder record.EventRecorder
|
||||
}
|
||||
|
||||
func Handler(recorder record.EventRecorder) capsulewebhook.Handler {
|
||||
return &handler{
|
||||
recorder: recorder,
|
||||
}
|
||||
func Handler() capsulewebhook.Handler {
|
||||
return &handler{}
|
||||
}
|
||||
|
||||
func (r *handler) OnCreate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (r *handler) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
ns := &corev1.Namespace{}
|
||||
if err := decoder.Decode(req, ns); err != nil {
|
||||
@@ -65,7 +62,7 @@ func (r *handler) OnCreate(client client.Client, decoder *admission.Decoder) cap
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
if tnt.IsFull() {
|
||||
r.recorder.Eventf(tnt, corev1.EventTypeWarning, "Error", "the Namespace quota has been exceeded, Namespace %s cannot been attached", ns.GetName())
|
||||
recorder.Eventf(tnt, corev1.EventTypeWarning, "NamespaceQuota", "Namespace %s cannot be attached, quota exceeded", ns.GetName())
|
||||
|
||||
return admission.Denied(NewNamespaceQuotaExceededError().Error())
|
||||
}
|
||||
@@ -75,13 +72,13 @@ func (r *handler) OnCreate(client client.Client, decoder *admission.Decoder) cap
|
||||
}
|
||||
}
|
||||
|
||||
func (r *handler) OnDelete(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (r *handler) OnDelete(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *handler) OnUpdate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (r *handler) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
@@ -45,30 +47,44 @@ func Handler() capsulewebhook.Handler {
|
||||
return &handler{}
|
||||
}
|
||||
|
||||
func (r *handler) OnCreate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (r *handler) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *handler) generic(ctx context.Context, req admission.Request, client client.Client, _ *admission.Decoder) (bool, error) {
|
||||
func (r *handler) generic(ctx context.Context, req admission.Request, client client.Client, _ *admission.Decoder) (*v1alpha1.Tenant, error) {
|
||||
var err error
|
||||
np := &networkingv1.NetworkPolicy{}
|
||||
err = client.Get(ctx, types.NamespacedName{Namespace: req.AdmissionRequest.Namespace, Name: req.AdmissionRequest.Name}, np)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.isCapsuleNetworkPolicy(np), nil
|
||||
tnt := &v1alpha1.Tenant{}
|
||||
|
||||
l, _ := v1alpha1.GetTypeLabel(&v1alpha1.Tenant{})
|
||||
if v, ok := np.GetLabels()[l]; ok {
|
||||
if err = client.Get(ctx, types.NamespacedName{Name: v}, tnt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tnt, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *handler) OnDelete(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
// nolint:dupl
|
||||
func (r *handler) OnDelete(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
ok, err := r.generic(ctx, req, client, decoder)
|
||||
tnt, err := r.generic(ctx, req, client, decoder)
|
||||
if err != nil {
|
||||
return admission.Errored(http.StatusInternalServerError, err)
|
||||
}
|
||||
if ok {
|
||||
if tnt != nil {
|
||||
recorder.Eventf(tnt, corev1.EventTypeWarning, "NetworkPolicyDeletion", "NetworkPolicy %s/%s cannot be deleted", req.Namespace, req.Name)
|
||||
|
||||
return admission.Denied("Capsule Network Policies cannot be deleted: please, reach out to the system administrators")
|
||||
}
|
||||
|
||||
@@ -76,19 +92,16 @@ func (r *handler) OnDelete(client client.Client, decoder *admission.Decoder) cap
|
||||
}
|
||||
}
|
||||
|
||||
func (r *handler) isCapsuleNetworkPolicy(np *networkingv1.NetworkPolicy) (ok bool) {
|
||||
l, _ := v1alpha1.GetTypeLabel(&v1alpha1.Tenant{})
|
||||
_, ok = np.GetLabels()[l]
|
||||
return
|
||||
}
|
||||
|
||||
func (r *handler) OnUpdate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
// nolint:dupl
|
||||
func (r *handler) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
ok, err := r.generic(ctx, req, client, decoder)
|
||||
tnt, err := r.generic(ctx, req, client, decoder)
|
||||
if err != nil {
|
||||
return admission.Errored(http.StatusInternalServerError, err)
|
||||
}
|
||||
if ok {
|
||||
if tnt != nil {
|
||||
recorder.Eventf(tnt, corev1.EventTypeWarning, "NetworkPolicyUpdate", "NetworkPolicy %s/%s cannot be updated", req.Namespace, req.Name)
|
||||
|
||||
return admission.Denied("Capsule Network Policies cannot be updated: please, reach out to the system administrators")
|
||||
}
|
||||
|
||||
|
||||
@@ -48,18 +48,16 @@ func (w *webhook) GetPath() string {
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
cfg configuration.Configuration
|
||||
recorder record.EventRecorder
|
||||
cfg configuration.Configuration
|
||||
}
|
||||
|
||||
func Handler(cfg configuration.Configuration, recorder record.EventRecorder) capsulewebhook.Handler {
|
||||
func Handler(cfg configuration.Configuration) capsulewebhook.Handler {
|
||||
return &handler{
|
||||
cfg: cfg,
|
||||
recorder: recorder,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) OnCreate(clt client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (h *handler) OnCreate(clt client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
ns := &corev1.Namespace{}
|
||||
if err := decoder.Decode(req, ns); err != nil {
|
||||
@@ -78,10 +76,12 @@ func (h *handler) OnCreate(clt client.Client, decoder *admission.Decoder) capsul
|
||||
}
|
||||
// Tenant owner must adhere to user that asked for NS creation
|
||||
if !h.isTenantOwner(tnt.Spec.Owner, req.UserInfo) {
|
||||
recorder.Eventf(tnt, corev1.EventTypeWarning, "NonOwnedTenant", "Namespace %s cannot be assigned to the current Tenant", ns.GetName())
|
||||
|
||||
return admission.Denied("Cannot assign the desired namespace to a non-owned Tenant")
|
||||
}
|
||||
// Patching the response
|
||||
return h.patchResponseForOwnerRef(tnt, ns)
|
||||
return h.patchResponseForOwnerRef(tnt, ns, recorder)
|
||||
}
|
||||
|
||||
// If we forceTenantPrefix -> find Tenant from NS name
|
||||
@@ -117,13 +117,13 @@ func (h *handler) OnCreate(clt client.Client, decoder *admission.Decoder) capsul
|
||||
}
|
||||
|
||||
if len(tenants) == 1 {
|
||||
return h.patchResponseForOwnerRef(&tenants[0], ns)
|
||||
return h.patchResponseForOwnerRef(&tenants[0], ns, recorder)
|
||||
}
|
||||
|
||||
if h.cfg.ForceTenantPrefix() {
|
||||
for _, tnt := range tenants {
|
||||
if strings.HasPrefix(ns.GetName(), fmt.Sprintf("%s-", tnt.GetName())) {
|
||||
return h.patchResponseForOwnerRef(tnt.DeepCopy(), ns)
|
||||
return h.patchResponseForOwnerRef(tnt.DeepCopy(), ns, recorder)
|
||||
}
|
||||
}
|
||||
admission.Denied("The Namespace prefix used doesn't match any available Tenant")
|
||||
@@ -132,31 +132,31 @@ func (h *handler) OnCreate(clt client.Client, decoder *admission.Decoder) capsul
|
||||
return admission.Denied("Unable to assign namespace to tenant. Please use " + ln + " label when creating a namespace")
|
||||
}
|
||||
}
|
||||
func (h *handler) OnDelete(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (h *handler) OnDelete(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) OnUpdate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (h *handler) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Denied("Capsule user cannot update a Namespace")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) patchResponseForOwnerRef(tenant *capsulev1alpha1.Tenant, ns *corev1.Namespace) admission.Response {
|
||||
func (h *handler) patchResponseForOwnerRef(tenant *capsulev1alpha1.Tenant, ns *corev1.Namespace, recorder record.EventRecorder) admission.Response {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = capsulev1alpha1.AddToScheme(scheme)
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
o, _ := json.Marshal(ns.DeepCopy())
|
||||
if err := controllerutil.SetControllerReference(tenant, ns, scheme); err != nil {
|
||||
h.recorder.Eventf(tenant, corev1.EventTypeWarning, "Error", "namespace %s cannot be assigned to the desired Tenant", ns.GetName())
|
||||
recorder.Eventf(tenant, corev1.EventTypeWarning, "Error", "Namespace %s cannot be assigned to the desired Tenant", ns.GetName())
|
||||
|
||||
return admission.Errored(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
h.recorder.Eventf(tenant, corev1.EventTypeNormal, "NamespaceCreationWebhook", "namespace %s has been assigned to the desired Tenant", ns.GetName())
|
||||
recorder.Eventf(tenant, corev1.EventTypeNormal, "NamespaceCreationWebhook", "Namespace %s has been assigned to the desired Tenant", ns.GetName())
|
||||
|
||||
c, _ := json.Marshal(ns)
|
||||
return admission.PatchResponseFromRaw(o, c)
|
||||
|
||||
@@ -7,8 +7,9 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
@@ -46,9 +47,9 @@ func Handler() capsulewebhook.Handler {
|
||||
return &handler{}
|
||||
}
|
||||
|
||||
func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
var pod = &v1.Pod{}
|
||||
var pod = &corev1.Pod{}
|
||||
|
||||
if err := decoder.Decode(req, pod); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
@@ -78,6 +79,8 @@ func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder) capsulew
|
||||
// We don't have to force Pod to specify a Priority Class
|
||||
return admission.Allowed("")
|
||||
case !allowed.ExactMatch(priorityClassName) && !allowed.RegexMatch(priorityClassName):
|
||||
recorder.Eventf(&tntList.Items[0], corev1.EventTypeWarning, "PriorityClass", "Pod %s/%s is using Priority Class %s not allowed for the current Tenant", pod.Namespace, pod.Name, priorityClassName)
|
||||
|
||||
return admission.Errored(http.StatusBadRequest, NewPodPriorityClassForbidden(priorityClassName, *allowed))
|
||||
default:
|
||||
return admission.Allowed("")
|
||||
@@ -85,13 +88,13 @@ func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder) capsulew
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) OnDelete(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (h *handler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) OnUpdate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (h *handler) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
@@ -45,10 +46,10 @@ func Handler() capsulewebhook.Handler {
|
||||
return &handler{}
|
||||
}
|
||||
|
||||
func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
var valid, matched bool
|
||||
pvc := &v1.PersistentVolumeClaim{}
|
||||
pvc := &corev1.PersistentVolumeClaim{}
|
||||
|
||||
if err := decoder.Decode(req, pvc); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
@@ -72,6 +73,8 @@ func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder) capsulew
|
||||
}
|
||||
|
||||
if pvc.Spec.StorageClassName == nil {
|
||||
recorder.Eventf(&tnt, corev1.EventTypeWarning, "StorageClassInvalid", "PersistentVolumeClaim %s/%s is missing StorageClass", req.Namespace, req.Name)
|
||||
|
||||
return admission.Errored(http.StatusBadRequest, NewStorageClassNotValid(*tntList.Items[0].Spec.StorageClasses))
|
||||
}
|
||||
|
||||
@@ -79,19 +82,21 @@ func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder) capsulew
|
||||
valid = tnt.Spec.StorageClasses.ExactMatch(sc)
|
||||
matched = tnt.Spec.StorageClasses.RegexMatch(sc)
|
||||
if !valid && !matched {
|
||||
recorder.Eventf(&tnt, corev1.EventTypeWarning, "StorageClassForbidden", "PersistentVolumeClaim %s/%s StorageClass %s is forbidden for the current Tenant", req.Namespace, req.Name, sc)
|
||||
|
||||
return admission.Errored(http.StatusBadRequest, NewStorageClassForbidden(*pvc.Spec.StorageClassName, *tnt.Spec.StorageClasses))
|
||||
}
|
||||
return admission.Allowed("")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) OnDelete(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (h *handler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) OnUpdate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (h *handler) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
@@ -46,9 +47,9 @@ func Handler() capsulewebhook.Handler {
|
||||
return &handler{}
|
||||
}
|
||||
|
||||
func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
pod := &v1.Pod{}
|
||||
pod := &corev1.Pod{}
|
||||
if err := decoder.Decode(req, pod); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
@@ -72,6 +73,8 @@ func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder) capsulew
|
||||
valid = tnt.Spec.ContainerRegistries.ExactMatch(registry.Registry())
|
||||
matched = tnt.Spec.ContainerRegistries.RegexMatch(registry.Registry())
|
||||
if !valid && !matched {
|
||||
recorder.Eventf(&tnt, corev1.EventTypeWarning, "ContainerRegistry", "Pod %s/%s is using a forbidden registry %s", req.Namespace, req.Name, registry.Registry())
|
||||
|
||||
return admission.Errored(http.StatusBadRequest, NewContainerRegistryForbidden(container.Image, *tnt.Spec.ContainerRegistries))
|
||||
}
|
||||
}
|
||||
@@ -81,13 +84,13 @@ func (h *handler) OnCreate(c client.Client, decoder *admission.Decoder) capsulew
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) OnDelete(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (h *handler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) OnUpdate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (h *handler) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
+12
-7
@@ -8,6 +8,7 @@ import (
|
||||
"io/ioutil"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/client-go/tools/record"
|
||||
controllerruntime "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
@@ -21,11 +22,14 @@ func Register(manager controllerruntime.Manager, webhookList ...Webhook) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
recorder := manager.GetEventRecorderFor("tenant-webhook")
|
||||
|
||||
server := manager.GetWebhookServer()
|
||||
for _, wh := range webhookList {
|
||||
server.Register(wh.GetPath(), &webhook.Admission{
|
||||
Handler: &handlerRouter{
|
||||
handler: wh.GetHandler(),
|
||||
recorder: recorder,
|
||||
handler: wh.GetHandler(),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -33,19 +37,20 @@ func Register(manager controllerruntime.Manager, webhookList ...Webhook) error {
|
||||
}
|
||||
|
||||
type handlerRouter struct {
|
||||
handler Handler
|
||||
client client.Client
|
||||
decoder *admission.Decoder
|
||||
handler Handler
|
||||
client client.Client
|
||||
decoder *admission.Decoder
|
||||
recorder record.EventRecorder
|
||||
}
|
||||
|
||||
func (r *handlerRouter) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
switch req.Operation {
|
||||
case admissionv1.Create:
|
||||
return r.handler.OnCreate(r.client, r.decoder)(ctx, req)
|
||||
return r.handler.OnCreate(r.client, r.decoder, r.recorder)(ctx, req)
|
||||
case admissionv1.Update:
|
||||
return r.handler.OnUpdate(r.client, r.decoder)(ctx, req)
|
||||
return r.handler.OnUpdate(r.client, r.decoder, r.recorder)(ctx, req)
|
||||
case admissionv1.Delete:
|
||||
return r.handler.OnDelete(r.client, r.decoder)(ctx, req)
|
||||
return r.handler.OnDelete(r.client, r.decoder, r.recorder)(ctx, req)
|
||||
default:
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
@@ -7,9 +7,11 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
@@ -49,7 +51,7 @@ func Handler() capsulewebhook.Handler {
|
||||
return &handler{}
|
||||
}
|
||||
|
||||
func (r *handler) handleService(ctx context.Context, clt client.Client, decoder *admission.Decoder, req admission.Request) admission.Response {
|
||||
func (r *handler) handleService(ctx context.Context, clt client.Client, decoder *admission.Decoder, req admission.Request, recorder record.EventRecorder) admission.Response {
|
||||
svc := &corev1.Service{}
|
||||
if err := decoder.Decode(req, svc); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
@@ -67,6 +69,8 @@ func (r *handler) handleService(ctx context.Context, clt client.Client, decoder
|
||||
tnt := tntList.Items[0]
|
||||
|
||||
if svc.Spec.Type == corev1.ServiceTypeNodePort && tnt.GetAnnotations()[enableNodePortsAnnotation] == "false" {
|
||||
recorder.Eventf(&tnt, corev1.EventTypeWarning, "NodePort", "Service %s/%s cannot be type of NodePort", req.Namespace, req.Name)
|
||||
|
||||
return admission.Errored(http.StatusBadRequest, NewNodePortDisabledError())
|
||||
}
|
||||
|
||||
@@ -84,22 +88,24 @@ func (r *handler) handleService(ctx context.Context, clt client.Client, decoder
|
||||
}
|
||||
}
|
||||
|
||||
recorder.Eventf(&tnt, corev1.EventTypeWarning, "NodePort", "Service %s/%s external IPs %s are not in the expected range for the current Tenant", req.Namespace, req.Name, strings.Join(svc.Spec.ExternalIPs, ","))
|
||||
|
||||
return admission.Errored(http.StatusBadRequest, NewExternalServiceIPForbidden(tnt.Spec.ExternalServiceIPs.Allowed))
|
||||
}
|
||||
|
||||
func (r *handler) OnCreate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (r *handler) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return r.handleService(ctx, client, decoder, req)
|
||||
return r.handleService(ctx, client, decoder, req, recorder)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *handler) OnUpdate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (r *handler) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return r.handleService(ctx, client, decoder, req)
|
||||
return r.handleService(ctx, client, decoder, req, recorder)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *handler) OnDelete(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (r *handler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"regexp"
|
||||
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
@@ -153,7 +154,7 @@ func (h *handler) validateTenantByRegex(tenant *v1alpha1.Tenant) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handler) OnCreate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (h *handler) OnCreate(client client.Client, decoder *admission.Decoder, _ record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
if err := h.validateTenant(ctx, req, client, decoder); err != nil {
|
||||
return admission.Denied(err.Error())
|
||||
@@ -162,13 +163,13 @@ func (h *handler) OnCreate(client client.Client, decoder *admission.Decoder) cap
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) OnDelete(client.Client, *admission.Decoder) capsulewebhook.Func {
|
||||
func (h *handler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
|
||||
return func(context.Context, admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) OnUpdate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (h *handler) OnUpdate(client client.Client, decoder *admission.Decoder, _ record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
if err := h.validateTenant(ctx, req, client, decoder); err != nil {
|
||||
return admission.Denied(err.Error())
|
||||
|
||||
@@ -46,17 +46,15 @@ func (w *webhook) GetPath() string {
|
||||
|
||||
type handler struct {
|
||||
configuration configuration.Configuration
|
||||
recorder record.EventRecorder
|
||||
}
|
||||
|
||||
func Handler(configuration configuration.Configuration, recorder record.EventRecorder) capsulewebhook.Handler {
|
||||
func Handler(configuration configuration.Configuration) capsulewebhook.Handler {
|
||||
return &handler{
|
||||
configuration: configuration,
|
||||
recorder: recorder,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *handler) OnCreate(clt client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (r *handler) OnCreate(clt client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
ns := &corev1.Namespace{}
|
||||
if err := decoder.Decode(req, ns); err != nil {
|
||||
@@ -79,7 +77,7 @@ func (r *handler) OnCreate(clt client.Client, decoder *admission.Decoder) capsul
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
if e := fmt.Sprintf("%s-%s", tnt.GetName(), ns.GetName()); !strings.HasPrefix(ns.GetName(), fmt.Sprintf("%s-", tnt.GetName())) {
|
||||
r.recorder.Eventf(tnt, corev1.EventTypeWarning, "TenantPrefix", "Namespace %s does not match the expected prefix", ns.GetName())
|
||||
recorder.Eventf(tnt, corev1.EventTypeWarning, "TenantPrefix", "Namespace %s does not match the expected Tenant prefix", ns.GetName())
|
||||
|
||||
return admission.Denied("The namespace doesn't match the tenant prefix, expected " + e)
|
||||
}
|
||||
@@ -88,13 +86,13 @@ func (r *handler) OnCreate(clt client.Client, decoder *admission.Decoder) capsul
|
||||
}
|
||||
}
|
||||
|
||||
func (r *handler) OnDelete(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (r *handler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *handler) OnUpdate(client client.Client, decoder *admission.Decoder) capsulewebhook.Func {
|
||||
func (r *handler) OnUpdate(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package utils
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/client-go/tools/record"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
@@ -43,30 +44,30 @@ func (h handler) isCapsuleUser(req admission.Request) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *handler) OnCreate(client client.Client, decoder *admission.Decoder) webhook.Func {
|
||||
func (h *handler) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) webhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
if !h.isCapsuleUser(req) {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
return h.handler.OnCreate(client, decoder)(ctx, req)
|
||||
return h.handler.OnCreate(client, decoder, recorder)(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) OnDelete(client client.Client, decoder *admission.Decoder) webhook.Func {
|
||||
func (h *handler) OnDelete(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) webhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
if !h.isCapsuleUser(req) {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
return h.handler.OnDelete(client, decoder)(ctx, req)
|
||||
return h.handler.OnDelete(client, decoder, recorder)(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) OnUpdate(client client.Client, decoder *admission.Decoder) webhook.Func {
|
||||
func (h *handler) OnUpdate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) webhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
if !h.isCapsuleUser(req) {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
return h.handler.OnUpdate(client, decoder)(ctx, req)
|
||||
return h.handler.OnUpdate(client, decoder, recorder)(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user