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 }