github.com/nginxinc/kubernetes-ingress@v1.12.5/cmd/nginx-ingress/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"fmt"
     7  	"net"
     8  	"net/http"
     9  	"os"
    10  	"os/signal"
    11  	"regexp"
    12  	"strings"
    13  	"syscall"
    14  	"time"
    15  
    16  	"github.com/golang/glog"
    17  	"github.com/nginxinc/kubernetes-ingress/internal/configs"
    18  	"github.com/nginxinc/kubernetes-ingress/internal/configs/version1"
    19  	"github.com/nginxinc/kubernetes-ingress/internal/configs/version2"
    20  	"github.com/nginxinc/kubernetes-ingress/internal/k8s"
    21  	"github.com/nginxinc/kubernetes-ingress/internal/k8s/secrets"
    22  	"github.com/nginxinc/kubernetes-ingress/internal/metrics"
    23  	"github.com/nginxinc/kubernetes-ingress/internal/metrics/collectors"
    24  	"github.com/nginxinc/kubernetes-ingress/internal/nginx"
    25  	cr_validation "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/validation"
    26  	k8s_nginx "github.com/nginxinc/kubernetes-ingress/pkg/client/clientset/versioned"
    27  	conf_scheme "github.com/nginxinc/kubernetes-ingress/pkg/client/clientset/versioned/scheme"
    28  	"github.com/nginxinc/nginx-plus-go-client/client"
    29  	nginxCollector "github.com/nginxinc/nginx-prometheus-exporter/collector"
    30  	"github.com/prometheus/client_golang/prometheus"
    31  	api_v1 "k8s.io/api/core/v1"
    32  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/util/validation"
    34  	util_version "k8s.io/apimachinery/pkg/util/version"
    35  	"k8s.io/client-go/dynamic"
    36  	"k8s.io/client-go/kubernetes"
    37  	"k8s.io/client-go/kubernetes/scheme"
    38  	"k8s.io/client-go/rest"
    39  	"k8s.io/client-go/tools/clientcmd"
    40  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    41  )
    42  
    43  var (
    44  
    45  	// Set during build
    46  	version string
    47  	commit  string
    48  	date    string
    49  
    50  	healthStatus = flag.Bool("health-status", false,
    51  		`Add a location based on the value of health-status-uri to the default server. The location responds with the 200 status code for any request.
    52  	Useful for external health-checking of the Ingress controller`)
    53  
    54  	healthStatusURI = flag.String("health-status-uri", "/nginx-health",
    55  		`Sets the URI of health status location in the default server. Requires -health-status`)
    56  
    57  	proxyURL = flag.String("proxy", "",
    58  		`Use a proxy server to connect to Kubernetes API started by "kubectl proxy" command. For testing purposes only.
    59  	The Ingress controller does not start NGINX and does not write any generated NGINX configuration files to disk`)
    60  
    61  	watchNamespace = flag.String("watch-namespace", api_v1.NamespaceAll,
    62  		`Namespace to watch for Ingress resources. By default the Ingress controller watches all namespaces`)
    63  
    64  	nginxConfigMaps = flag.String("nginx-configmaps", "",
    65  		`A ConfigMap resource for customizing NGINX configuration. If a ConfigMap is set,
    66  	but the Ingress controller is not able to fetch it from Kubernetes API, the Ingress controller will fail to start.
    67  	Format: <namespace>/<name>`)
    68  
    69  	nginxPlus = flag.Bool("nginx-plus", false, "Enable support for NGINX Plus")
    70  
    71  	appProtect = flag.Bool("enable-app-protect", false, "Enable support for NGINX App Protect. Requires -nginx-plus.")
    72  
    73  	ingressClass = flag.String("ingress-class", "nginx",
    74  		`A class of the Ingress controller.
    75  
    76  	For Kubernetes >= 1.18, a corresponding IngressClass resource with the name equal to the class must be deployed. Otherwise,
    77  	the Ingress Controller will fail to start.
    78  	The Ingress controller only processes resources that belong to its class - i.e. have the "ingressClassName" field resource equal to the class.
    79  
    80  	For Kubernetes < 1.18, the Ingress Controller only processes resources that belong to its class -
    81  	i.e have the annotation "kubernetes.io/ingress.class" (for Ingress resources)
    82  	or field "ingressClassName" (for VirtualServer/VirtualServerRoute/TransportServer resources) equal to the class.
    83  	Additionally, the Ingress Controller processes resources that do not have the class set,
    84  	which can be disabled by setting the "-use-ingress-class-only" flag
    85  
    86  	The Ingress Controller processes all the VirtualServer/VirtualServerRoute/TransportServer resources that do not have the "ingressClassName" field for all versions of kubernetes.`)
    87  
    88  	useIngressClassOnly = flag.Bool("use-ingress-class-only", false,
    89  		`For kubernetes versions >= 1.18 this flag will be IGNORED.
    90  
    91  	Ignore Ingress resources without the "kubernetes.io/ingress.class" annotation`)
    92  
    93  	defaultServerSecret = flag.String("default-server-tls-secret", "",
    94  		`A Secret with a TLS certificate and key for TLS termination of the default server. Format: <namespace>/<name>.
    95  	If not set, than the certificate and key in the file "/etc/nginx/secrets/default" are used.
    96  	If "/etc/nginx/secrets/default" doesn't exist, the Ingress Controller will configure NGINX to reject TLS connections to the default server.
    97  	If a secret is set, but the Ingress controller is not able to fetch it from Kubernetes API or it is not set and the Ingress Controller
    98  	fails to read the file "/etc/nginx/secrets/default", the Ingress controller will fail to start.`)
    99  
   100  	versionFlag = flag.Bool("version", false, "Print the version, git-commit hash and build date and exit")
   101  
   102  	mainTemplatePath = flag.String("main-template-path", "",
   103  		`Path to the main NGINX configuration template. (default for NGINX "nginx.tmpl"; default for NGINX Plus "nginx-plus.tmpl")`)
   104  
   105  	ingressTemplatePath = flag.String("ingress-template-path", "",
   106  		`Path to the ingress NGINX configuration template for an ingress resource.
   107  	(default for NGINX "nginx.ingress.tmpl"; default for NGINX Plus "nginx-plus.ingress.tmpl")`)
   108  
   109  	virtualServerTemplatePath = flag.String("virtualserver-template-path", "",
   110  		`Path to the VirtualServer NGINX configuration template for a VirtualServer resource.
   111  	(default for NGINX "nginx.virtualserver.tmpl"; default for NGINX Plus "nginx-plus.virtualserver.tmpl")`)
   112  
   113  	transportServerTemplatePath = flag.String("transportserver-template-path", "",
   114  		`Path to the TransportServer NGINX configuration template for a TransportServer resource.
   115  	(default for NGINX "nginx.transportserver.tmpl"; default for NGINX Plus "nginx-plus.transportserver.tmpl")`)
   116  
   117  	externalService = flag.String("external-service", "",
   118  		`Specifies the name of the service with the type LoadBalancer through which the Ingress controller pods are exposed externally.
   119  	The external address of the service is used when reporting the status of Ingress, VirtualServer and VirtualServerRoute resources. For Ingress resources only: Requires -report-ingress-status.`)
   120  
   121  	ingressLink = flag.String("ingresslink", "",
   122  		`Specifies the name of the IngressLink resource, which exposes the Ingress Controller pods via a BIG-IP system.
   123  	The IP of the BIG-IP system is used when reporting the status of Ingress, VirtualServer and VirtualServerRoute resources. For Ingress resources only: Requires -report-ingress-status.`)
   124  
   125  	reportIngressStatus = flag.Bool("report-ingress-status", false,
   126  		"Updates the address field in the status of Ingress resources. Requires the -external-service or -ingresslink flag, or the 'external-status-address' key in the ConfigMap.")
   127  
   128  	leaderElectionEnabled = flag.Bool("enable-leader-election", true,
   129  		"Enable Leader election to avoid multiple replicas of the controller reporting the status of Ingress, VirtualServer and VirtualServerRoute resources -- only one replica will report status (default true). See -report-ingress-status flag.")
   130  
   131  	leaderElectionLockName = flag.String("leader-election-lock-name", "nginx-ingress-leader-election",
   132  		`Specifies the name of the ConfigMap, within the same namespace as the controller, used as the lock for leader election. Requires -enable-leader-election.`)
   133  
   134  	nginxStatusAllowCIDRs = flag.String("nginx-status-allow-cidrs", "127.0.0.1", `Add IPv4 IP/CIDR blocks to the allow list for NGINX stub_status or the NGINX Plus API. Separate multiple IP/CIDR by commas.`)
   135  
   136  	nginxStatusPort = flag.Int("nginx-status-port", 8080,
   137  		"Set the port where the NGINX stub_status or the NGINX Plus API is exposed. [1024 - 65535]")
   138  
   139  	nginxStatus = flag.Bool("nginx-status", true,
   140  		"Enable the NGINX stub_status, or the NGINX Plus API.")
   141  
   142  	nginxDebug = flag.Bool("nginx-debug", false,
   143  		"Enable debugging for NGINX. Uses the nginx-debug binary. Requires 'error-log-level: debug' in the ConfigMap.")
   144  
   145  	nginxReloadTimeout = flag.Int("nginx-reload-timeout", 0,
   146  		`The timeout in milliseconds which the Ingress Controller will wait for a successful NGINX reload after a change or at the initial start.
   147  		The default is 4000 (or 20000 if -enable-app-protect is true). If set to 0, the default value will be used`)
   148  
   149  	wildcardTLSSecret = flag.String("wildcard-tls-secret", "",
   150  		`A Secret with a TLS certificate and key for TLS termination of every Ingress host for which TLS termination is enabled but the Secret is not specified.
   151  		Format: <namespace>/<name>. If the argument is not set, for such Ingress hosts NGINX will break any attempt to establish a TLS connection.
   152  		If the argument is set, but the Ingress controller is not able to fetch the Secret from Kubernetes API, the Ingress controller will fail to start.`)
   153  
   154  	enablePrometheusMetrics = flag.Bool("enable-prometheus-metrics", false,
   155  		"Enable exposing NGINX or NGINX Plus metrics in the Prometheus format")
   156  
   157  	prometheusTLSSecretName = flag.String("prometheus-tls-secret", "",
   158  		`A Secret with a TLS certificate and key for TLS termination of the prometheus endpoint.`)
   159  
   160  	prometheusMetricsListenPort = flag.Int("prometheus-metrics-listen-port", 9113,
   161  		"Set the port where the Prometheus metrics are exposed. [1024 - 65535]")
   162  
   163  	enableCustomResources = flag.Bool("enable-custom-resources", true,
   164  		"Enable custom resources")
   165  
   166  	enablePreviewPolicies = flag.Bool("enable-preview-policies", false,
   167  		"Enable preview policies")
   168  
   169  	enableSnippets = flag.Bool("enable-snippets", false,
   170  		"Enable custom NGINX configuration snippets in Ingress, VirtualServer, VirtualServerRoute and TransportServer resources.")
   171  
   172  	globalConfiguration = flag.String("global-configuration", "",
   173  		`The namespace/name of the GlobalConfiguration resource for global configuration of the Ingress Controller. Requires -enable-custom-resources. Format: <namespace>/<name>`)
   174  
   175  	enableTLSPassthrough = flag.Bool("enable-tls-passthrough", false,
   176  		"Enable TLS Passthrough on port 443. Requires -enable-custom-resources")
   177  
   178  	spireAgentAddress = flag.String("spire-agent-address", "",
   179  		`Specifies the address of the running Spire agent. Requires -nginx-plus and is for use with NGINX Service Mesh only. If the flag is set,
   180  			but the Ingress Controller is not able to connect with the Spire Agent, the Ingress Controller will fail to start.`)
   181  
   182  	enableInternalRoutes = flag.Bool("enable-internal-routes", false,
   183  		`Enable support for internal routes with NGINX Service Mesh. Requires -spire-agent-address and -nginx-plus. Is for use with NGINX Service Mesh only.`)
   184  
   185  	readyStatus = flag.Bool("ready-status", true, "Enables the readiness endpoint '/nginx-ready'. The endpoint returns a success code when NGINX has loaded all the config after the startup")
   186  
   187  	readyStatusPort = flag.Int("ready-status-port", 8081, "Set the port where the readiness endpoint is exposed. [1024 - 65535]")
   188  
   189  	enableLatencyMetrics = flag.Bool("enable-latency-metrics", false,
   190  		"Enable collection of latency metrics for upstreams. Requires -enable-prometheus-metrics")
   191  
   192  	startupCheckFn func() error
   193  )
   194  
   195  func main() {
   196  	flag.Parse()
   197  
   198  	err := flag.Lookup("logtostderr").Value.Set("true")
   199  	if err != nil {
   200  		glog.Fatalf("Error setting logtostderr to true: %v", err)
   201  	}
   202  
   203  	versionInfo := fmt.Sprintf("Version=%v GitCommit=%v Date=%v", version, commit, date)
   204  	if *versionFlag {
   205  		fmt.Println(versionInfo)
   206  		os.Exit(0)
   207  	}
   208  
   209  	if startupCheckFn != nil {
   210  		err := startupCheckFn()
   211  		if err != nil {
   212  			glog.Fatalf("Failed startup check: %v", err)
   213  		}
   214  	}
   215  
   216  	healthStatusURIValidationError := validateLocation(*healthStatusURI)
   217  	if healthStatusURIValidationError != nil {
   218  		glog.Fatalf("Invalid value for health-status-uri: %v", healthStatusURIValidationError)
   219  	}
   220  
   221  	statusLockNameValidationError := validateResourceName(*leaderElectionLockName)
   222  	if statusLockNameValidationError != nil {
   223  		glog.Fatalf("Invalid value for leader-election-lock-name: %v", statusLockNameValidationError)
   224  	}
   225  
   226  	statusPortValidationError := validatePort(*nginxStatusPort)
   227  	if statusPortValidationError != nil {
   228  		glog.Fatalf("Invalid value for nginx-status-port: %v", statusPortValidationError)
   229  	}
   230  
   231  	metricsPortValidationError := validatePort(*prometheusMetricsListenPort)
   232  	if metricsPortValidationError != nil {
   233  		glog.Fatalf("Invalid value for prometheus-metrics-listen-port: %v", metricsPortValidationError)
   234  	}
   235  
   236  	readyStatusPortValidationError := validatePort(*readyStatusPort)
   237  	if readyStatusPortValidationError != nil {
   238  		glog.Fatalf("Invalid value for ready-status-port: %v", readyStatusPortValidationError)
   239  	}
   240  
   241  	allowedCIDRs, err := parseNginxStatusAllowCIDRs(*nginxStatusAllowCIDRs)
   242  	if err != nil {
   243  		glog.Fatalf(`Invalid value for nginx-status-allow-cidrs: %v`, err)
   244  	}
   245  
   246  	if *enableTLSPassthrough && !*enableCustomResources {
   247  		glog.Fatal("enable-tls-passthrough flag requires -enable-custom-resources")
   248  	}
   249  
   250  	if *appProtect && !*nginxPlus {
   251  		glog.Fatal("NGINX App Protect support is for NGINX Plus only")
   252  	}
   253  
   254  	if *spireAgentAddress != "" && !*nginxPlus {
   255  		glog.Fatal("spire-agent-address support is for NGINX Plus only")
   256  	}
   257  
   258  	if *enableInternalRoutes && *spireAgentAddress == "" {
   259  		glog.Fatal("enable-internal-routes flag requires spire-agent-address")
   260  	}
   261  
   262  	if *enableLatencyMetrics && !*enablePrometheusMetrics {
   263  		glog.Warning("enable-latency-metrics flag requires enable-prometheus-metrics, latency metrics will not be collected")
   264  		*enableLatencyMetrics = false
   265  	}
   266  
   267  	if *ingressLink != "" && *externalService != "" {
   268  		glog.Fatal("ingresslink and external-service cannot both be set")
   269  	}
   270  
   271  	glog.Infof("Starting NGINX Ingress controller %v PlusFlag=%v", versionInfo, *nginxPlus)
   272  
   273  	var config *rest.Config
   274  	if *proxyURL != "" {
   275  		config, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
   276  			&clientcmd.ClientConfigLoadingRules{},
   277  			&clientcmd.ConfigOverrides{
   278  				ClusterInfo: clientcmdapi.Cluster{
   279  					Server: *proxyURL,
   280  				},
   281  			}).ClientConfig()
   282  		if err != nil {
   283  			glog.Fatalf("error creating client configuration: %v", err)
   284  		}
   285  	} else {
   286  		if config, err = rest.InClusterConfig(); err != nil {
   287  			glog.Fatalf("error creating client configuration: %v", err)
   288  		}
   289  	}
   290  
   291  	kubeClient, err := kubernetes.NewForConfig(config)
   292  	if err != nil {
   293  		glog.Fatalf("Failed to create client: %v.", err)
   294  	}
   295  
   296  	k8sVersion, err := k8s.GetK8sVersion(kubeClient)
   297  	if err != nil {
   298  		glog.Fatalf("error retrieving k8s version: %v", err)
   299  	}
   300  
   301  	minK8sVersion := minVersion("1.14.0")
   302  	if !k8sVersion.AtLeast(minK8sVersion) {
   303  		glog.Fatalf("Versions of Kubernetes < %v are not supported, please refer to the documentation for details on supported versions.", minK8sVersion)
   304  	}
   305  
   306  	// Ingress V1 is only available from k8s > 1.18
   307  	ingressV1Version := minVersion("1.18.0")
   308  	if k8sVersion.AtLeast(ingressV1Version) {
   309  		*useIngressClassOnly = true
   310  		glog.Warningln("The '-use-ingress-class-only' flag will be deprecated and has no effect on versions of kubernetes >= 1.18.0. Processing ONLY resources that have the 'ingressClassName' field in Ingress equal to the class.")
   311  
   312  		ingressClassRes, err := kubeClient.NetworkingV1beta1().IngressClasses().Get(context.TODO(), *ingressClass, meta_v1.GetOptions{})
   313  		if err != nil {
   314  			glog.Fatalf("Error when getting IngressClass %v: %v", *ingressClass, err)
   315  		}
   316  
   317  		if ingressClassRes.Spec.Controller != k8s.IngressControllerName {
   318  			glog.Fatalf("IngressClass with name %v has an invalid Spec.Controller %v", ingressClassRes.Name, ingressClassRes.Spec.Controller)
   319  		}
   320  	}
   321  
   322  	var dynClient dynamic.Interface
   323  	if *appProtect || *ingressLink != "" {
   324  		dynClient, err = dynamic.NewForConfig(config)
   325  		if err != nil {
   326  			glog.Fatalf("Failed to create dynamic client: %v.", err)
   327  		}
   328  	}
   329  	var confClient k8s_nginx.Interface
   330  	if *enableCustomResources {
   331  		confClient, err = k8s_nginx.NewForConfig(config)
   332  		if err != nil {
   333  			glog.Fatalf("Failed to create a conf client: %v", err)
   334  		}
   335  
   336  		// required for emitting Events for VirtualServer
   337  		err = conf_scheme.AddToScheme(scheme.Scheme)
   338  		if err != nil {
   339  			glog.Fatalf("Failed to add configuration types to the scheme: %v", err)
   340  		}
   341  	}
   342  
   343  	nginxConfTemplatePath := "nginx.tmpl"
   344  	nginxIngressTemplatePath := "nginx.ingress.tmpl"
   345  	nginxVirtualServerTemplatePath := "nginx.virtualserver.tmpl"
   346  	nginxTransportServerTemplatePath := "nginx.transportserver.tmpl"
   347  	if *nginxPlus {
   348  		nginxConfTemplatePath = "nginx-plus.tmpl"
   349  		nginxIngressTemplatePath = "nginx-plus.ingress.tmpl"
   350  		nginxVirtualServerTemplatePath = "nginx-plus.virtualserver.tmpl"
   351  		nginxTransportServerTemplatePath = "nginx-plus.transportserver.tmpl"
   352  	}
   353  
   354  	if *mainTemplatePath != "" {
   355  		nginxConfTemplatePath = *mainTemplatePath
   356  	}
   357  	if *ingressTemplatePath != "" {
   358  		nginxIngressTemplatePath = *ingressTemplatePath
   359  	}
   360  	if *virtualServerTemplatePath != "" {
   361  		nginxVirtualServerTemplatePath = *virtualServerTemplatePath
   362  	}
   363  	if *transportServerTemplatePath != "" {
   364  		nginxTransportServerTemplatePath = *transportServerTemplatePath
   365  	}
   366  
   367  	var registry *prometheus.Registry
   368  	var managerCollector collectors.ManagerCollector
   369  	var controllerCollector collectors.ControllerCollector
   370  	var latencyCollector collectors.LatencyCollector
   371  	constLabels := map[string]string{"class": *ingressClass}
   372  	managerCollector = collectors.NewManagerFakeCollector()
   373  	controllerCollector = collectors.NewControllerFakeCollector()
   374  	latencyCollector = collectors.NewLatencyFakeCollector()
   375  
   376  	if *enablePrometheusMetrics {
   377  		registry = prometheus.NewRegistry()
   378  		managerCollector = collectors.NewLocalManagerMetricsCollector(constLabels)
   379  		controllerCollector = collectors.NewControllerMetricsCollector(*enableCustomResources, constLabels)
   380  		processCollector := collectors.NewNginxProcessesMetricsCollector(constLabels)
   381  		workQueueCollector := collectors.NewWorkQueueMetricsCollector(constLabels)
   382  
   383  		err = managerCollector.Register(registry)
   384  		if err != nil {
   385  			glog.Errorf("Error registering Manager Prometheus metrics: %v", err)
   386  		}
   387  
   388  		err = controllerCollector.Register(registry)
   389  		if err != nil {
   390  			glog.Errorf("Error registering Controller Prometheus metrics: %v", err)
   391  		}
   392  
   393  		err = processCollector.Register(registry)
   394  		if err != nil {
   395  			glog.Errorf("Error registering NginxProcess Prometheus metrics: %v", err)
   396  		}
   397  
   398  		err = workQueueCollector.Register(registry)
   399  		if err != nil {
   400  			glog.Errorf("Error registering WorkQueue Prometheus metrics: %v", err)
   401  		}
   402  	}
   403  
   404  	useFakeNginxManager := *proxyURL != ""
   405  	var nginxManager nginx.Manager
   406  	if useFakeNginxManager {
   407  		nginxManager = nginx.NewFakeManager("/etc/nginx")
   408  	} else {
   409  		nginxManager = nginx.NewLocalManager("/etc/nginx/", *nginxDebug, managerCollector, parseReloadTimeout(*appProtect, *nginxReloadTimeout))
   410  	}
   411  	nginxVersion := nginxManager.Version()
   412  	isPlus := strings.Contains(nginxVersion, "plus")
   413  	glog.Infof("Using %s", nginxVersion)
   414  
   415  	if *nginxPlus && !isPlus {
   416  		glog.Fatal("NGINX Plus flag enabled (-nginx-plus) without NGINX Plus binary")
   417  	} else if !*nginxPlus && isPlus {
   418  		glog.Fatal("NGINX Plus binary found without NGINX Plus flag (-nginx-plus)")
   419  	}
   420  
   421  	templateExecutor, err := version1.NewTemplateExecutor(nginxConfTemplatePath, nginxIngressTemplatePath)
   422  	if err != nil {
   423  		glog.Fatalf("Error creating TemplateExecutor: %v", err)
   424  	}
   425  
   426  	templateExecutorV2, err := version2.NewTemplateExecutor(nginxVirtualServerTemplatePath, nginxTransportServerTemplatePath)
   427  	if err != nil {
   428  		glog.Fatalf("Error creating TemplateExecutorV2: %v", err)
   429  	}
   430  
   431  	var aPPluginDone chan error
   432  	var aPAgentDone chan error
   433  
   434  	if *appProtect {
   435  		aPPluginDone = make(chan error, 1)
   436  		aPAgentDone = make(chan error, 1)
   437  
   438  		nginxManager.AppProtectAgentStart(aPAgentDone, *nginxDebug)
   439  		nginxManager.AppProtectPluginStart(aPPluginDone)
   440  	}
   441  
   442  	var sslRejectHandshake bool
   443  
   444  	if *defaultServerSecret != "" {
   445  		secret, err := getAndValidateSecret(kubeClient, *defaultServerSecret)
   446  		if err != nil {
   447  			glog.Fatalf("Error trying to get the default server TLS secret %v: %v", *defaultServerSecret, err)
   448  		}
   449  
   450  		bytes := configs.GenerateCertAndKeyFileContent(secret)
   451  		nginxManager.CreateSecret(configs.DefaultServerSecretName, bytes, nginx.TLSSecretFileMode)
   452  	} else {
   453  		_, err := os.Stat(configs.DefaultServerSecretPath)
   454  		if err != nil {
   455  			if os.IsNotExist(err) {
   456  				// file doesn't exist - it is OK! we will reject TLS connections in the default server
   457  				sslRejectHandshake = true
   458  			} else {
   459  				glog.Fatalf("Error checking the default server TLS cert and key in %s: %v", configs.DefaultServerSecretPath, err)
   460  			}
   461  		}
   462  	}
   463  
   464  	if *wildcardTLSSecret != "" {
   465  		secret, err := getAndValidateSecret(kubeClient, *wildcardTLSSecret)
   466  		if err != nil {
   467  			glog.Fatalf("Error trying to get the wildcard TLS secret %v: %v", *wildcardTLSSecret, err)
   468  		}
   469  
   470  		bytes := configs.GenerateCertAndKeyFileContent(secret)
   471  		nginxManager.CreateSecret(configs.WildcardSecretName, bytes, nginx.TLSSecretFileMode)
   472  	}
   473  
   474  	var prometheusSecret *api_v1.Secret
   475  	if *prometheusTLSSecretName != "" {
   476  		prometheusSecret, err = getAndValidateSecret(kubeClient, *prometheusTLSSecretName)
   477  		if err != nil {
   478  			glog.Fatalf("Error trying to get the prometheus TLS secret %v: %v", *prometheusTLSSecretName, err)
   479  		}
   480  	}
   481  
   482  	globalConfigurationValidator := createGlobalConfigurationValidator()
   483  
   484  	if *globalConfiguration != "" {
   485  		_, _, err := k8s.ParseNamespaceName(*globalConfiguration)
   486  		if err != nil {
   487  			glog.Fatalf("Error parsing the global-configuration argument: %v", err)
   488  		}
   489  
   490  		if !*enableCustomResources {
   491  			glog.Fatal("global-configuration flag requires -enable-custom-resources")
   492  		}
   493  	}
   494  
   495  	cfgParams := configs.NewDefaultConfigParams()
   496  
   497  	if *nginxConfigMaps != "" {
   498  		ns, name, err := k8s.ParseNamespaceName(*nginxConfigMaps)
   499  		if err != nil {
   500  			glog.Fatalf("Error parsing the nginx-configmaps argument: %v", err)
   501  		}
   502  		cfm, err := kubeClient.CoreV1().ConfigMaps(ns).Get(context.TODO(), name, meta_v1.GetOptions{})
   503  		if err != nil {
   504  			glog.Fatalf("Error when getting %v: %v", *nginxConfigMaps, err)
   505  		}
   506  		cfgParams = configs.ParseConfigMap(cfm, *nginxPlus, *appProtect)
   507  		if cfgParams.MainServerSSLDHParamFileContent != nil {
   508  			fileName, err := nginxManager.CreateDHParam(*cfgParams.MainServerSSLDHParamFileContent)
   509  			if err != nil {
   510  				glog.Fatalf("Configmap %s/%s: Could not update dhparams: %v", ns, name, err)
   511  			} else {
   512  				cfgParams.MainServerSSLDHParam = fileName
   513  			}
   514  		}
   515  		if cfgParams.MainTemplate != nil {
   516  			err = templateExecutor.UpdateMainTemplate(cfgParams.MainTemplate)
   517  			if err != nil {
   518  				glog.Fatalf("Error updating NGINX main template: %v", err)
   519  			}
   520  		}
   521  		if cfgParams.IngressTemplate != nil {
   522  			err = templateExecutor.UpdateIngressTemplate(cfgParams.IngressTemplate)
   523  			if err != nil {
   524  				glog.Fatalf("Error updating ingress template: %v", err)
   525  			}
   526  		}
   527  	}
   528  	staticCfgParams := &configs.StaticConfigParams{
   529  		HealthStatus:                   *healthStatus,
   530  		HealthStatusURI:                *healthStatusURI,
   531  		NginxStatus:                    *nginxStatus,
   532  		NginxStatusAllowCIDRs:          allowedCIDRs,
   533  		NginxStatusPort:                *nginxStatusPort,
   534  		StubStatusOverUnixSocketForOSS: *enablePrometheusMetrics,
   535  		TLSPassthrough:                 *enableTLSPassthrough,
   536  		EnableSnippets:                 *enableSnippets,
   537  		NginxServiceMesh:               *spireAgentAddress != "",
   538  		MainAppProtectLoadModule:       *appProtect,
   539  		EnableLatencyMetrics:           *enableLatencyMetrics,
   540  		EnablePreviewPolicies:          *enablePreviewPolicies,
   541  		SSLRejectHandshake:             sslRejectHandshake,
   542  	}
   543  
   544  	ngxConfig := configs.GenerateNginxMainConfig(staticCfgParams, cfgParams)
   545  	content, err := templateExecutor.ExecuteMainConfigTemplate(ngxConfig)
   546  	if err != nil {
   547  		glog.Fatalf("Error generating NGINX main config: %v", err)
   548  	}
   549  	nginxManager.CreateMainConfig(content)
   550  
   551  	nginxManager.UpdateConfigVersionFile(ngxConfig.OpenTracingLoadModule)
   552  
   553  	nginxManager.SetOpenTracing(ngxConfig.OpenTracingLoadModule)
   554  
   555  	if ngxConfig.OpenTracingLoadModule {
   556  		err := nginxManager.CreateOpenTracingTracerConfig(cfgParams.MainOpenTracingTracerConfig)
   557  		if err != nil {
   558  			glog.Fatalf("Error creating OpenTracing tracer config file: %v", err)
   559  		}
   560  	}
   561  
   562  	if *enableTLSPassthrough {
   563  		var emptyFile []byte
   564  		nginxManager.CreateTLSPassthroughHostsConfig(emptyFile)
   565  	}
   566  
   567  	nginxDone := make(chan error, 1)
   568  	nginxManager.Start(nginxDone)
   569  
   570  	var plusClient *client.NginxClient
   571  
   572  	if *nginxPlus && !useFakeNginxManager {
   573  		httpClient := getSocketClient("/var/lib/nginx/nginx-plus-api.sock")
   574  		plusClient, err = client.NewNginxClient(httpClient, "http://nginx-plus-api/api")
   575  		if err != nil {
   576  			glog.Fatalf("Failed to create NginxClient for Plus: %v", err)
   577  		}
   578  		nginxManager.SetPlusClients(plusClient, httpClient)
   579  	}
   580  
   581  	var plusCollector *nginxCollector.NginxPlusCollector
   582  	var syslogListener metrics.SyslogListener
   583  	syslogListener = metrics.NewSyslogFakeServer()
   584  	if *enablePrometheusMetrics {
   585  		upstreamServerVariableLabels := []string{"service", "resource_type", "resource_name", "resource_namespace"}
   586  		upstreamServerPeerVariableLabelNames := []string{"pod_name"}
   587  		if staticCfgParams.NginxServiceMesh {
   588  			upstreamServerPeerVariableLabelNames = append(upstreamServerPeerVariableLabelNames, "pod_owner")
   589  		}
   590  		if *nginxPlus {
   591  			streamUpstreamServerVariableLabels := []string{"service", "resource_type", "resource_name", "resource_namespace"}
   592  			streamUpstreamServerPeerVariableLabelNames := []string{"pod_name"}
   593  
   594  			serverZoneVariableLabels := []string{"resource_type", "resource_name", "resource_namespace"}
   595  			streamServerZoneVariableLabels := []string{"resource_type", "resource_name", "resource_namespace"}
   596  			variableLabelNames := nginxCollector.NewVariableLabelNames(upstreamServerVariableLabels, serverZoneVariableLabels, upstreamServerPeerVariableLabelNames,
   597  				streamUpstreamServerVariableLabels, streamServerZoneVariableLabels, streamUpstreamServerPeerVariableLabelNames)
   598  			plusCollector = nginxCollector.NewNginxPlusCollector(plusClient, "nginx_ingress_nginxplus", variableLabelNames, constLabels)
   599  			go metrics.RunPrometheusListenerForNginxPlus(*prometheusMetricsListenPort, plusCollector, registry, prometheusSecret)
   600  		} else {
   601  			httpClient := getSocketClient("/var/lib/nginx/nginx-status.sock")
   602  			client, err := metrics.NewNginxMetricsClient(httpClient)
   603  			if err != nil {
   604  				glog.Errorf("Error creating the Nginx client for Prometheus metrics: %v", err)
   605  			}
   606  			go metrics.RunPrometheusListenerForNginx(*prometheusMetricsListenPort, client, registry, constLabels, prometheusSecret)
   607  		}
   608  		if *enableLatencyMetrics {
   609  			latencyCollector = collectors.NewLatencyMetricsCollector(constLabels, upstreamServerVariableLabels, upstreamServerPeerVariableLabelNames)
   610  			if err := latencyCollector.Register(registry); err != nil {
   611  				glog.Errorf("Error registering Latency Prometheus metrics: %v", err)
   612  			}
   613  			syslogListener = metrics.NewLatencyMetricsListener("/var/lib/nginx/nginx-syslog.sock", latencyCollector)
   614  			go syslogListener.Run()
   615  		}
   616  	}
   617  
   618  	isWildcardEnabled := *wildcardTLSSecret != ""
   619  	cnf := configs.NewConfigurator(nginxManager, staticCfgParams, cfgParams, templateExecutor,
   620  		templateExecutorV2, *nginxPlus, isWildcardEnabled, plusCollector, *enablePrometheusMetrics, latencyCollector, *enableLatencyMetrics)
   621  	controllerNamespace := os.Getenv("POD_NAMESPACE")
   622  
   623  	transportServerValidator := cr_validation.NewTransportServerValidator(*enableTLSPassthrough, *enableSnippets, *nginxPlus)
   624  	virtualServerValidator := cr_validation.NewVirtualServerValidator(*nginxPlus)
   625  
   626  	lbcInput := k8s.NewLoadBalancerControllerInput{
   627  		KubeClient:                   kubeClient,
   628  		ConfClient:                   confClient,
   629  		DynClient:                    dynClient,
   630  		ResyncPeriod:                 30 * time.Second,
   631  		Namespace:                    *watchNamespace,
   632  		NginxConfigurator:            cnf,
   633  		DefaultServerSecret:          *defaultServerSecret,
   634  		AppProtectEnabled:            *appProtect,
   635  		IsNginxPlus:                  *nginxPlus,
   636  		IngressClass:                 *ingressClass,
   637  		UseIngressClassOnly:          *useIngressClassOnly,
   638  		ExternalServiceName:          *externalService,
   639  		IngressLink:                  *ingressLink,
   640  		ControllerNamespace:          controllerNamespace,
   641  		ReportIngressStatus:          *reportIngressStatus,
   642  		IsLeaderElectionEnabled:      *leaderElectionEnabled,
   643  		LeaderElectionLockName:       *leaderElectionLockName,
   644  		WildcardTLSSecret:            *wildcardTLSSecret,
   645  		ConfigMaps:                   *nginxConfigMaps,
   646  		GlobalConfiguration:          *globalConfiguration,
   647  		AreCustomResourcesEnabled:    *enableCustomResources,
   648  		EnablePreviewPolicies:        *enablePreviewPolicies,
   649  		MetricsCollector:             controllerCollector,
   650  		GlobalConfigurationValidator: globalConfigurationValidator,
   651  		TransportServerValidator:     transportServerValidator,
   652  		VirtualServerValidator:       virtualServerValidator,
   653  		SpireAgentAddress:            *spireAgentAddress,
   654  		InternalRoutesEnabled:        *enableInternalRoutes,
   655  		IsPrometheusEnabled:          *enablePrometheusMetrics,
   656  		IsLatencyMetricsEnabled:      *enableLatencyMetrics,
   657  		IsTLSPassthroughEnabled:      *enableTLSPassthrough,
   658  		SnippetsEnabled:              *enableSnippets,
   659  	}
   660  
   661  	lbc := k8s.NewLoadBalancerController(lbcInput)
   662  
   663  	if *readyStatus {
   664  		go func() {
   665  			port := fmt.Sprintf(":%v", *readyStatusPort)
   666  			s := http.NewServeMux()
   667  			s.HandleFunc("/nginx-ready", ready(lbc))
   668  			glog.Fatal(http.ListenAndServe(port, s))
   669  		}()
   670  	}
   671  
   672  	if *appProtect {
   673  		go handleTerminationWithAppProtect(lbc, nginxManager, syslogListener, nginxDone, aPAgentDone, aPPluginDone)
   674  	} else {
   675  		go handleTermination(lbc, nginxManager, syslogListener, nginxDone)
   676  	}
   677  
   678  	lbc.Run()
   679  
   680  	for {
   681  		glog.Info("Waiting for the controller to exit...")
   682  		time.Sleep(30 * time.Second)
   683  	}
   684  }
   685  
   686  func createGlobalConfigurationValidator() *cr_validation.GlobalConfigurationValidator {
   687  	forbiddenListenerPorts := map[int]bool{
   688  		80:  true,
   689  		443: true,
   690  	}
   691  
   692  	if *nginxStatus {
   693  		forbiddenListenerPorts[*nginxStatusPort] = true
   694  	}
   695  	if *enablePrometheusMetrics {
   696  		forbiddenListenerPorts[*prometheusMetricsListenPort] = true
   697  	}
   698  
   699  	return cr_validation.NewGlobalConfigurationValidator(forbiddenListenerPorts)
   700  }
   701  
   702  func handleTermination(lbc *k8s.LoadBalancerController, nginxManager nginx.Manager, listener metrics.SyslogListener, nginxDone chan error) {
   703  	signalChan := make(chan os.Signal, 1)
   704  	signal.Notify(signalChan, syscall.SIGTERM)
   705  
   706  	exitStatus := 0
   707  	exited := false
   708  
   709  	select {
   710  	case err := <-nginxDone:
   711  		if err != nil {
   712  			glog.Errorf("nginx command exited with an error: %v", err)
   713  			exitStatus = 1
   714  		} else {
   715  			glog.Info("nginx command exited successfully")
   716  		}
   717  		exited = true
   718  	case <-signalChan:
   719  		glog.Info("Received SIGTERM, shutting down")
   720  	}
   721  
   722  	glog.Info("Shutting down the controller")
   723  	lbc.Stop()
   724  
   725  	if !exited {
   726  		glog.Info("Shutting down NGINX")
   727  		nginxManager.Quit()
   728  		<-nginxDone
   729  	}
   730  	listener.Stop()
   731  
   732  	glog.Infof("Exiting with a status: %v", exitStatus)
   733  	os.Exit(exitStatus)
   734  }
   735  
   736  // getSocketClient gets an http.Client with the a unix socket transport.
   737  func getSocketClient(sockPath string) *http.Client {
   738  	return &http.Client{
   739  		Transport: &http.Transport{
   740  			DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
   741  				return net.Dial("unix", sockPath)
   742  			},
   743  		},
   744  	}
   745  }
   746  
   747  // validateResourceName validates the name of a resource
   748  func validateResourceName(lock string) error {
   749  	allErrs := validation.IsDNS1123Subdomain(lock)
   750  	if len(allErrs) > 0 {
   751  		return fmt.Errorf("invalid resource name %v: %v", lock, allErrs)
   752  	}
   753  	return nil
   754  }
   755  
   756  // validatePort makes sure a given port is inside the valid port range for its usage
   757  func validatePort(port int) error {
   758  	if port < 1024 || port > 65535 {
   759  		return fmt.Errorf("port outside of valid port range [1024 - 65535]: %v", port)
   760  	}
   761  	return nil
   762  }
   763  
   764  // parseNginxStatusAllowCIDRs converts a comma separated CIDR/IP address string into an array of CIDR/IP addresses.
   765  // It returns an array of the valid CIDR/IP addresses or an error if given an invalid address.
   766  func parseNginxStatusAllowCIDRs(input string) (cidrs []string, err error) {
   767  	cidrsArray := strings.Split(input, ",")
   768  	for _, cidr := range cidrsArray {
   769  		trimmedCidr := strings.TrimSpace(cidr)
   770  		err := validateCIDRorIP(trimmedCidr)
   771  		if err != nil {
   772  			return cidrs, err
   773  		}
   774  		cidrs = append(cidrs, trimmedCidr)
   775  	}
   776  	return cidrs, nil
   777  }
   778  
   779  // validateCIDRorIP makes sure a given string is either a valid CIDR block or IP address.
   780  // It an error if it is not valid.
   781  func validateCIDRorIP(cidr string) error {
   782  	if cidr == "" {
   783  		return fmt.Errorf("invalid CIDR address: an empty string is an invalid CIDR block or IP address")
   784  	}
   785  	_, _, err := net.ParseCIDR(cidr)
   786  	if err == nil {
   787  		return nil
   788  	}
   789  	ip := net.ParseIP(cidr)
   790  	if ip == nil {
   791  		return fmt.Errorf("invalid IP address: %v", cidr)
   792  	}
   793  	return nil
   794  }
   795  
   796  // getAndValidateSecret gets and validates a secret.
   797  func getAndValidateSecret(kubeClient *kubernetes.Clientset, secretNsName string) (secret *api_v1.Secret, err error) {
   798  	ns, name, err := k8s.ParseNamespaceName(secretNsName)
   799  	if err != nil {
   800  		return nil, fmt.Errorf("could not parse the %v argument: %w", secretNsName, err)
   801  	}
   802  	secret, err = kubeClient.CoreV1().Secrets(ns).Get(context.TODO(), name, meta_v1.GetOptions{})
   803  	if err != nil {
   804  		return nil, fmt.Errorf("could not get %v: %w", secretNsName, err)
   805  	}
   806  	err = secrets.ValidateTLSSecret(secret)
   807  	if err != nil {
   808  		return nil, fmt.Errorf("%v is invalid: %w", secretNsName, err)
   809  	}
   810  	return secret, nil
   811  }
   812  
   813  const (
   814  	locationFmt    = `/[^\s{};]*`
   815  	locationErrMsg = "must start with / and must not include any whitespace character, `{`, `}` or `;`"
   816  )
   817  
   818  var locationRegexp = regexp.MustCompile("^" + locationFmt + "$")
   819  
   820  func validateLocation(location string) error {
   821  	if location == "" || location == "/" {
   822  		return fmt.Errorf("invalid location format: '%v' is an invalid location", location)
   823  	}
   824  	if !locationRegexp.MatchString(location) {
   825  		msg := validation.RegexError(locationErrMsg, locationFmt, "/path", "/path/subpath-123")
   826  		return fmt.Errorf("invalid location format: %v", msg)
   827  	}
   828  	return nil
   829  }
   830  
   831  func handleTerminationWithAppProtect(lbc *k8s.LoadBalancerController, nginxManager nginx.Manager, listener metrics.SyslogListener, nginxDone, agentDone, pluginDone chan error) {
   832  	signalChan := make(chan os.Signal, 1)
   833  	signal.Notify(signalChan, syscall.SIGTERM)
   834  
   835  	select {
   836  	case err := <-nginxDone:
   837  		glog.Fatalf("nginx command exited unexpectedly with status: %v", err)
   838  	case err := <-pluginDone:
   839  		glog.Fatalf("AppProtectPlugin command exited unexpectedly with status: %v", err)
   840  	case err := <-agentDone:
   841  		glog.Fatalf("AppProtectAgent command exited unexpectedly with status: %v", err)
   842  	case <-signalChan:
   843  		glog.Infof("Received SIGTERM, shutting down")
   844  		lbc.Stop()
   845  		nginxManager.Quit()
   846  		<-nginxDone
   847  		nginxManager.AppProtectPluginQuit()
   848  		<-pluginDone
   849  		nginxManager.AppProtectAgentQuit()
   850  		<-agentDone
   851  		listener.Stop()
   852  	}
   853  	glog.Info("Exiting successfully")
   854  	os.Exit(0)
   855  }
   856  
   857  func parseReloadTimeout(appProtectEnabled bool, timeout int) time.Duration {
   858  	const defaultTimeout = 4000 * time.Millisecond
   859  	const defaultTimeoutAppProtect = 20000 * time.Millisecond
   860  
   861  	if timeout != 0 {
   862  		return time.Duration(timeout) * time.Millisecond
   863  	}
   864  
   865  	if appProtectEnabled {
   866  		return defaultTimeoutAppProtect
   867  	}
   868  
   869  	return defaultTimeout
   870  }
   871  
   872  func ready(lbc *k8s.LoadBalancerController) http.HandlerFunc {
   873  	return func(w http.ResponseWriter, _ *http.Request) {
   874  		if !lbc.IsNginxReady() {
   875  			http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
   876  			return
   877  		}
   878  		w.WriteHeader(http.StatusOK)
   879  		fmt.Fprintln(w, "Ready")
   880  	}
   881  }
   882  
   883  func minVersion(min string) (v *util_version.Version) {
   884  	minVer, err := util_version.ParseGeneric(min)
   885  	if err != nil {
   886  		glog.Fatalf("unexpected error parsing minimum supported version: %v", err)
   887  	}
   888  
   889  	return minVer
   890  }