mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-08-19 04:26:45 +00:00
refactor: using separated webhooks for Namespace handling
This commit is contained in:
@@ -30,7 +30,7 @@ import (
|
||||
"github.com/clastix/capsule/pkg/webhook"
|
||||
"github.com/clastix/capsule/pkg/webhook/imagepullpolicy"
|
||||
"github.com/clastix/capsule/pkg/webhook/ingress"
|
||||
"github.com/clastix/capsule/pkg/webhook/namespacequota"
|
||||
namespacewebhook "github.com/clastix/capsule/pkg/webhook/namespace"
|
||||
"github.com/clastix/capsule/pkg/webhook/networkpolicies"
|
||||
"github.com/clastix/capsule/pkg/webhook/ownerreference"
|
||||
"github.com/clastix/capsule/pkg/webhook/podpriority"
|
||||
@@ -148,12 +148,13 @@ func main() {
|
||||
podpriority.Webhook(podpriority.Handler()),
|
||||
services.Webhook(services.Handler()),
|
||||
ownerreference.Webhook(utils.InCapsuleGroups(cfg, ownerreference.Handler(cfg))),
|
||||
namespacequota.Webhook(utils.InCapsuleGroups(cfg, namespacequota.Handler())),
|
||||
namespacewebhook.QuotaWebhook(utils.InCapsuleGroups(cfg, namespacewebhook.QuotaHandler())),
|
||||
namespacewebhook.FreezedWebhook(utils.InCapsuleGroups(cfg, namespacewebhook.FreezeHandler(cfg))),
|
||||
networkpolicies.Webhook(utils.InCapsuleGroups(cfg, networkpolicies.Handler())),
|
||||
tenantprefix.Webhook(utils.InCapsuleGroups(cfg, tenantprefix.Handler(cfg))),
|
||||
tenant.Validating(tenant.ValidatingHandler(cfg)),
|
||||
imagepullpolicy.Webhook(imagepullpolicy.Handler()),
|
||||
tenant.Cordoning(tenant.CordoningHandler()),
|
||||
tenant.Cordoning(tenant.CordoningHandler(cfg)),
|
||||
)
|
||||
if err = webhook.Register(manager, webhooksList...); err != nil {
|
||||
setupLog.Error(err, "unable to setup webhooks")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package namespacequota
|
||||
package namespace
|
||||
|
||||
type namespaceQuotaExceededError struct{}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package namespace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"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"
|
||||
|
||||
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
|
||||
"github.com/clastix/capsule/pkg/configuration"
|
||||
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
|
||||
"github.com/clastix/capsule/pkg/webhook/utils"
|
||||
)
|
||||
|
||||
// +kubebuilder:webhook:path=/validate-v1-namespace-freezed,mutating=false,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="",resources=namespaces,verbs=create;update;delete,versions=v1,name=freezed.namespace.capsule.clastix.io
|
||||
|
||||
type freezedWebhook struct {
|
||||
handler capsulewebhook.Handler
|
||||
}
|
||||
|
||||
func FreezedWebhook(handler capsulewebhook.Handler) capsulewebhook.Webhook {
|
||||
return &freezedWebhook{
|
||||
handler: handler,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *freezedWebhook) GetHandler() capsulewebhook.Handler {
|
||||
return w.handler
|
||||
}
|
||||
|
||||
func (w *freezedWebhook) GetName() string {
|
||||
return "NamespaceFreezed"
|
||||
}
|
||||
|
||||
func (w *freezedWebhook) GetPath() string {
|
||||
return "/validate-v1-namespace-freezed"
|
||||
}
|
||||
|
||||
type freezedHandler struct {
|
||||
configuration configuration.Configuration
|
||||
}
|
||||
|
||||
func FreezeHandler(configuration configuration.Configuration) capsulewebhook.Handler {
|
||||
return &freezedHandler{configuration: configuration}
|
||||
}
|
||||
|
||||
func (r *freezedHandler) 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 {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
for _, objectRef := range ns.ObjectMeta.OwnerReferences {
|
||||
// retrieving the selected Tenant
|
||||
tnt := &capsulev1alpha1.Tenant{}
|
||||
if err := client.Get(ctx, types.NamespacedName{Name: objectRef.Name}, tnt); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
if tnt.IsCordoned() {
|
||||
recorder.Eventf(tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be attached, the current Tenant is freezed", ns.GetName())
|
||||
|
||||
return admission.Denied("the selected Tenant is freezed")
|
||||
}
|
||||
}
|
||||
// creating NS that is not bounded to any Tenant
|
||||
return admission.Allowed("")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *freezedHandler) OnDelete(c client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) admission.Response {
|
||||
tntList := &capsulev1alpha1.TenantList{}
|
||||
if err := c.List(ctx, tntList, client.MatchingFieldsSelector{
|
||||
Selector: fields.OneTermEqualSelector(".status.namespaces", req.Name),
|
||||
}); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
if len(tntList.Items) == 0 {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
tnt := tntList.Items[0]
|
||||
|
||||
if tnt.IsCordoned() && utils.RequestFromOwnerOrSA(tnt, req, r.configuration.UserGroups()) {
|
||||
recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespaced %s cannot be deleted, the current Tenant is freezed", req.Name)
|
||||
|
||||
return admission.Denied("the selected Tenant is freezed")
|
||||
}
|
||||
|
||||
return admission.Allowed("")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *freezedHandler) OnUpdate(c 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 {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
tntList := &capsulev1alpha1.TenantList{}
|
||||
if err := c.List(ctx, tntList, client.MatchingFieldsSelector{
|
||||
Selector: fields.OneTermEqualSelector(".status.namespaces", ns.Name),
|
||||
}); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
if len(tntList.Items) == 0 {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
tnt := tntList.Items[0]
|
||||
|
||||
if tnt.IsCordoned() && utils.RequestFromOwnerOrSA(tnt, req, r.configuration.UserGroups()) {
|
||||
recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespaced %s cannot be updated, the current Tenant is freezed", ns.GetName())
|
||||
|
||||
return admission.Denied("the selected Tenant is freezed")
|
||||
}
|
||||
|
||||
return admission.Allowed("")
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package namespacequota
|
||||
package namespace
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -19,36 +19,36 @@ import (
|
||||
|
||||
// +kubebuilder:webhook:path=/validate-v1-namespace-quota,mutating=false,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="",resources=namespaces,verbs=create,versions=v1,name=quota.namespace.capsule.clastix.io
|
||||
|
||||
type webhook struct {
|
||||
type quotaWebhook struct {
|
||||
handler capsulewebhook.Handler
|
||||
}
|
||||
|
||||
func Webhook(handler capsulewebhook.Handler) capsulewebhook.Webhook {
|
||||
return &webhook{
|
||||
func QuotaWebhook(handler capsulewebhook.Handler) capsulewebhook.Webhook {
|
||||
return "aWebhook{
|
||||
handler: handler,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *webhook) GetHandler() capsulewebhook.Handler {
|
||||
func (w *quotaWebhook) GetHandler() capsulewebhook.Handler {
|
||||
return w.handler
|
||||
}
|
||||
|
||||
func (w *webhook) GetName() string {
|
||||
func (w *quotaWebhook) GetName() string {
|
||||
return "NamespaceQuota"
|
||||
}
|
||||
|
||||
func (w *webhook) GetPath() string {
|
||||
func (w *quotaWebhook) GetPath() string {
|
||||
return "/validate-v1-namespace-quota"
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
type quotaHandler struct {
|
||||
}
|
||||
|
||||
func Handler() capsulewebhook.Handler {
|
||||
return &handler{}
|
||||
func QuotaHandler() capsulewebhook.Handler {
|
||||
return "aHandler{}
|
||||
}
|
||||
|
||||
func (r *handler) OnCreate(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
func (r *quotaHandler) 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 {
|
||||
@@ -78,13 +78,13 @@ func (r *handler) OnCreate(client client.Client, decoder *admission.Decoder, rec
|
||||
}
|
||||
}
|
||||
|
||||
func (r *handler) OnDelete(client client.Client, decoder *admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
func (r *quotaHandler) 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, recorder record.EventRecorder) capsulewebhook.Func {
|
||||
func (r *quotaHandler) 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("")
|
||||
}
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
|
||||
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
|
||||
"github.com/clastix/capsule/pkg/configuration"
|
||||
"github.com/clastix/capsule/pkg/utils"
|
||||
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
|
||||
"github.com/clastix/capsule/pkg/webhook/utils"
|
||||
)
|
||||
|
||||
// +kubebuilder:webhook:path=/tenant-cordoning,mutating=false,sideEffects=None,admissionReviewVersions=v1,failurePolicy=fail,groups="*",resources="*",verbs=create;update;delete,versions="*",name=cordoning.tenant.capsule.clastix.io
|
||||
@@ -47,29 +47,10 @@ type cordoningHandler struct {
|
||||
configuration configuration.Configuration
|
||||
}
|
||||
|
||||
func CordoningHandler() capsulewebhook.Handler {
|
||||
return &cordoningHandler{}
|
||||
}
|
||||
|
||||
func (h *cordoningHandler) requestFromOwnerOrSA(tenant capsulev1alpha1.Tenant, req admission.Request) bool {
|
||||
switch {
|
||||
case tenant.Spec.Owner.Kind == "User" && req.UserInfo.Username == tenant.Spec.Owner.Name:
|
||||
return true
|
||||
case tenant.Spec.Owner.Kind == "Group":
|
||||
groupList := utils.NewUserGroupList(req.UserInfo.Groups)
|
||||
for _, group := range h.configuration.UserGroups() {
|
||||
if groupList.Find(group) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
default:
|
||||
for _, group := range req.UserInfo.Groups {
|
||||
if len(req.Namespace) > 0 && strings.HasPrefix(group, "system:serviceaccounts:"+req.Namespace) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
func CordoningHandler(configuration configuration.Configuration) capsulewebhook.Handler {
|
||||
return &cordoningHandler{
|
||||
configuration: configuration,
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *cordoningHandler) cordonHandler(ctx context.Context, clt client.Client, req admission.Request, recorder record.EventRecorder) admission.Response {
|
||||
@@ -88,7 +69,7 @@ func (h *cordoningHandler) cordonHandler(ctx context.Context, clt client.Client,
|
||||
tnt := tntList.Items[0]
|
||||
|
||||
if tnt.IsCordoned() {
|
||||
if h.requestFromOwnerOrSA(tnt, req) {
|
||||
if utils.RequestFromOwnerOrSA(tnt, req, h.configuration.UserGroups()) {
|
||||
recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantFreezed", "%s %s/%s cannot be %sd, current Tenant is freezed", req.Kind.String(), req.Namespace, req.Name, strings.ToLower(string(req.Operation)))
|
||||
|
||||
return admission.Denied(fmt.Sprintf("tenant %s is freezed: please, reach out to the system administrator", tnt.GetName()))
|
||||
|
||||
Reference in New Issue
Block a user