refactor: hostname collision is now managed at Tenant level

This commit is contained in:
Dario Tranchitella
2021-08-12 19:30:27 +02:00
parent 07daffd669
commit df08c9e63e
8 changed files with 7 additions and 150 deletions
@@ -18,15 +18,6 @@ type CapsuleConfigurationSpec struct {
ForceTenantPrefix bool `json:"forceTenantPrefix,omitempty"`
// Disallow creation of namespaces, whose name matches this regexp
ProtectedNamespaceRegexpString string `json:"protectedNamespaceRegex,omitempty"`
// When defining the exact match for allowed Ingress hostnames at Tenant level, a collision is not allowed.
// Toggling this, Capsule will not check if a hostname collision is in place, allowing the creation of
// two or more Tenant resources although sharing the same allowed hostname(s).
//
// The JSON path of the resource is: /spec/ingressHostnames/allowed
AllowTenantIngressHostnamesCollision bool `json:"allowTenantIngressHostnamesCollision,omitempty"`
// Allow the collision of Ingress resource hostnames across all the Tenants.
// +kubebuilder:default=true
AllowIngressHostnameCollision bool `json:"allowIngressHostnameCollision,omitempty"`
}
// +kubebuilder:object:root=true
+2 -2
View File
@@ -154,7 +154,7 @@ func main() {
route.PVC(pvc.Handler()),
route.Service(service.Handler()),
route.NetworkPolicy(utils.InCapsuleGroups(cfg, networkpolicy.Handler())),
route.Tenant(tenant.NameHandler(), tenant.IngressClassRegexHandler(), tenant.StorageClassRegexHandler(), tenant.ContainerRegistryRegexHandler(), tenant.HostnameRegexHandler(), tenant.HostnamesCollisionHandler(cfg), tenant.FreezedEmitter()),
route.Tenant(tenant.NameHandler(), tenant.IngressClassRegexHandler(), tenant.StorageClassRegexHandler(), tenant.ContainerRegistryRegexHandler(), tenant.HostnameRegexHandler(), tenant.FreezedEmitter()),
route.OwnerReference(utils.InCapsuleGroups(cfg, ownerreference.Handler(cfg))),
route.Cordoning(tenant.CordoningHandler(cfg)),
)
@@ -224,7 +224,7 @@ func main() {
ctx := ctrl.SetupSignalHandler()
if err = indexer.AddToManager(manager, ctx); err != nil {
if err = indexer.AddToManager(ctx, manager); err != nil {
setupLog.Error(err, "unable to setup indexers")
os.Exit(1)
}
+3 -13
View File
@@ -29,11 +29,9 @@ func NewCapsuleConfiguration(client client.Client, name string) Configuration {
if machineryerr.IsNotFound(err) {
return &capsulev1alpha1.CapsuleConfiguration{
Spec: capsulev1alpha1.CapsuleConfigurationSpec{
UserGroups: []string{"capsule.clastix.io"},
ForceTenantPrefix: false,
ProtectedNamespaceRegexpString: "",
AllowTenantIngressHostnamesCollision: false,
AllowIngressHostnameCollision: true,
UserGroups: []string{"capsule.clastix.io"},
ForceTenantPrefix: false,
ProtectedNamespaceRegexpString: "",
},
}
}
@@ -44,14 +42,6 @@ func NewCapsuleConfiguration(client client.Client, name string) Configuration {
}}
}
func (c capsuleConfiguration) AllowIngressHostnameCollision() bool {
return c.retrievalFn().Spec.AllowIngressHostnameCollision
}
func (c capsuleConfiguration) AllowTenantIngressHostnamesCollision() bool {
return c.retrievalFn().Spec.AllowTenantIngressHostnamesCollision
}
func (c capsuleConfiguration) ProtectedNamespaceRegexp() (*regexp.Regexp, error) {
expr := c.retrievalFn().Spec.ProtectedNamespaceRegexpString
if len(expr) == 0 {
-2
View File
@@ -8,8 +8,6 @@ import (
)
type Configuration interface {
AllowIngressHostnameCollision() bool
AllowTenantIngressHostnamesCollision() bool
ProtectedNamespaceRegexp() (*regexp.Regexp, error)
ForceTenantPrefix() bool
UserGroups() []string
+2 -3
View File
@@ -24,9 +24,8 @@ type CustomIndexer interface {
Func() client.IndexerFunc
}
func AddToManager(m manager.Manager, ctx context.Context) error {
func AddToManager(ctx context.Context, mgr manager.Manager) error {
indexers := append([]CustomIndexer{},
tenant.IngressHostnames{},
tenant.NamespacesReference{},
tenant.OwnerReference{},
namespace.OwnerReference{},
@@ -44,7 +43,7 @@ func AddToManager(m manager.Manager, ctx context.Context) error {
}
for _, f := range indexers {
if err := m.GetFieldIndexer().IndexField(ctx, f.Object(), f.Field(), f.Func()); err != nil {
if err := mgr.GetFieldIndexer().IndexField(ctx, f.Object(), f.Field(), f.Func()); err != nil {
return err
}
}
-31
View File
@@ -1,31 +0,0 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package tenant
import (
"sigs.k8s.io/controller-runtime/pkg/client"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
)
type IngressHostnames struct {
}
func (IngressHostnames) Object() client.Object {
return &capsulev1beta1.Tenant{}
}
func (IngressHostnames) Field() string {
return ".spec.ingressHostnames"
}
func (IngressHostnames) Func() client.IndexerFunc {
return func(object client.Object) (out []string) {
tenant := object.(*capsulev1beta1.Tenant)
if tenant.Spec.IngressOptions.AllowedHostnames != nil {
out = append(out, tenant.Spec.IngressOptions.AllowedHostnames.Exact...)
}
return
}
}
@@ -111,10 +111,6 @@ func (r *collision) OnDelete(client.Client, *admission.Decoder, record.EventReco
}
func (r *collision) validateCollision(ctx context.Context, clt client.Client, ing Ingress, scope capsulev1beta1.HostnameCollisionScope) error {
if r.configuration.AllowIngressHostnameCollision() {
return nil
}
for hostname, paths := range ing.HostnamePathsPairs() {
for path := range paths {
var ingressObjList client.ObjectList
-86
View File
@@ -1,86 +0,0 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package tenant
import (
"context"
"fmt"
"net/http"
"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"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
"github.com/clastix/capsule/pkg/configuration"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"github.com/clastix/capsule/pkg/webhook/utils"
)
type hostnamesCollisionHandler struct {
configuration configuration.Configuration
}
func HostnamesCollisionHandler(configuration configuration.Configuration) capsulewebhook.Handler {
return &hostnamesCollisionHandler{configuration: configuration}
}
func (h *hostnamesCollisionHandler) validateTenant(ctx context.Context, req admission.Request, clt client.Client, decoder *admission.Decoder) *admission.Response {
tenant := &capsulev1beta1.Tenant{}
if err := decoder.Decode(req, tenant); err != nil {
return utils.ErroredResponse(err)
}
if !h.configuration.AllowTenantIngressHostnamesCollision() && tenant.Spec.IngressOptions.AllowedHostnames != nil && len(tenant.Spec.IngressOptions.AllowedHostnames.Exact) > 0 {
for _, h := range tenant.Spec.IngressOptions.AllowedHostnames.Exact {
tntList := &capsulev1beta1.TenantList{}
if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{
Selector: fields.OneTermEqualSelector(".spec.ingressHostnames", h),
}); err != nil {
response := admission.Errored(http.StatusInternalServerError, fmt.Errorf("cannot retrieve Tenant list using .spec.ingressHostnames field selector: %w", err))
return &response
}
switch {
case len(tntList.Items) == 1 && tntList.Items[0].GetName() == tenant.GetName():
continue
case len(tntList.Items) > 0:
response := admission.Denied(fmt.Sprintf("the allowed hostname %s is already used by the Tenant %s, cannot proceed", h, tntList.Items[0].GetName()))
return &response
default:
continue
}
}
}
return nil
}
func (h *hostnamesCollisionHandler) OnCreate(client client.Client, decoder *admission.Decoder, _ record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
if response := h.validateTenant(ctx, req, client, decoder); response != nil {
return response
}
return nil
}
}
func (h *hostnamesCollisionHandler) OnDelete(client.Client, *admission.Decoder, record.EventRecorder) capsulewebhook.Func {
return func(context.Context, admission.Request) *admission.Response {
return nil
}
}
func (h *hostnamesCollisionHandler) OnUpdate(client client.Client, decoder *admission.Decoder, _ record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
if response := h.validateTenant(ctx, req, client, decoder); response != nil {
return response
}
return nil
}
}