github.com/cilium/cilium@v1.16.2/operator/pkg/gateway-api/cell.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package gateway_api
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  
    11  	"github.com/cilium/hive/cell"
    12  	"github.com/sirupsen/logrus"
    13  	"github.com/spf13/pflag"
    14  	corev1 "k8s.io/api/core/v1"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	"k8s.io/apimachinery/pkg/runtime"
    17  	"k8s.io/apimachinery/pkg/runtime/schema"
    18  	ctrlRuntime "sigs.k8s.io/controller-runtime"
    19  	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
    20  	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
    21  	gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
    22  	mcsapiv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
    23  
    24  	operatorOption "github.com/cilium/cilium/operator/option"
    25  	"github.com/cilium/cilium/operator/pkg/model/translation"
    26  	gatewayApiTranslation "github.com/cilium/cilium/operator/pkg/model/translation/gateway-api"
    27  	"github.com/cilium/cilium/operator/pkg/secretsync"
    28  	k8sClient "github.com/cilium/cilium/pkg/k8s/client"
    29  	"github.com/cilium/cilium/pkg/option"
    30  )
    31  
    32  // Cell manages the Gateway API related controllers.
    33  var Cell = cell.Module(
    34  	"gateway-api",
    35  	"Manages the Gateway API controllers",
    36  
    37  	cell.Config(gatewayApiConfig{
    38  		EnableGatewayAPISecretsSync:            true,
    39  		EnableGatewayAPIProxyProtocol:          false,
    40  		EnableGatewayAPIAppProtocol:            false,
    41  		EnableGatewayAPIAlpn:                   false,
    42  		GatewayAPIServiceExternalTrafficPolicy: "Cluster",
    43  		GatewayAPISecretsNamespace:             "cilium-secrets",
    44  		GatewayAPIXffNumTrustedHops:            0,
    45  
    46  		GatewayAPIHostnetworkEnabled:           false,
    47  		GatewayAPIHostnetworkNodelabelselector: "",
    48  	}),
    49  	cell.Invoke(initGatewayAPIController),
    50  	cell.Provide(registerSecretSync),
    51  )
    52  
    53  var requiredGVK = []schema.GroupVersionKind{
    54  	gatewayv1.SchemeGroupVersion.WithKind("gatewayclasses"),
    55  	gatewayv1.SchemeGroupVersion.WithKind("gateways"),
    56  	gatewayv1.SchemeGroupVersion.WithKind("httproutes"),
    57  	gatewayv1.SchemeGroupVersion.WithKind("grpcroutes"),
    58  	gatewayv1beta1.SchemeGroupVersion.WithKind("referencegrants"),
    59  	gatewayv1alpha2.SchemeGroupVersion.WithKind("tlsroutes"),
    60  }
    61  
    62  type gatewayApiConfig struct {
    63  	KubeProxyReplacement string
    64  	EnableNodePort       bool
    65  
    66  	EnableGatewayAPISecretsSync            bool
    67  	EnableGatewayAPIProxyProtocol          bool
    68  	EnableGatewayAPIAppProtocol            bool
    69  	EnableGatewayAPIAlpn                   bool
    70  	GatewayAPIServiceExternalTrafficPolicy string
    71  	GatewayAPISecretsNamespace             string
    72  	GatewayAPIXffNumTrustedHops            uint32
    73  
    74  	GatewayAPIHostnetworkEnabled           bool
    75  	GatewayAPIHostnetworkNodelabelselector string
    76  }
    77  
    78  func (r gatewayApiConfig) Flags(flags *pflag.FlagSet) {
    79  	flags.String("kube-proxy-replacement", r.KubeProxyReplacement, "Enable only selected features (will panic if any selected feature cannot be enabled) (\"false\"), or enable all features (will panic if any feature cannot be enabled) (\"true\") (default \"false\")")
    80  	flags.Bool("enable-node-port", r.EnableNodePort, "Enable NodePort type services by Cilium")
    81  
    82  	flags.Bool("enable-gateway-api-secrets-sync", r.EnableGatewayAPISecretsSync, "Enables fan-in TLS secrets sync from multiple namespaces to singular namespace (specified by gateway-api-secrets-namespace flag)")
    83  	flags.Bool("enable-gateway-api-proxy-protocol", r.EnableGatewayAPIProxyProtocol, "Enable proxy protocol for all GatewayAPI listeners. Note that _only_ Proxy protocol traffic will be accepted once this is enabled.")
    84  	flags.Bool("enable-gateway-api-app-protocol", r.EnableGatewayAPIAppProtocol, "Enables Backend Protocol selection (GEP-1911) for Gateway API via appProtocol")
    85  	flags.Bool("enable-gateway-api-alpn", r.EnableGatewayAPIAlpn, "Enables exposing ALPN with HTTP2 and HTTP/1.1 support for Gateway API")
    86  	flags.Uint32("gateway-api-xff-num-trusted-hops", r.GatewayAPIXffNumTrustedHops, "The number of additional GatewayAPI proxy hops from the right side of the HTTP header to trust when determining the origin client's IP address.")
    87  	flags.String("gateway-api-service-externaltrafficpolicy", r.GatewayAPIServiceExternalTrafficPolicy, "Kubernetes LoadBalancer Service externalTrafficPolicy for all Gateway instances.")
    88  	flags.String("gateway-api-secrets-namespace", r.GatewayAPISecretsNamespace, "Namespace having tls secrets used by CEC for Gateway API")
    89  	flags.Bool("gateway-api-hostnetwork-enabled", r.GatewayAPIHostnetworkEnabled, "Exposes Gateway listeners on the host network.")
    90  	flags.String("gateway-api-hostnetwork-nodelabelselector", r.GatewayAPIHostnetworkNodelabelselector, "Label selector that matches the nodes where the gateway listeners should be exposed. It's a list of comma-separated key-value label pairs. e.g. 'kubernetes.io/os=linux,kubernetes.io/hostname=kind-worker'")
    91  }
    92  
    93  type gatewayAPIParams struct {
    94  	cell.In
    95  
    96  	Logger             logrus.FieldLogger
    97  	K8sClient          k8sClient.Clientset
    98  	CtrlRuntimeManager ctrlRuntime.Manager
    99  	Scheme             *runtime.Scheme
   100  
   101  	AgentConfig      *option.DaemonConfig
   102  	OperatorConfig   *operatorOption.OperatorConfig
   103  	GatewayApiConfig gatewayApiConfig
   104  }
   105  
   106  func initGatewayAPIController(params gatewayAPIParams) error {
   107  	if !operatorOption.Config.EnableGatewayAPI {
   108  		return nil
   109  	}
   110  
   111  	if params.GatewayApiConfig.KubeProxyReplacement != option.KubeProxyReplacementTrue &&
   112  		!params.GatewayApiConfig.EnableNodePort {
   113  		params.Logger.Warn("Gateway API support requires either kube-proxy-replacement or enable-node-port enabled")
   114  		return nil
   115  	}
   116  
   117  	if err := validateExternalTrafficPolicy(params); err != nil {
   118  		return err
   119  	}
   120  
   121  	params.Logger.WithField("requiredGVK", requiredGVK).Info("Checking for required GatewayAPI resources")
   122  	if err := checkRequiredCRDs(context.Background(), params.K8sClient); err != nil {
   123  		params.Logger.WithError(err).Error("Required GatewayAPI resources are not found, please refer to docs for installation instructions")
   124  		return nil
   125  	}
   126  
   127  	if err := registerGatewayAPITypesToScheme(params.Scheme); err != nil {
   128  		return err
   129  	}
   130  
   131  	if err := registerMCSAPITypesToScheme(params.K8sClient, params.Scheme); err != nil {
   132  		return err
   133  	}
   134  
   135  	cecTranslator := translation.NewCECTranslator(
   136  		params.GatewayApiConfig.GatewayAPISecretsNamespace,
   137  		params.GatewayApiConfig.EnableGatewayAPIProxyProtocol,
   138  		params.GatewayApiConfig.EnableGatewayAPIAppProtocol,
   139  		true, // hostNameSuffixMatch
   140  		params.OperatorConfig.ProxyIdleTimeoutSeconds,
   141  		params.GatewayApiConfig.GatewayAPIHostnetworkEnabled,
   142  		translation.ParseNodeLabelSelector(params.GatewayApiConfig.GatewayAPIHostnetworkNodelabelselector),
   143  		params.AgentConfig.EnableIPv4,
   144  		params.AgentConfig.EnableIPv6,
   145  		params.GatewayApiConfig.GatewayAPIXffNumTrustedHops,
   146  	)
   147  
   148  	cecTranslator.WithUseAlpn(params.GatewayApiConfig.EnableGatewayAPIAlpn)
   149  
   150  	gatewayAPITranslator := gatewayApiTranslation.NewTranslator(
   151  		cecTranslator,
   152  		params.GatewayApiConfig.GatewayAPIHostnetworkEnabled,
   153  		params.GatewayApiConfig.GatewayAPIServiceExternalTrafficPolicy,
   154  	)
   155  
   156  	if err := registerReconcilers(
   157  		params.CtrlRuntimeManager,
   158  		gatewayAPITranslator,
   159  	); err != nil {
   160  		return fmt.Errorf("failed to create gateway controller: %w", err)
   161  	}
   162  
   163  	return nil
   164  }
   165  
   166  // registerSecretSync registers the Gateway API for secret synchronization based on TLS secrets referenced
   167  // by a Cilium Gateway resource.
   168  func registerSecretSync(params gatewayAPIParams) secretsync.SecretSyncRegistrationOut {
   169  	if err := checkRequiredCRDs(context.Background(), params.K8sClient); err != nil {
   170  		return secretsync.SecretSyncRegistrationOut{}
   171  	}
   172  
   173  	if !operatorOption.Config.EnableGatewayAPI || !params.GatewayApiConfig.EnableGatewayAPISecretsSync {
   174  		return secretsync.SecretSyncRegistrationOut{}
   175  	}
   176  
   177  	return secretsync.SecretSyncRegistrationOut{
   178  		SecretSyncRegistration: &secretsync.SecretSyncRegistration{
   179  			RefObject:            &gatewayv1.Gateway{},
   180  			RefObjectEnqueueFunc: EnqueueTLSSecrets(params.CtrlRuntimeManager.GetClient(), params.Logger),
   181  			RefObjectCheckFunc:   IsReferencedByCiliumGateway,
   182  			SecretsNamespace:     params.GatewayApiConfig.GatewayAPISecretsNamespace,
   183  		},
   184  	}
   185  }
   186  
   187  func validateExternalTrafficPolicy(params gatewayAPIParams) error {
   188  	if params.GatewayApiConfig.GatewayAPIHostnetworkEnabled && params.GatewayApiConfig.GatewayAPIServiceExternalTrafficPolicy != "" {
   189  		log.Warn("Gateway API host networking is enabled, externalTrafficPolicy will be ignored.")
   190  		return nil
   191  	} else if params.GatewayApiConfig.GatewayAPIServiceExternalTrafficPolicy == string(corev1.ServiceExternalTrafficPolicyCluster) ||
   192  		params.GatewayApiConfig.GatewayAPIServiceExternalTrafficPolicy == string(corev1.ServiceExternalTrafficPolicyLocal) {
   193  		return nil
   194  	}
   195  	return fmt.Errorf("invalid externalTrafficPolicy: %s", params.GatewayApiConfig.GatewayAPIServiceExternalTrafficPolicy)
   196  }
   197  
   198  func checkCRD(ctx context.Context, clientset k8sClient.Clientset, gvk schema.GroupVersionKind) error {
   199  	if !clientset.IsEnabled() {
   200  		return nil
   201  	}
   202  
   203  	crd, err := clientset.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, gvk.GroupKind().String(), metav1.GetOptions{})
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	found := false
   209  	for _, v := range crd.Spec.Versions {
   210  		if v.Name == gvk.Version {
   211  			found = true
   212  			break
   213  		}
   214  	}
   215  	if !found {
   216  		return fmt.Errorf("CRD %q does not have version %q", gvk.GroupKind().String(), gvk.Version)
   217  	}
   218  
   219  	return nil
   220  }
   221  
   222  func checkRequiredCRDs(ctx context.Context, clientset k8sClient.Clientset) error {
   223  	var res error
   224  	for _, gvk := range requiredGVK {
   225  		if err := checkCRD(ctx, clientset, gvk); err != nil {
   226  			res = errors.Join(res, err)
   227  		}
   228  	}
   229  	return res
   230  }
   231  
   232  // registerReconcilers registers the Gateway API reconcilers to the controller-runtime library manager.
   233  func registerReconcilers(mgr ctrlRuntime.Manager, translator translation.Translator) error {
   234  	reconcilers := []interface {
   235  		SetupWithManager(mgr ctrlRuntime.Manager) error
   236  	}{
   237  		newGatewayClassReconciler(mgr),
   238  		newGatewayReconciler(mgr, translator),
   239  		newReferenceGrantReconciler(mgr),
   240  		newHTTPRouteReconciler(mgr),
   241  		newGammaHttpRouteReconciler(mgr, translator),
   242  		newGRPCRouteReconciler(mgr),
   243  		newTLSRouteReconciler(mgr),
   244  	}
   245  
   246  	for _, r := range reconcilers {
   247  		if err := r.SetupWithManager(mgr); err != nil {
   248  			return fmt.Errorf("failed to setup reconciler: %w", err)
   249  		}
   250  	}
   251  
   252  	return nil
   253  }
   254  
   255  func registerGatewayAPITypesToScheme(scheme *runtime.Scheme) error {
   256  	for gv, f := range map[fmt.Stringer]func(s *runtime.Scheme) error{
   257  		gatewayv1.GroupVersion:       gatewayv1.AddToScheme,
   258  		gatewayv1beta1.GroupVersion:  gatewayv1beta1.AddToScheme,
   259  		gatewayv1alpha2.GroupVersion: gatewayv1alpha2.AddToScheme,
   260  	} {
   261  		if err := f(scheme); err != nil {
   262  			return fmt.Errorf("failed to add types from %s to scheme: %w", gv, err)
   263  		}
   264  	}
   265  
   266  	return nil
   267  }
   268  
   269  func registerMCSAPITypesToScheme(clientset k8sClient.Clientset, scheme *runtime.Scheme) error {
   270  	serviceImportSupport := checkCRD(context.Background(), clientset, mcsapiv1alpha1.SchemeGroupVersion.WithKind("serviceimports")) == nil
   271  	log.WithField("enabled", serviceImportSupport).
   272  		Info("Multi-cluster Service API ServiceImport GatewayAPI integration")
   273  	if serviceImportSupport {
   274  		return mcsapiv1alpha1.AddToScheme(scheme)
   275  	}
   276  
   277  	return nil
   278  }