pkg/providers/gateway/validator.go (228 lines of code) (raw):
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package gateway
import (
"context"
"fmt"
"go.uber.org/zap"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
"github.com/apache/apisix-ingress-controller/pkg/log"
"github.com/apache/apisix-ingress-controller/pkg/providers/gateway/types"
)
type Validator struct {
provider *Provider
}
func newValidator(p *Provider) *Validator {
return &Validator{
provider: p,
}
}
type commonRoute struct {
routeNamespace string
parentRefs []gatewayv1beta1.ParentReference
routeProtocol gatewayv1beta1.ProtocolType
routeHostnames []gatewayv1beta1.Hostname
routeGroupKind gatewayv1beta1.RouteGroupKind
}
func (r *commonRoute) hasParentRefs() bool {
return len(r.parentRefs) != 0
}
func (r *commonRoute) isHTTPProtocol() bool {
return r.routeProtocol == gatewayv1beta1.HTTPProtocolType
}
func (r *commonRoute) isHTTPSProtocol() bool {
return r.routeProtocol == gatewayv1beta1.HTTPSProtocolType
}
func ConvertParentRefsToV1beta1(parentRefs []gatewayv1alpha2.ParentReference) []gatewayv1beta1.ParentReference {
v1beta1ParentRefs := make([]gatewayv1beta1.ParentReference, len(parentRefs))
for i, p := range parentRefs {
v1beta1ParentRefs[i] = gatewayv1beta1.ParentReference{
Group: (*gatewayv1beta1.Group)(p.Group),
Kind: (*gatewayv1beta1.Kind)(p.Kind),
Namespace: (*gatewayv1beta1.Namespace)(p.Namespace),
Name: (gatewayv1beta1.ObjectName)(p.Name),
SectionName: (*gatewayv1beta1.SectionName)(p.SectionName),
Port: (*gatewayv1beta1.PortNumber)(p.Port),
}
}
return v1beta1ParentRefs
}
func ConvertHostnamesToV1beta1(hostnames []gatewayv1alpha2.Hostname) []gatewayv1beta1.Hostname {
v1beta1Hostnames := make([]gatewayv1beta1.Hostname, len(hostnames))
for i, h := range hostnames {
v1beta1Hostnames[i] = (gatewayv1beta1.Hostname)(h)
}
return v1beta1Hostnames
}
func parseToCommentRoute(route any) (*commonRoute, error) {
r := new(commonRoute)
group := gatewayv1beta1.Group(gatewayv1beta1.GroupName)
switch route := route.(type) {
case *gatewayv1beta1.HTTPRoute:
r.routeNamespace = route.Namespace
r.parentRefs = route.Spec.ParentRefs
r.routeProtocol = gatewayv1beta1.HTTPProtocolType
r.routeHostnames = route.Spec.Hostnames
r.routeGroupKind = gatewayv1beta1.RouteGroupKind{
Group: &group,
Kind: types.KindHTTPRoute,
}
case *gatewayv1alpha2.TLSRoute:
r.routeNamespace = route.Namespace
r.parentRefs = ConvertParentRefsToV1beta1(route.Spec.ParentRefs)
r.routeProtocol = gatewayv1beta1.HTTPSProtocolType
r.routeHostnames = ConvertHostnamesToV1beta1(route.Spec.Hostnames)
r.routeGroupKind = gatewayv1beta1.RouteGroupKind{
Group: &group,
Kind: types.KindTLSRoute,
}
case *gatewayv1alpha2.TCPRoute:
r.routeNamespace = route.Namespace
r.parentRefs = ConvertParentRefsToV1beta1(route.Spec.ParentRefs)
r.routeProtocol = gatewayv1beta1.TCPProtocolType
r.routeGroupKind = gatewayv1beta1.RouteGroupKind{
Group: &group,
Kind: types.KindTCPRoute,
}
case *gatewayv1alpha2.UDPRoute:
r.routeNamespace = route.Namespace
r.parentRefs = ConvertParentRefsToV1beta1(route.Spec.ParentRefs)
r.routeProtocol = gatewayv1beta1.UDPProtocolType
r.routeGroupKind = gatewayv1beta1.RouteGroupKind{
Group: &group,
Kind: types.KindUDPRoute,
}
default:
return nil, fmt.Errorf("validator unsupported Route %T", r)
}
return r, nil
}
func (v *Validator) getListenersConf(r *commonRoute, parentRef gatewayv1beta1.ParentReference) ([]*types.ListenerConf, error) {
var name, kind, namespace, sectionName string
name = string(parentRef.Name)
if parentRef.Kind != nil {
kind = string(*parentRef.Kind)
} else {
kind = "Gateway"
}
if parentRef.Namespace != nil {
namespace = string(*parentRef.Namespace)
}
if parentRef.SectionName != nil {
sectionName = string(*parentRef.SectionName)
}
// The only kind of parent resource with "Core" support is Gateway.
if kind != "Gateway" {
return nil, fmt.Errorf("ParentRef.Kind support Gateway only")
}
// When namespace unspecified this refers to the local namespace of the Route.
if namespace == "" {
namespace = r.routeNamespace
}
listeners := make([]*types.ListenerConf, 0)
if sectionName != "" {
listenerConf, err := v.provider.FindListener(namespace, name, sectionName)
if err != nil {
return nil, err
}
listeners = append(listeners, listenerConf)
} else {
_listeners, err := v.provider.QueryListeners(namespace, name)
if err != nil {
return nil, err
}
for _, listenerConf := range _listeners {
listeners = append(listeners, listenerConf)
}
}
if len(listeners) == 0 {
log.Warnw("can't find ListenerConf by ParentRef",
zap.Any("ParentRef", parentRef),
)
return nil, fmt.Errorf("can't find Listener by ParentRef")
}
return listeners, nil
}
func (v *Validator) validateParentRef(r *commonRoute) ([]*types.ListenerConf, error) {
var matchedListeners []*types.ListenerConf
for _, parentRef := range r.parentRefs {
listeners, err := v.getListenersConf(r, parentRef)
if err != nil {
return nil, err
}
// filter listener by ParentRef
for _, listenerConf := range listeners {
if listenerConf.Protocol != r.routeProtocol {
continue
}
if !listenerConf.IsAllowedKind(r.routeGroupKind) {
// TODO: set the “ResolvedRefs” condition to False for this Listener with the “InvalidRouteKinds” reason.
continue
}
if r.isHTTPProtocol() || r.isHTTPSProtocol() {
if listenerConf.HasHostname() && len(r.routeHostnames) != 0 {
if !listenerConf.IsHostnameMatch(r.routeHostnames) {
continue
}
}
}
// match listener by AllowRoute.Namespaces
switch *listenerConf.RouteNamespace.From {
case gatewayv1beta1.NamespacesFromSame:
if r.routeNamespace != listenerConf.Namespace {
continue
}
case gatewayv1beta1.NamespacesFromSelector:
// get listener namespace with selector labeled namespace
selector, err := metav1.LabelSelectorAsSelector(listenerConf.RouteNamespace.Selector)
if err != nil {
log.Errorw("convert Selector failed",
zap.Error(err),
zap.Any("Object", listenerConf.RouteNamespace.Selector),
)
return nil, err
}
allowedNamespaces := &corev1.NamespaceList{}
err = v.provider.runtimeClient.List(
context.TODO(), allowedNamespaces,
&runtimeclient.ListOptions{LabelSelector: selector},
)
if err != nil {
log.Errorw("list parent namespace failed",
zap.Error(err),
zap.Any("Object", *listenerConf.RouteNamespace.From),
)
return nil, err
}
namespaceMatched := false
for _, allowedNamespace := range allowedNamespaces.Items {
if string(allowedNamespace.Name) == r.routeNamespace {
namespaceMatched = true
break
}
}
if !namespaceMatched {
continue
}
}
matchedListeners = append(matchedListeners, listenerConf)
}
}
if len(matchedListeners) == 0 {
log.Errorw("no listeners referenced by ParentRefs",
zap.Any("listeners", v.provider.listeners),
zap.Any("route", r),
)
return nil, fmt.Errorf("no listeners referenced by ParentRefs")
}
return matchedListeners, nil
}
// ValidateCommonRoute only checks CommonRoute and ParentRef.
// route argument support HTTPRoute TLSRoute UDPRoute TLSRoute for now.
func (v *Validator) ValidateCommonRoute(route any) error {
r, err := parseToCommentRoute(route)
if err != nil {
return err
}
if r.hasParentRefs() {
_, err = v.validateParentRef(r)
if err != nil {
return err
}
}
return nil
}