diff --git a/api/v1alpha1/backendtrafficpolicy_types.go b/api/v1alpha1/backendtrafficpolicy_types.go index b7ebc5fa..90bc8860 100644 --- a/api/v1alpha1/backendtrafficpolicy_types.go +++ b/api/v1alpha1/backendtrafficpolicy_types.go @@ -22,8 +22,8 @@ type BackendTrafficPolicy struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // BackendTrafficPolicySpec defines traffic handling policies applied to backend services, - // such as load balancing strategy, connection settings, and failover behavior. + // BackendTrafficPolicySpec defines traffic handling policies applied to backend services, + // such as load balancing strategy, connection settings, and failover behavior. Spec BackendTrafficPolicySpec `json:"spec,omitempty"` Status PolicyStatus `json:"status,omitempty"` } diff --git a/api/v1alpha1/consumer_types.go b/api/v1alpha1/consumer_types.go index 108c8f99..7e75f359 100644 --- a/api/v1alpha1/consumer_types.go +++ b/api/v1alpha1/consumer_types.go @@ -23,7 +23,7 @@ type Consumer struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // ConsumerSpec defines the configuration for a consumer, including consumer name, + // ConsumerSpec defines the configuration for a consumer, including consumer name, // authentication credentials, and plugin settings. Spec ConsumerSpec `json:"spec,omitempty"` Status Status `json:"status,omitempty"` @@ -31,11 +31,11 @@ type Consumer struct { type ConsumerSpec struct { // GatewayRef specifies the gateway details. - GatewayRef GatewayRef `json:"gatewayRef,omitempty"` + GatewayRef GatewayRef `json:"gatewayRef,omitempty"` // Credentials specifies the credential details of a consumer. Credentials []Credential `json:"credentials,omitempty"` // Plugins define the plugins associated with a consumer. - Plugins []Plugin `json:"plugins,omitempty"` + Plugins []Plugin `json:"plugins,omitempty"` } type GatewayRef struct { @@ -48,7 +48,7 @@ type GatewayRef struct { Kind *string `json:"kind,omitempty"` // Group is the API group the resource belongs to. Default is `gateway.networking.k8s.io`. // +kubebuilder:default=gateway.networking.k8s.io - Group *string `json:"group,omitempty"` + Group *string `json:"group,omitempty"` // Namespace is namespace of the resource. Namespace *string `json:"namespace,omitempty"` } @@ -58,18 +58,18 @@ type Credential struct { // +kubebuilder:validation:Enum=jwt-auth;basic-auth;key-auth;hmac-auth; // Type specifies the type of authentication to configure credentials for. // Can be one of `jwt-auth`, `basic-auth`, `key-auth`, or `hmac-auth`. - Type string `json:"type"` + Type string `json:"type"` // Config specifies the credential details for authentication. - Config apiextensionsv1.JSON `json:"config,omitempty"` + Config apiextensionsv1.JSON `json:"config,omitempty"` // SecretRef references to the Secret that contains the credentials. - SecretRef *SecretReference `json:"secretRef,omitempty"` + SecretRef *SecretReference `json:"secretRef,omitempty"` // Name is the name of the credential. - Name string `json:"name,omitempty"` + Name string `json:"name,omitempty"` } type SecretReference struct { // Name is the name of the secret. - Name string `json:"name"` + Name string `json:"name"` // Namespace is the namespace of the secret. Namespace *string `json:"namespace,omitempty"` } diff --git a/api/v1alpha1/gatewayproxy_types.go b/api/v1alpha1/gatewayproxy_types.go index cafd596f..871d43bd 100644 --- a/api/v1alpha1/gatewayproxy_types.go +++ b/api/v1alpha1/gatewayproxy_types.go @@ -27,14 +27,14 @@ type GatewayProxySpec struct { // PublishService specifies the LoadBalancer-type Service whose external address the controller uses to // update the status of Ingress resources. - PublishService string `json:"publishService,omitempty"` + PublishService string `json:"publishService,omitempty"` // StatusAddress specifies the external IP addresses that the controller uses to populate the status field // of GatewayProxy or Ingress resources for developers to access. - StatusAddress []string `json:"statusAddress,omitempty"` + StatusAddress []string `json:"statusAddress,omitempty"` // Provider configures the provider details. - Provider *GatewayProxyProvider `json:"provider,omitempty"` + Provider *GatewayProxyProvider `json:"provider,omitempty"` // Plugins configure global plugins. - Plugins []GatewayProxyPlugin `json:"plugins,omitempty"` + Plugins []GatewayProxyPlugin `json:"plugins,omitempty"` // PluginMetadata configures common configurations shared by all plugin instances of the same name. PluginMetadata map[string]apiextensionsv1.JSON `json:"pluginMetadata,omitempty"` } @@ -132,8 +132,8 @@ type GatewayProxy struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // GatewayProxySpec defines the desired state and configuration of a GatewayProxy, - // including networking settings, global plugins, and plugin metadata. + // GatewayProxySpec defines the desired state and configuration of a GatewayProxy, + // including networking settings, global plugins, and plugin metadata. Spec GatewayProxySpec `json:"spec,omitempty"` } @@ -148,11 +148,11 @@ type GatewayProxyList struct { // GatewayProxyPlugin contains plugin configurations. type GatewayProxyPlugin struct { // Name is the name of the plugin. - Name string `json:"name,omitempty"` - // Enabled defines whether the plugin is enabled. - Enabled bool `json:"enabled,omitempty"` + Name string `json:"name,omitempty"` + // Enabled defines whether the plugin is enabled. + Enabled bool `json:"enabled,omitempty"` // Config defines the plugin's configuration details. - Config apiextensionsv1.JSON `json:"config,omitempty"` + Config apiextensionsv1.JSON `json:"config,omitempty"` } func init() { diff --git a/api/v1alpha1/httproutepolicy_types.go b/api/v1alpha1/httproutepolicy_types.go index 5cce9a36..82c37bdd 100644 --- a/api/v1alpha1/httproutepolicy_types.go +++ b/api/v1alpha1/httproutepolicy_types.go @@ -25,9 +25,9 @@ type HTTPRoutePolicySpec struct { // +kubebuilder:validation:MaxItems=16 TargetRefs []gatewayv1alpha2.LocalPolicyTargetReferenceWithSectionName `json:"targetRefs"` // Priority sets the priority for route. A higher value sets a higher priority in route matching. - Priority *int64 `json:"priority,omitempty" yaml:"priority,omitempty"` + Priority *int64 `json:"priority,omitempty" yaml:"priority,omitempty"` // Vars sets the request matching conditions. - Vars []apiextensionsv1.JSON `json:"vars,omitempty" yaml:"vars,omitempty"` + Vars []apiextensionsv1.JSON `json:"vars,omitempty" yaml:"vars,omitempty"` } // +kubebuilder:object:root=true diff --git a/internal/controller/gateway_controller.go b/internal/controller/gateway_controller.go index 332089ae..acf3e9a8 100644 --- a/internal/controller/gateway_controller.go +++ b/internal/controller/gateway_controller.go @@ -18,6 +18,7 @@ import ( "fmt" "reflect" + "github.com/api7/gopkg/pkg/log" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -26,15 +27,12 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" "sigs.k8s.io/gateway-api/apis/v1beta1" - "github.com/api7/gopkg/pkg/log" - "github.com/apache/apisix-ingress-controller/api/v1alpha1" "github.com/apache/apisix-ingress-controller/internal/controller/indexer" "github.com/apache/apisix-ingress-controller/internal/provider" @@ -51,7 +49,7 @@ type GatewayReconciler struct { //nolint:revive // SetupWithManager sets up the controller with the Manager. func (r *GatewayReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). + bdr := ctrl.NewControllerManagedBy(mgr). For( &gatewayv1.Gateway{}, builder.WithPredicates( @@ -85,25 +83,16 @@ func (r *GatewayReconciler) SetupWithManager(mgr ctrl.Manager) error { Watches( &corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(r.listGatewaysForSecret), - ). - Watches(&v1beta1.ReferenceGrant{}, + ) + + if GetEnableReferenceGrant() { + bdr.Watches(&v1beta1.ReferenceGrant{}, handler.EnqueueRequestsFromMapFunc(r.listReferenceGrantsForGateway), - builder.WithPredicates(predicate.Funcs{ - CreateFunc: func(e event.CreateEvent) bool { - return referenceGrantHasGatewayFrom(e.Object) - }, - UpdateFunc: func(e event.UpdateEvent) bool { - return referenceGrantHasGatewayFrom(e.ObjectOld) || referenceGrantHasGatewayFrom(e.ObjectNew) - }, - DeleteFunc: func(e event.DeleteEvent) bool { - return referenceGrantHasGatewayFrom(e.Object) - }, - GenericFunc: func(e event.GenericEvent) bool { - return referenceGrantHasGatewayFrom(e.Object) - }, - }), - ). - Complete(r) + builder.WithPredicates(referenceGrantPredicates(KindGateway)), + ) + } + + return bdr.Complete(r) } func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -184,14 +173,9 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } } - var referenceGrantList v1beta1.ReferenceGrantList - if err := r.List(ctx, &referenceGrantList); err != nil { - r.Log.Error(err, "failed to list reference grants") - return ctrl.Result{}, err - } - listenerStatuses, err := getListenerStatus(ctx, r.Client, gateway, referenceGrantList.Items) + listenerStatuses, err := getListenerStatus(ctx, r.Client, gateway) if err != nil { - r.Log.Error(err, "failed to get listener status", "gateway", types.NamespacedName{Namespace: gateway.GetNamespace(), Name: gateway.GetName()}) + r.Log.Error(err, "failed to get listener status", "gateway", req.NamespacedName) return ctrl.Result{}, err } @@ -391,12 +375,12 @@ func (r *GatewayReconciler) listReferenceGrantsForGateway(ctx context.Context, o } for _, gateway := range gatewayList.Items { + gw := v1beta1.ReferenceGrantFrom{ + Group: gatewayv1.GroupName, + Kind: KindGateway, + Namespace: v1beta1.Namespace(gateway.GetNamespace()), + } for _, from := range grant.Spec.From { - gw := v1beta1.ReferenceGrantFrom{ - Group: gatewayv1.GroupName, - Kind: KindGateway, - Namespace: v1beta1.Namespace(gateway.GetNamespace()), - } if from == gw { requests = append(requests, reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -410,19 +394,6 @@ func (r *GatewayReconciler) listReferenceGrantsForGateway(ctx context.Context, o return requests } -func referenceGrantHasGatewayFrom(obj client.Object) bool { - grant, ok := obj.(*v1beta1.ReferenceGrant) - if !ok { - return false - } - for _, from := range grant.Spec.From { - if from.Kind == KindGateway && string(from.Group) == gatewayv1.GroupName { - return true - } - } - return false -} - func (r *GatewayReconciler) processInfrastructure(tctx *provider.TranslateContext, gateway *gatewayv1.Gateway) error { rk := provider.ResourceKind{ Kind: gateway.Kind, diff --git a/internal/controller/httproute_controller.go b/internal/controller/httproute_controller.go index 9bfdd5dd..ab6814c6 100644 --- a/internal/controller/httproute_controller.go +++ b/internal/controller/httproute_controller.go @@ -13,9 +13,9 @@ package controller import ( + "cmp" "context" "fmt" - "strings" "github.com/api7/gopkg/pkg/log" "github.com/go-logr/logr" @@ -38,6 +38,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" "sigs.k8s.io/gateway-api/apis/v1alpha2" + "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/apache/apisix-ingress-controller/api/v1alpha1" "github.com/apache/apisix-ingress-controller/internal/controller/indexer" @@ -60,7 +61,7 @@ type HTTPRouteReconciler struct { //nolint:revive func (r *HTTPRouteReconciler) SetupWithManager(mgr ctrl.Manager) error { r.genericEvent = make(chan event.GenericEvent, 100) - return ctrl.NewControllerManagedBy(mgr). + bdr := ctrl.NewControllerManagedBy(mgr). For(&gatewayv1.HTTPRoute{}). WithEventFilter(predicate.GenerationChangedPredicate{}). Watches(&discoveryv1.EndpointSlice{}, @@ -106,8 +107,16 @@ func (r *HTTPRouteReconciler) SetupWithManager(mgr ctrl.Manager) error { r.genericEvent, handler.EnqueueRequestsFromMapFunc(r.listHTTPRouteForGenericEvent), ), - ). - Complete(r) + ) + + if GetEnableReferenceGrant() { + bdr.Watches(&v1beta1.ReferenceGrant{}, + handler.EnqueueRequestsFromMapFunc(r.listHTTPRoutesForReferenceGrant), + builder.WithPredicates(referenceGrantPredicates(KindHTTPRoute)), + ) + } + + return bdr.Complete(r) } func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -169,11 +178,12 @@ func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } } - var httpRouteErr error + var backendRefErr error if err := r.processHTTPRoute(tctx, hr); err != nil { - httpRouteErr = err // When encountering a backend reference error, it should not affect the acceptance status - if !IsInvalidKindError(err) { + if IsSomeReasonError(err, gatewayv1.RouteReasonInvalidKind) { + backendRefErr = err + } else { acceptStatus.status = false acceptStatus.msg = err.Error() } @@ -184,16 +194,12 @@ func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( acceptStatus.msg = err.Error() } - // Store the backend reference error for later use - var backendRefErr error - if err := r.processHTTPRouteBackendRefs(tctx); err != nil { + // Store the backend reference error for later use. + // If the backend reference error is because of an invalid kind, use this error first + if err := r.processHTTPRouteBackendRefs(tctx, req.NamespacedName); err != nil && backendRefErr == nil { backendRefErr = err } - // If the backend reference error is because of an invalid kind, use this error first - if httpRouteErr != nil && IsInvalidKindError(httpRouteErr) { - backendRefErr = httpRouteErr - } ProcessBackendTrafficPolicy(r.Client, r.Log, tctx) filteredHTTPRoute, err := filterHostnames(gateways, hr.DeepCopy()) @@ -419,14 +425,19 @@ func (r *HTTPRouteReconciler) listHTTPRouteForGenericEvent(ctx context.Context, } } -func (r *HTTPRouteReconciler) processHTTPRouteBackendRefs(tctx *provider.TranslateContext) error { +func (r *HTTPRouteReconciler) processHTTPRouteBackendRefs(tctx *provider.TranslateContext, hrNN types.NamespacedName) error { var terr error for _, backend := range tctx.BackendRefs { - namespace := string(*backend.Namespace) - name := string(backend.Name) + targetNN := types.NamespacedName{ + Namespace: hrNN.Namespace, + Name: string(backend.Name), + } + if backend.Namespace != nil { + targetNN.Namespace = string(*backend.Namespace) + } if backend.Kind != nil && *backend.Kind != "Service" { - terr = NewInvalidKindError(string(*backend.Kind)) + terr = newInvalidKindError(*backend.Kind) continue } @@ -435,22 +446,44 @@ func (r *HTTPRouteReconciler) processHTTPRouteBackendRefs(tctx *provider.Transla continue } - serviceNS := types.NamespacedName{ - Namespace: namespace, - Name: name, - } - var service corev1.Service - if err := r.Get(tctx, serviceNS, &service); err != nil { + if err := r.Get(tctx, targetNN, &service); err != nil { + terr = err if client.IgnoreNotFound(err) == nil { - terr = NewBackendNotFoundError(namespace, name) - } else { - terr = err + terr = ReasonError{ + Reason: string(gatewayv1.RouteReasonBackendNotFound), + Message: fmt.Sprintf("Service %s not found", targetNN), + } } continue } + + // if cross namespaces between HTTPRoute and referenced Service, check ReferenceGrant + if hrNN.Namespace != targetNN.Namespace { + if permitted := checkReferenceGrant(tctx, + r.Client, + v1beta1.ReferenceGrantFrom{ + Group: gatewayv1.GroupName, + Kind: KindHTTPRoute, + Namespace: v1beta1.Namespace(hrNN.Namespace), + }, + gatewayv1.ObjectReference{ + Group: corev1.GroupName, + Kind: KindService, + Name: gatewayv1.ObjectName(targetNN.Name), + Namespace: (*gatewayv1.Namespace)(&targetNN.Namespace), + }, + ); !permitted { + terr = ReasonError{ + Reason: string(v1beta1.RouteReasonRefNotPermitted), + Message: fmt.Sprintf("%s is in a different namespace than the HTTPRoute %s and no ReferenceGrant allowing reference is configured", targetNN, hrNN), + } + continue + } + } + if service.Spec.Type == corev1.ServiceTypeExternalName { - tctx.Services[serviceNS] = &service + tctx.Services[targetNN] = &service return nil } @@ -462,24 +495,24 @@ func (r *HTTPRouteReconciler) processHTTPRouteBackendRefs(tctx *provider.Transla } } if !portExists { - terr = fmt.Errorf("port %d not found in service %s", *backend.Port, name) + terr = fmt.Errorf("port %d not found in service %s", *backend.Port, targetNN.Name) continue } - tctx.Services[serviceNS] = &service + tctx.Services[targetNN] = &service endpointSliceList := new(discoveryv1.EndpointSliceList) if err := r.List(tctx, endpointSliceList, - client.InNamespace(namespace), + client.InNamespace(targetNN.Namespace), client.MatchingLabels{ - discoveryv1.LabelServiceName: name, + discoveryv1.LabelServiceName: targetNN.Name, }, ); err != nil { - r.Log.Error(err, "failed to list endpoint slices", "namespace", namespace, "name", name) + r.Log.Error(err, "failed to list endpoint slices", "Service", targetNN) terr = err continue } - tctx.EndpointSlices[serviceNS] = endpointSliceList.Items + tctx.EndpointSlices[targetNN] = endpointSliceList.Items } return terr } @@ -507,29 +540,14 @@ func (r *HTTPRouteReconciler) processHTTPRoute(tctx *provider.TranslateContext, } } for _, backend := range rule.BackendRefs { - var kind string - if backend.Kind == nil { - kind = "service" - } else { - kind = strings.ToLower(string(*backend.Kind)) - } - if kind != "service" { - terror = NewInvalidKindError(kind) + if backend.Kind != nil && *backend.Kind != "Service" { + terror = newInvalidKindError(*backend.Kind) continue } - - var ns string - if backend.Namespace == nil { - ns = httpRoute.Namespace - } else { - ns = string(*backend.Namespace) - } - - backendNs := gatewayv1.Namespace(ns) tctx.BackendRefs = append(tctx.BackendRefs, gatewayv1.BackendRef{ BackendObjectReference: gatewayv1.BackendObjectReference{ Name: backend.Name, - Namespace: &backendNs, + Namespace: cmp.Or(backend.Namespace, (*gatewayv1.Namespace)(&httpRoute.Namespace)), Port: backend.Port, }, }) @@ -615,3 +633,36 @@ func (r *HTTPRouteReconciler) listHTTPRoutesForGatewayProxy(ctx context.Context, return requests } + +func (r *HTTPRouteReconciler) listHTTPRoutesForReferenceGrant(ctx context.Context, obj client.Object) (requests []reconcile.Request) { + grant, ok := obj.(*v1beta1.ReferenceGrant) + if !ok { + r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to ReferenceGrant") + return nil + } + + var httpRouteList gatewayv1.HTTPRouteList + if err := r.List(ctx, &httpRouteList); err != nil { + r.Log.Error(err, "failed to list httproutes for reference ReferenceGrant", "ReferenceGrant", types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()}) + return nil + } + + for _, httpRoute := range httpRouteList.Items { + hr := v1beta1.ReferenceGrantFrom{ + Group: gatewayv1.GroupName, + Kind: KindHTTPRoute, + Namespace: v1beta1.Namespace(httpRoute.GetNamespace()), + } + for _, from := range grant.Spec.From { + if from == hr { + requests = append(requests, reconcile.Request{ + NamespacedName: client.ObjectKey{ + Namespace: httpRoute.GetNamespace(), + Name: httpRoute.GetName(), + }, + }) + } + } + } + return requests +} diff --git a/internal/controller/utils.go b/internal/controller/utils.go index dd356dd6..4c5de816 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -13,6 +13,7 @@ package controller import ( + "cmp" "context" "encoding/pem" "errors" @@ -31,6 +32,8 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" "sigs.k8s.io/gateway-api/apis/v1beta1" @@ -48,6 +51,7 @@ const ( KindIngressClass = "IngressClass" KindGatewayProxy = "GatewayProxy" KindSecret = "Secret" + KindService = "Service" ) const defaultIngressClassAnnotation = "ingressclass.kubernetes.io/is-default-class" @@ -56,6 +60,18 @@ var ( ErrNoMatchingListenerHostname = errors.New("no matching hostnames in listener") ) +var ( + enableReferenceGrant bool +) + +func SetEnableReferenceGrant(enable bool) { + enableReferenceGrant = enable +} + +func GetEnableReferenceGrant() bool { + return enableReferenceGrant +} + // IsDefaultIngressClass returns whether an IngressClass is the default IngressClass. func IsDefaultIngressClass(obj client.Object) bool { if ingressClass, ok := obj.(*networkingv1.IngressClass); ok { @@ -240,33 +256,23 @@ func SetRouteConditionAccepted(routeParentStatus *gatewayv1.RouteParentStatus, g // SetRouteConditionResolvedRefs sets the ResolvedRefs condition with proper reason based on error type func SetRouteConditionResolvedRefs(routeParentStatus *gatewayv1.RouteParentStatus, generation int64, err error) { - var ( - reason string - status = metav1.ConditionTrue - message = "backendRefs are resolved" - ) - - if err != nil { - status = metav1.ConditionFalse - message = err.Error() - reason = string(gatewayv1.RouteReasonResolvedRefs) - - if IsInvalidKindError(err) { - reason = string(gatewayv1.RouteReasonInvalidKind) - } else if IsBackendNotFoundError(err) { - reason = string(gatewayv1.RouteReasonBackendNotFound) - } - } else { - reason = string(gatewayv1.RouteReasonResolvedRefs) - } - - condition := metav1.Condition{ + var condition = metav1.Condition{ Type: string(gatewayv1.RouteConditionResolvedRefs), - Status: status, - Reason: reason, + Status: metav1.ConditionTrue, ObservedGeneration: generation, - Message: message, LastTransitionTime: metav1.Now(), + Reason: string(gatewayv1.RouteReasonResolvedRefs), + Message: "backendRefs are resolved", + } + + if err != nil { + condition.Status = metav1.ConditionFalse + condition.Message = err.Error() + + var re ReasonError + if errors.As(err, &re) { + condition.Reason = re.Reason + } } if !IsConditionPresentAndEqual(routeParentStatus.Conditions, condition) { @@ -657,7 +663,6 @@ func getListenerStatus( ctx context.Context, mrgc client.Client, gateway *gatewayv1.Gateway, - grants []v1beta1.ReferenceGrant, ) ([]gatewayv1.ListenerStatus, error) { statuses := make(map[gatewayv1.SectionName]gatewayv1.ListenerStatus, len(gateway.Spec.Listeners)) @@ -745,7 +750,20 @@ func getListenerStatus( conditionProgrammed.Reason = string(gatewayv1.ListenerReasonInvalid) break } - if ok := checkReferenceGrantBetweenGatewayAndSecret(gateway.Namespace, ref, grants); !ok { + if permitted := checkReferenceGrant(ctx, + mrgc, + v1beta1.ReferenceGrantFrom{ + Group: gatewayv1.GroupName, + Kind: KindGateway, + Namespace: v1beta1.Namespace(gateway.Namespace), + }, + gatewayv1.ObjectReference{ + Group: corev1.GroupName, + Kind: KindSecret, + Name: ref.Name, + Namespace: ref.Namespace, + }, + ); !permitted { conditionResolvedRefs.Status = metav1.ConditionFalse conditionResolvedRefs.Reason = string(gatewayv1.ListenerReasonRefNotPermitted) conditionResolvedRefs.Message = "certificateRefs cross namespaces is not permitted" @@ -753,11 +771,12 @@ func getListenerStatus( conditionProgrammed.Reason = string(gatewayv1.ListenerReasonInvalid) break } - ns := gateway.Namespace - if ref.Namespace != nil { - ns = string(*ref.Namespace) + + secretNN := types.NamespacedName{ + Namespace: string(*cmp.Or(ref.Namespace, (*gatewayv1.Namespace)(&gateway.Namespace))), + Name: string(ref.Name), } - if err := mrgc.Get(ctx, client.ObjectKey{Namespace: ns, Name: string(ref.Name)}, &secret); err != nil { + if err := mrgc.Get(ctx, secretNN, &secret); err != nil { conditionResolvedRefs.Status = metav1.ConditionFalse conditionResolvedRefs.Reason = string(gatewayv1.ListenerReasonInvalidCertificateRef) conditionResolvedRefs.Message = err.Error() @@ -919,50 +938,34 @@ func FullTypeName(a any) string { return path.Join(path.Dir(pkgPath), name) } -// InvalidKindError represents an error when backend reference kind is not supported -type InvalidKindError struct { - Kind string -} - -// Error implements the error interface -func (e *InvalidKindError) Error() string { - return fmt.Sprintf("%s %s", string(gatewayv1.RouteReasonInvalidKind), e.Kind) -} - -// NewInvalidKindError creates a new InvalidKindError -func NewInvalidKindError(kind string) *InvalidKindError { - return &InvalidKindError{Kind: kind} -} - -// IsInvalidKindError checks if the error is an InvalidKindError -func IsInvalidKindError(err error) bool { - _, ok := err.(*InvalidKindError) - return ok -} - -// BackendNotFoundError represents an error when a backend service is not found -type BackendNotFoundError struct { - Name string - Namespace string +type ReasonError struct { + Reason string + Message string } -// Error implements the error interface -func (e *BackendNotFoundError) Error() string { - return fmt.Sprintf("Service %s/%s not found", e.Namespace, e.Name) +func (e ReasonError) Error() string { + return e.Message } -// NewBackendNotFoundError creates a new BackendNotFoundError -func NewBackendNotFoundError(namespace, name string) *BackendNotFoundError { - return &BackendNotFoundError{ - Name: name, - Namespace: namespace, +func IsSomeReasonError[Reason ~string](err error, reasons ...Reason) bool { + if err == nil { + return false } + var re ReasonError + if !errors.As(err, &re) { + return false + } + if len(reasons) == 0 { + return true + } + return slices.Contains(reasons, Reason(re.Reason)) } -// IsBackendNotFoundError checks if the error is a BackendNotFoundError -func IsBackendNotFoundError(err error) bool { - _, ok := err.(*BackendNotFoundError) - return ok +func newInvalidKindError[Kind ~string](kind Kind) ReasonError { + return ReasonError{ + Reason: string(gatewayv1.RouteReasonInvalidKind), + Message: fmt.Sprintf("Invalid kind %s, only Service is supported", kind), + } } // filterHostnames accepts a list of gateways and an HTTPRoute, and returns a copy of the HTTPRoute with only the hostnames that match the listener hostnames of the gateways. @@ -1095,23 +1098,46 @@ func isTLSSecretValid(secret *corev1.Secret) (string, bool) { return "", true } -func checkReferenceGrantBetweenGatewayAndSecret(gwNamespace string, certRef gatewayv1.SecretObjectReference, grants []v1beta1.ReferenceGrant) bool { - // if not cross namespaces - if certRef.Namespace == nil || string(*certRef.Namespace) == gwNamespace { +func referenceGrantPredicates(kind gatewayv1.Kind) predicate.Funcs { + var filter = func(obj client.Object) bool { + grant, ok := obj.(*v1beta1.ReferenceGrant) + if !ok { + return false + } + for _, from := range grant.Spec.From { + if from.Kind == kind && string(from.Group) == gatewayv1.GroupName { + return true + } + } + return false + } + predicates := predicate.NewPredicateFuncs(filter) + predicates.UpdateFunc = func(e event.UpdateEvent) bool { + return filter(e.ObjectOld) || filter(e.ObjectNew) + } + return predicates +} + +func checkReferenceGrant(ctx context.Context, cli client.Client, obj v1beta1.ReferenceGrantFrom, ref gatewayv1.ObjectReference) bool { + if ref.Namespace == nil || *ref.Namespace == obj.Namespace { return true } - for _, grant := range grants { - if grant.Namespace == string(*certRef.Namespace) { + if !GetEnableReferenceGrant() { + return false + } + + var grantList v1beta1.ReferenceGrantList + if err := cli.List(ctx, &grantList, client.InNamespace(*ref.Namespace)); err != nil { + return false + } + + for _, grant := range grantList.Items { + if grant.Namespace == string(*ref.Namespace) { for _, from := range grant.Spec.From { - gw := v1beta1.ReferenceGrantFrom{ - Group: gatewayv1.GroupName, - Kind: KindGateway, - Namespace: v1beta1.Namespace(gwNamespace), - } - if from == gw { + if obj == from { for _, to := range grant.Spec.To { - if to.Group == corev1.GroupName && to.Kind == KindSecret && (to.Name == nil || *to.Name == certRef.Name) { + if to.Group == ref.Group && to.Kind == ref.Kind && (to.Name == nil || *to.Name == ref.Name) { return true } } diff --git a/internal/manager/run.go b/internal/manager/run.go index c8156ba9..2a1daf99 100644 --- a/internal/manager/run.go +++ b/internal/manager/run.go @@ -19,7 +19,6 @@ import ( "time" "github.com/go-logr/logr" - "github.com/pkg/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -34,6 +33,7 @@ import ( "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/apache/apisix-ingress-controller/api/v1alpha1" + "github.com/apache/apisix-ingress-controller/internal/controller" "github.com/apache/apisix-ingress-controller/internal/controller/config" "github.com/apache/apisix-ingress-controller/internal/provider/adc" ) @@ -177,20 +177,24 @@ func Run(ctx context.Context, logger logr.Logger) error { } }() + setupLog.Info("check ReferenceGrants is enabled") + _, err = mgr.GetRESTMapper().KindsFor(schema.GroupVersionResource{ + Group: v1beta1.GroupVersion.Group, + Version: v1beta1.GroupVersion.Version, + Resource: "referencegrants", + }) + if err != nil { + setupLog.Info("CRD ReferenceGrants is not installed", "err", err) + } + controller.SetEnableReferenceGrant(err == nil) + setupLog.Info("setting up controllers") controllers, err := setupControllers(ctx, mgr, provider) if err != nil { setupLog.Error(err, "unable to set up controllers") return err } - if _, err = mgr.GetRESTMapper().KindsFor(schema.GroupVersionResource{ - Group: v1beta1.GroupVersion.Group, - Version: v1beta1.GroupVersion.Version, - Resource: "referencegrants", - }); err != nil { - logger.Error(err, "CRD ReferenceGrants is not installed") - return errors.Wrap(err, "CRD ReferenceGrants is not installed") - } + for _, c := range controllers { if err := c.SetupWithManager(mgr); err != nil { return err diff --git a/test/conformance/conformance_test.go b/test/conformance/conformance_test.go index c664f1fe..2e77998e 100644 --- a/test/conformance/conformance_test.go +++ b/test/conformance/conformance_test.go @@ -24,14 +24,7 @@ var skippedTestsForSSL = []string{ tests.HTTPRouteRedirectPortAndScheme.ShortName, } -var skippedTestsForTraditionalRoutes = []string{ - tests.HTTPRouteInvalidCrossNamespaceBackendRef.ShortName, - tests.HTTPRouteInvalidReferenceGrant.ShortName, - tests.HTTPRoutePartiallyInvalidViaInvalidReferenceGrant.ShortName, - tests.HTTPRouteReferenceGrant.ShortName, - - // TODO: HTTPRoute hostname intersection and listener hostname matching -} +// TODO: HTTPRoute hostname intersection and listener hostname matching var gatewaySupportedFeatures = []features.FeatureName{ features.SupportGateway, @@ -51,7 +44,7 @@ func TestGatewayAPIConformance(t *testing.T) { opts.CleanupBaseResources = true opts.GatewayClassName = gatewayClassName opts.SupportedFeatures = sets.New(gatewaySupportedFeatures...) - opts.SkipTests = append(skippedTestsForSSL, skippedTestsForTraditionalRoutes...) + opts.SkipTests = skippedTestsForSSL opts.Implementation = conformancev1.Implementation{ Organization: "APISIX", Project: "apisix-ingress-controller", @@ -80,6 +73,6 @@ func TestGatewayAPIConformance(t *testing.T) { if err != nil { t.Fatalf("failed to marshal the gateway conformance test report: %v", err) } - // Save report in root of the repository, file name is in .gitignore. + // Save report in the root of the repository, file name is in .gitignore. require.NoError(t, os.WriteFile("../../"+reportFileName, rawReport, 0o600)) }