Skip to content

add validating webhook for ingress_class_params #1902

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

Merged
merged 1 commit into from
Mar 23, 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
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func main() {
corewebhook.NewPodMutator(podReadinessGateInjector).SetupWithManager(mgr)
elbv2webhook.NewTargetGroupBindingMutator(cloud.ELBV2(), ctrl.Log).SetupWithManager(mgr)
elbv2webhook.NewTargetGroupBindingValidator(ctrl.Log).SetupWithManager(mgr)
networkingwebhook.NewIngressValidator(controllerCFG.IngressConfig, ctrl.Log).SetupWithManager(mgr)
networkingwebhook.NewIngressValidator(mgr.GetClient(), controllerCFG.IngressConfig, ctrl.Log).SetupWithManager(mgr)
//+kubebuilder:scaffold:builder

stopChan := ctrl.SetupSignalHandler()
Expand Down
14 changes: 14 additions & 0 deletions pkg/ingress/class.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ingress

import (
networking "k8s.io/api/networking/v1beta1"
elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1"
)

// ClassConfiguration contains configurations for IngressClass
type ClassConfiguration struct {
// The IngressClass for Ingress if any.
IngClass *networking.IngressClass
// The IngressClassParams for Ingress if any.
IngClassParams *elbv2api.IngressClassParams
}
111 changes: 111 additions & 0 deletions pkg/ingress/class_loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package ingress

import (
"context"
"fmt"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
networking "k8s.io/api/networking/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1"
"sigs.k8s.io/aws-load-balancer-controller/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
// the controller name used in IngressClass for ALB.
ingressClassControllerALB = "ingress.k8s.aws/alb"
// the Kind for IngressClassParams CRD.
ingressClassParamsKind = "IngressClassParams"
)

// ErrInvalidIngressClass is an sentinel error that represents the IngressClass configuration for Ingress is invalid.
var ErrInvalidIngressClass = errors.New("invalid ingress class")

// ClassLoader loads IngressClass configurations for Ingress.
type ClassLoader interface {
Load(ctx context.Context, ing *networking.Ingress) (ClassConfiguration, error)
}

// NewDefaultClassLoader constructs new defaultClassLoader instance.
func NewDefaultClassLoader(client client.Client) *defaultClassLoader {
return &defaultClassLoader{
client: client,
}
}

// default implementation for ClassLoader
type defaultClassLoader struct {
client client.Client
}

func (l *defaultClassLoader) Load(ctx context.Context, ing *networking.Ingress) (ClassConfiguration, error) {
if ing.Spec.IngressClassName == nil {
return ClassConfiguration{}, nil
}

ingClassKey := types.NamespacedName{Name: *ing.Spec.IngressClassName}
ingClass := &networking.IngressClass{}
if err := l.client.Get(ctx, ingClassKey, ingClass); err != nil {
if apierrors.IsNotFound(err) {
return ClassConfiguration{}, fmt.Errorf("%w: %v", ErrInvalidIngressClass, err.Error())
}
return ClassConfiguration{}, err
}
if ingClass.Spec.Controller != ingressClassControllerALB || ingClass.Spec.Parameters == nil {
return ClassConfiguration{
IngClass: ingClass,
}, nil
}

if ingClass.Spec.Parameters.APIGroup == nil ||
(*ingClass.Spec.Parameters.APIGroup) != elbv2api.GroupVersion.Group ||
ingClass.Spec.Parameters.Kind != ingressClassParamsKind {
return ClassConfiguration{}, fmt.Errorf("%w: IngressClass %v references unknown parameters", ErrInvalidIngressClass, ingClass.Name)
}
ingClassParamsKey := types.NamespacedName{Name: ingClass.Spec.Parameters.Name}
ingClassParams := &elbv2api.IngressClassParams{}
if err := l.client.Get(ctx, ingClassParamsKey, ingClassParams); err != nil {
if apierrors.IsNotFound(err) {
return ClassConfiguration{}, fmt.Errorf("%w: %v", ErrInvalidIngressClass, err.Error())
}
return ClassConfiguration{}, err
}
if err := l.validateIngressClassParamsNamespaceRestriction(ctx, ing, ingClassParams); err != nil {
return ClassConfiguration{}, fmt.Errorf("%w: %v", ErrInvalidIngressClass, err.Error())
}

return ClassConfiguration{
IngClass: ingClass,
IngClassParams: ingClassParams,
}, nil
}

func (l *defaultClassLoader) validateIngressClassParamsNamespaceRestriction(ctx context.Context, ing *networking.Ingress, ingClassParams *elbv2api.IngressClassParams) error {
// when namespaceSelector is empty, it matches every namespace
if ingClassParams.Spec.NamespaceSelector == nil {
return nil
}

ingNamespace := ing.Namespace
// see https://github.com/kubernetes/kubernetes/issues/88282 and https://github.com/kubernetes/kubernetes/issues/76680
if admissionReq := webhook.ContextGetAdmissionRequest(ctx); admissionReq != nil {
ingNamespace = admissionReq.Namespace
}
ingNSKey := types.NamespacedName{Name: ingNamespace}
ingNS := &corev1.Namespace{}
if err := l.client.Get(ctx, ingNSKey, ingNS); err != nil {
return err
}
selector, err := metav1.LabelSelectorAsSelector(ingClassParams.Spec.NamespaceSelector)
if err != nil {
return err
}
if !selector.Matches(labels.Set(ingNS.Labels)) {
return errors.Errorf("namespaceSelector of IngressClassParams %v mismatch", ingClassParams.Name)
}
return nil
}
Loading