diff --git a/example/kube/README.md b/example/kube/README.md index 930724c43..7878055c6 100644 --- a/example/kube/README.md +++ b/example/kube/README.md @@ -1,17 +1,21 @@ # Authelia on Kubernetes Authelia is now available on Kube in order to protect your most critical -applications using 2-factor authentication. +applications using 2-factor authentication and Single Sign-On. + +This example leverages [ingress-nginx](https://github.com/kubernetes/ingress-nginx) +v0.13.0 to delegate authentications and authorizations to Authelia within +the cluster. ## Getting started -In order to deploy Authelia on Kube, we must have a cluster at hand. If you +In order to deploy Authelia on Kube, you must have a cluster at hand. If you don't, please follow the next section otherwise skip it and go to the next. ### Set up a Kube cluster -Hopefully for us, spawning a development cluster from scratch has become very +Hopefully, spawning a development cluster from scratch has become very easy lately with the use of **minikube**. This project creates a VM on your computer and start a Kube cluster inside it. It also configure a CLI called kubectl so that you can deploy applications in the cluster right away. @@ -53,50 +57,56 @@ with the IP of your VM given by minikube: 192.168.39.26 app2.kube.example.com 192.168.39.26 mail.kube.example.com 192.168.39.26 home.kube.example.com + +# The domain of the private docker registry holding dev version of Authelia +192.168.39.26 registry.kube.example.com ``` Once done, you can visit http://home.kube.example.com and follow the -instructions written in the page +instructions written in the page. ## How does it work? ### Authentication via Authelia In a Kube clusters, the routing logic of requests is handled by ingress -controllers which follow the provided ingress configurations. +controllers following rules provided by ingress configurations. -In this setup, requests goes through a [ingress-nginx](https://github.com/kubernetes/ingress-nginx) -controller which forward verification requests to Authelia in order to allow -or deny access. +In this example, [ingress-nginx](https://github.com/kubernetes/ingress-nginx) +controller has been installed to handle the incoming requests. Some of them +(specified in the ingress configuration) are forwarded to Authelia so that +it can verify whether they are allowed and should reach the protected endpoint. The authentication is provided at the ingress level by an annotation called `nginx.ingress.kubernetes.io/auth-url` that is filled with the URL of Authelia's verification endpoint. -The ingress controller also requires the ingress provides the URL of the -authentication portal in case the user is not yet authenticated. +The ingress controller also requires the URL to the +authentication portal so that the user can be redirected in case she is not +yet authenticated. Those annotations can be seen in `apps/secure-ingress.yml` configuration. ### Production grade infrastructure -What is great about using [ingress-nginx](https://github.com/kubernetes/ingress-nginx) +What is great with using [ingress-nginx](https://github.com/kubernetes/ingress-nginx) is that it is compatible with [kube-lego](https://github.com/jetstack/kube-lego) -that makes renewal of SSL certifiactes automatic. +which removes the usual pain of manual SSL certificate renewals. It uses +letsencrypt to issue and renew certificates every three month without any +manual intervention. ## What do I need know to deploy it in my cluster? Given your cluster is already made of an LDAP server, a Redis cluster, a Mongo cluster and a SMTP server, you'll only need to install the ingress-controller -and Authelia whose configurations are respectively in `ingress-controller` and -`authelia` directories. +and Authelia whose kubernetes deployment configurations are respectively in +`ingress-controller` and `authelia` directories. A template configuration +is provided there, you just need to create the configmap to use it within +the cluster. ### I'm already using ingress-nginx -If you're already using ingress-nginx as your ingress controller, the only -thing you'll need to change is the nginx template used by the controller to -make it compatible with Authelia. The template is located in -`ingress-controller/configs/nginx.tmpl`. Make it a configmap and pass it to -your controller arguments. +If you're already using ingress-nginx as your ingress controller, you only +need to install Authelia with its configuration and that's it! ## Questions diff --git a/example/kube/authelia/configs/config.yml b/example/kube/authelia/configs/config.yml index e19051a84..4d37fd158 100644 --- a/example/kube/authelia/configs/config.yml +++ b/example/kube/authelia/configs/config.yml @@ -180,7 +180,8 @@ storage: # Settings to connect to mongo server mongo: - url: mongodb://mongo-service/authelia + url: mongodb://mongo-service + database: authelia # Configuration of the notification system. # diff --git a/example/kube/authelia/deployment.yml b/example/kube/authelia/deployment.yml index e09ab2748..c0010ee3f 100644 --- a/example/kube/authelia/deployment.yml +++ b/example/kube/authelia/deployment.yml @@ -18,7 +18,8 @@ spec: spec: containers: - name: authelia - image: clems4ever/authelia:v3.7.0 + image: localhost:5000/authelia:latest + imagePullPolicy: Always ports: - containerPort: 80 volumeMounts: diff --git a/example/kube/bootstrap.sh b/example/kube/bootstrap.sh index bdcd20ac9..7c5493496 100755 --- a/example/kube/bootstrap.sh +++ b/example/kube/bootstrap.sh @@ -19,7 +19,6 @@ start_apps() { } start_ingress_controller() { - kubectl create configmap authelia-ingress-controller-config --namespace=authelia --from-file=ingress-controller/configs/nginx.tmpl kubectl apply -f ingress-controller } @@ -43,12 +42,17 @@ start_ldap() { kubectl apply -f ldap } +start_docker_registry() { + kubectl apply -f docker-registry +} + # Create the Authelia namespace in the cluster create_namespace() { kubectl apply -f namespace.yml } create_namespace +start_docker_registry start_storage start_ldap start_mailcatcher diff --git a/example/kube/build_and_push.sh b/example/kube/build_and_push.sh new file mode 100755 index 000000000..de6310054 --- /dev/null +++ b/example/kube/build_and_push.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +build_and_push_authelia() { + cd ../../ + docker build -t registry.kube.example.com:80/authelia . + docker push registry.kube.example.com:80/authelia +} + +build_and_push_authelia diff --git a/example/kube/docker-registry/daemonset.yml b/example/kube/docker-registry/daemonset.yml new file mode 100644 index 000000000..3d370a30d --- /dev/null +++ b/example/kube/docker-registry/daemonset.yml @@ -0,0 +1,35 @@ +--- +apiVersion: extensions/v1beta1 +kind: DaemonSet +metadata: + name: kube-registry-proxy + namespace: kube-system + labels: + k8s-app: kube-registry-proxy + kubernetes.io/cluster-service: "true" + version: v0.4 +spec: + template: + metadata: + labels: + k8s-app: kube-registry-proxy + kubernetes.io/name: "kube-registry-proxy" + kubernetes.io/cluster-service: "true" + version: v0.4 + spec: + containers: + - name: kube-registry-proxy + image: gcr.io/google_containers/kube-registry-proxy:0.4 + resources: + limits: + cpu: 100m + memory: 50Mi + env: + - name: REGISTRY_HOST + value: kube-registry.kube-system.svc.cluster.local + - name: REGISTRY_PORT + value: "5000" + ports: + - name: registry + containerPort: 80 + hostPort: 5000 diff --git a/example/kube/docker-registry/ingress.yml b/example/kube/docker-registry/ingress.yml new file mode 100644 index 000000000..3c71a94d6 --- /dev/null +++ b/example/kube/docker-registry/ingress.yml @@ -0,0 +1,18 @@ +--- +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: registry-ingress + namespace: kube-system + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/proxy-body-size: 100m # Avoid 413 Request entity too large +spec: + rules: + - host: registry.kube.example.com + http: + paths: + - path: / + backend: + serviceName: kube-registry + servicePort: 5000 diff --git a/example/kube/docker-registry/replicationcontroller.yml b/example/kube/docker-registry/replicationcontroller.yml new file mode 100644 index 000000000..45881785c --- /dev/null +++ b/example/kube/docker-registry/replicationcontroller.yml @@ -0,0 +1,44 @@ +--- +apiVersion: v1 +kind: ReplicationController +metadata: + name: kube-registry-v0 + namespace: kube-system + labels: + k8s-app: kube-registry-upstream + version: v0 + kubernetes.io/cluster-service: "true" +spec: + replicas: 1 + selector: + k8s-app: kube-registry-upstream + version: v0 + template: + metadata: + labels: + k8s-app: kube-registry-upstream + version: v0 + kubernetes.io/cluster-service: "true" + spec: + containers: + - name: registry + image: registry:2 + resources: + limits: + cpu: 100m + memory: 100Mi + env: + - name: REGISTRY_HTTP_ADDR + value: :5000 + - name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY + value: /var/lib/registry + volumeMounts: + - name: image-store + mountPath: /var/lib/registry + ports: + - containerPort: 5000 + name: registry + protocol: TCP + volumes: + - name: image-store + emptyDir: {} diff --git a/example/kube/docker-registry/service.yml b/example/kube/docker-registry/service.yml new file mode 100644 index 000000000..cbadaaf9c --- /dev/null +++ b/example/kube/docker-registry/service.yml @@ -0,0 +1,17 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: kube-registry + namespace: kube-system + labels: + k8s-app: kube-registry-upstream + kubernetes.io/cluster-service: "true" + kubernetes.io/name: "KubeRegistry" +spec: + selector: + k8s-app: kube-registry-upstream + ports: + - name: registry + port: 5000 + protocol: TCP diff --git a/example/kube/ingress-controller/configs/nginx.tmpl b/example/kube/ingress-controller/configs/nginx.tmpl deleted file mode 100644 index 6106d6781..000000000 --- a/example/kube/ingress-controller/configs/nginx.tmpl +++ /dev/null @@ -1,887 +0,0 @@ -{{ $all := . }} -{{ $servers := .Servers }} -{{ $cfg := .Cfg }} -{{ $IsIPV6Enabled := .IsIPV6Enabled }} -{{ $healthzURI := .HealthzURI }} -{{ $backends := .Backends }} -{{ $proxyHeaders := .ProxySetHeaders }} -{{ $addHeaders := .AddHeaders }} - -{{ if $cfg.EnableModsecurity }} -load_module /etc/nginx/modules/ngx_http_modsecurity_module.so; -{{ end }} - -{{ if $cfg.EnableOpentracing }} -load_module /etc/nginx/modules/ngx_http_opentracing_module.so; -{{ end }} - -{{ if (and $cfg.EnableOpentracing (ne $cfg.ZipkinCollectorHost "")) }} -load_module /etc/nginx/modules/ngx_http_zipkin_module.so; -{{ end }} - -daemon off; - -worker_processes {{ $cfg.WorkerProcesses }}; -pid /run/nginx.pid; -{{ if ne .MaxOpenFiles 0 }} -worker_rlimit_nofile {{ .MaxOpenFiles }}; -{{ end}} - -{{/* http://nginx.org/en/docs/ngx_core_module.html#worker_shutdown_timeout */}} -{{/* avoid waiting too long during a reload */}} -worker_shutdown_timeout {{ $cfg.WorkerShutdownTimeout }} ; - -events { - multi_accept on; - worker_connections {{ $cfg.MaxWorkerConnections }}; - use epoll; -} - -http { - {{/* we use the value of the header X-Forwarded-For to be able to use the geo_ip module */}} - {{ if $cfg.UseProxyProtocol }} - real_ip_header proxy_protocol; - {{ else }} - real_ip_header {{ $cfg.ForwardedForHeader }}; - {{ end }} - - real_ip_recursive on; - {{ range $trusted_ip := $cfg.ProxyRealIPCIDR }} - set_real_ip_from {{ $trusted_ip }}; - {{ end }} - - {{/* databases used to determine the country depending on the client IP address */}} - {{/* http://nginx.org/en/docs/http/ngx_http_geoip_module.html */}} - {{/* this is require to calculate traffic for individual country using GeoIP in the status page */}} - geoip_country /etc/nginx/GeoIP.dat; - geoip_city /etc/nginx/GeoLiteCity.dat; - geoip_proxy_recursive on; - - {{ if $cfg.EnableVtsStatus }} - vhost_traffic_status_zone shared:vhost_traffic_status:{{ $cfg.VtsStatusZoneSize }}; - vhost_traffic_status_filter_by_set_key {{ $cfg.VtsDefaultFilterKey }}; - {{ end }} - - sendfile on; - - aio threads; - aio_write on; - - tcp_nopush on; - tcp_nodelay on; - - log_subrequest on; - - reset_timedout_connection on; - - keepalive_timeout {{ $cfg.KeepAlive }}s; - keepalive_requests {{ $cfg.KeepAliveRequests }}; - - client_header_buffer_size {{ $cfg.ClientHeaderBufferSize }}; - client_header_timeout {{ $cfg.ClientHeaderTimeout }}s; - large_client_header_buffers {{ $cfg.LargeClientHeaderBuffers }}; - client_body_buffer_size {{ $cfg.ClientBodyBufferSize }}; - client_body_timeout {{ $cfg.ClientBodyTimeout }}s; - - http2_max_field_size {{ $cfg.HTTP2MaxFieldSize }}; - http2_max_header_size {{ $cfg.HTTP2MaxHeaderSize }}; - - types_hash_max_size 2048; - server_names_hash_max_size {{ $cfg.ServerNameHashMaxSize }}; - server_names_hash_bucket_size {{ $cfg.ServerNameHashBucketSize }}; - map_hash_bucket_size {{ $cfg.MapHashBucketSize }}; - - proxy_headers_hash_max_size {{ $cfg.ProxyHeadersHashMaxSize }}; - proxy_headers_hash_bucket_size {{ $cfg.ProxyHeadersHashBucketSize }}; - - variables_hash_bucket_size {{ $cfg.VariablesHashBucketSize }}; - variables_hash_max_size {{ $cfg.VariablesHashMaxSize }}; - - underscores_in_headers {{ if $cfg.EnableUnderscoresInHeaders }}on{{ else }}off{{ end }}; - ignore_invalid_headers {{ if $cfg.IgnoreInvalidHeaders }}on{{ else }}off{{ end }}; - - {{ if $cfg.EnableOpentracing }} - opentracing on; - {{ end }} - - {{ if (and $cfg.EnableOpentracing (ne $cfg.ZipkinCollectorHost "")) }} - zipkin_collector_host {{ $cfg.ZipkinCollectorHost }}; - zipkin_collector_port {{ $cfg.ZipkinCollectorPort }}; - zipkin_service_name {{ $cfg.ZipkinServiceName }}; - {{ end }} - - include /etc/nginx/mime.types; - default_type text/html; - - {{ if $cfg.EnableBrotli }} - brotli on; - brotli_comp_level {{ $cfg.BrotliLevel }}; - brotli_types {{ $cfg.BrotliTypes }}; - {{ end }} - - {{ if $cfg.UseGzip }} - gzip on; - gzip_comp_level 5; - gzip_http_version 1.1; - gzip_min_length 256; - gzip_types {{ $cfg.GzipTypes }}; - gzip_proxied any; - gzip_vary on; - {{ end }} - - # Custom headers for response - {{ range $k, $v := $addHeaders }} - add_header {{ $k }} "{{ $v }}"; - {{ end }} - - server_tokens {{ if $cfg.ShowServerTokens }}on{{ else }}off{{ end }}; - - # disable warnings - uninitialized_variable_warn off; - - # Additional available variables: - # $namespace - # $ingress_name - # $service_name - log_format upstreaminfo {{ if $cfg.LogFormatEscapeJSON }}escape=json {{ end }}'{{ buildLogFormatUpstream $cfg }}'; - - {{/* map urls that should not appear in access.log */}} - {{/* http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log */}} - map $request_uri $loggable { - {{ range $reqUri := $cfg.SkipAccessLogURLs }} - {{ $reqUri }} 0;{{ end }} - default 1; - } - - {{ if $cfg.DisableAccessLog }} - access_log off; - {{ else }} - access_log {{ $cfg.AccessLogPath }} upstreaminfo if=$loggable; - {{ end }} - error_log {{ $cfg.ErrorLogPath }} {{ $cfg.ErrorLogLevel }}; - - {{ buildResolvers $cfg.Resolver }} - - {{/* Whenever nginx proxies a request without a "Connection" header, the "Connection" header is set to "close" */}} - {{/* when making the target request. This means that you cannot simply use */}} - {{/* "proxy_set_header Connection $http_connection" for WebSocket support because in this case, the */}} - {{/* "Connection" header would be set to "" whenever the original request did not have a "Connection" header, */}} - {{/* which would mean no "Connection" header would be in the target request. Since this would deviate from */}} - {{/* normal nginx behavior we have to use this approach. */}} - # Retain the default nginx handling of requests without a "Connection" header - map $http_upgrade $connection_upgrade { - default upgrade; - '' close; - } - - map {{ buildForwardedFor $cfg.ForwardedForHeader }} $the_real_ip { - {{ if $cfg.UseProxyProtocol }} - # Get IP address from Proxy Protocol - default $proxy_protocol_addr; - {{ else }} - default $remote_addr; - {{ end }} - } - - # trust http_x_forwarded_proto headers correctly indicate ssl offloading - map $http_x_forwarded_proto $pass_access_scheme { - default $http_x_forwarded_proto; - '' $scheme; - } - - map $http_x_forwarded_port $pass_server_port { - default $http_x_forwarded_port; - '' $server_port; - } - - map $http_x_forwarded_host $best_http_host { - default $http_x_forwarded_host; - '' $this_host; - } - - {{ if $all.IsSSLPassthroughEnabled }} - # map port {{ $all.ListenPorts.SSLProxy }} to 443 for header X-Forwarded-Port - map $pass_server_port $pass_port { - {{ $all.ListenPorts.SSLProxy }} 443; - default $pass_server_port; - } - {{ else }} - map $pass_server_port $pass_port { - 443 443; - default $pass_server_port; - } - {{ end }} - - # Obtain best http host - map $http_host $this_host { - default $http_host; - '' $host; - } - - {{ if $cfg.ComputeFullForwardedFor }} - # We can't use $proxy_add_x_forwarded_for because the realip module - # replaces the remote_addr too soon - map $http_x_forwarded_for $full_x_forwarded_for { - {{ if $all.Cfg.UseProxyProtocol }} - default "$http_x_forwarded_for, $proxy_protocol_addr"; - '' "$proxy_protocol_addr"; - {{ else }} - default "$http_x_forwarded_for, $realip_remote_addr"; - '' "$realip_remote_addr"; - {{ end}} - } - {{ end }} - - server_name_in_redirect off; - port_in_redirect off; - - ssl_protocols {{ $cfg.SSLProtocols }}; - - # turn on session caching to drastically improve performance - {{ if $cfg.SSLSessionCache }} - ssl_session_cache builtin:1000 shared:SSL:{{ $cfg.SSLSessionCacheSize }}; - ssl_session_timeout {{ $cfg.SSLSessionTimeout }}; - {{ end }} - - # allow configuring ssl session tickets - ssl_session_tickets {{ if $cfg.SSLSessionTickets }}on{{ else }}off{{ end }}; - - {{ if not (empty $cfg.SSLSessionTicketKey ) }} - ssl_session_ticket_key /etc/nginx/tickets.key; - {{ end }} - - # slightly reduce the time-to-first-byte - ssl_buffer_size {{ $cfg.SSLBufferSize }}; - - {{ if not (empty $cfg.SSLCiphers) }} - # allow configuring custom ssl ciphers - ssl_ciphers '{{ $cfg.SSLCiphers }}'; - ssl_prefer_server_ciphers on; - {{ end }} - - {{ if not (empty $cfg.SSLDHParam) }} - # allow custom DH file http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_dhparam - ssl_dhparam {{ $cfg.SSLDHParam }}; - {{ end }} - - {{ if not $cfg.EnableDynamicTLSRecords }} - ssl_dyn_rec_size_lo 0; - {{ end }} - - ssl_ecdh_curve {{ $cfg.SSLECDHCurve }}; - - {{ if .CustomErrors }} - # Custom error pages - proxy_intercept_errors on; - {{ end }} - - {{ range $errCode := $cfg.CustomHTTPErrors }} - error_page {{ $errCode }} = @custom_{{ $errCode }};{{ end }} - - proxy_ssl_session_reuse on; - - {{ if $cfg.AllowBackendServerHeader }} - proxy_pass_header Server; - {{ end }} - - {{ if not (empty $cfg.HTTPSnippet) }} - # Custom code snippet configured in the configuration configmap - {{ $cfg.HTTPSnippet }} - {{ end }} - - {{ range $name, $upstream := $backends }} - {{ if eq $upstream.SessionAffinity.AffinityType "cookie" }} - upstream sticky-{{ $upstream.Name }} { - sticky hash={{ $upstream.SessionAffinity.CookieSessionAffinity.Hash }} name={{ $upstream.SessionAffinity.CookieSessionAffinity.Name }} httponly; - - {{ if (gt $cfg.UpstreamKeepaliveConnections 0) }} - keepalive {{ $cfg.UpstreamKeepaliveConnections }}; - {{ end }} - - {{ range $server := $upstream.Endpoints }}server {{ $server.Address | formatIP }}:{{ $server.Port }} max_fails={{ $server.MaxFails }} fail_timeout={{ $server.FailTimeout }}; - {{ end }} - - } - - {{ end }} - - - upstream {{ $upstream.Name }} { - # Load balance algorithm; empty for round robin, which is the default - {{ if ne $cfg.LoadBalanceAlgorithm "round_robin" }} - {{ $cfg.LoadBalanceAlgorithm }}; - {{ end }} - - {{ if $upstream.UpstreamHashBy }} - hash {{ $upstream.UpstreamHashBy }} consistent; - {{ end }} - - {{ if (gt $cfg.UpstreamKeepaliveConnections 0) }} - keepalive {{ $cfg.UpstreamKeepaliveConnections }}; - {{ end }} - - {{ range $server := $upstream.Endpoints }}server {{ $server.Address | formatIP }}:{{ $server.Port }} max_fails={{ $server.MaxFails }} fail_timeout={{ $server.FailTimeout }}; - {{ end }} - } - - {{ end }} - - {{/* build the maps that will be use to validate the Whitelist */}} - {{ range $index, $server := $servers }} - {{ range $location := $server.Locations }} - {{ $path := buildLocation $location }} - - {{ if isLocationAllowed $location }} - {{ if gt (len $location.Whitelist.CIDR) 0 }} - - # Deny for {{ print $server.Hostname $path }} - geo $the_real_ip {{ buildDenyVariable (print $server.Hostname "_" $path) }} { - default 1; - - {{ range $ip := $location.Whitelist.CIDR }} - {{ $ip }} 0;{{ end }} - } - {{ end }} - {{ end }} - {{ end }} - {{ end }} - - {{ range $rl := (filterRateLimits $servers ) }} - # Ratelimit {{ $rl.Name }} - geo $the_real_ip $whitelist_{{ $rl.ID }} { - default 0; - {{ range $ip := $rl.Whitelist }} - {{ $ip }} 1;{{ end }} - } - - # Ratelimit {{ $rl.Name }} - map $whitelist_{{ $rl.ID }} $limit_{{ $rl.ID }} { - 0 {{ $cfg.LimitConnZoneVariable }}; - 1 ""; - } - {{ end }} - - {{/* build all the required rate limit zones. Each annotation requires a dedicated zone */}} - {{/* 1MB -> 16 thousand 64-byte states or about 8 thousand 128-byte states */}} - {{ range $zone := (buildRateLimitZones $servers) }} - {{ $zone }} - {{ end }} - - {{/* Build server redirects (from/to www) */}} - {{ range $hostname, $to := .RedirectServers }} - server { - {{ range $address := $all.Cfg.BindAddressIpv4 }} - listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}; - listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} ssl; - {{ else }} - listen {{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}; - listen {{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} ssl; - {{ end }} - {{ if $IsIPV6Enabled }} - {{ range $address := $all.Cfg.BindAddressIpv6 }} - listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}; - listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}; - {{ else }} - listen [::]:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}; - listen [::]:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}; - {{ end }} - {{ end }} - server_name {{ $hostname }}; - return 301 $scheme://{{ $to }}$request_uri; - } - {{ end }} - - {{ range $index, $server := $servers }} - - ## start server {{ $server.Hostname }} - server { - server_name {{ $server.Hostname }} {{ $server.Alias }}; - {{ template "SERVER" serverConfig $all $server }} - - {{ if not (empty $cfg.ServerSnippet) }} - # Custom code snippet configured in the configuration configmap - {{ $cfg.ServerSnippet }} - {{ end }} - - {{ template "CUSTOM_ERRORS" $all }} - } - ## end server {{ $server.Hostname }} - - {{ end }} - - # default server, used for NGINX healthcheck and access to nginx stats - server { - # Use the port {{ $all.ListenPorts.Status }} (random value just to avoid known ports) as default port for nginx. - # Changing this value requires a change in: - # https://github.com/kubernetes/ingress-nginx/blob/master/controllers/nginx/pkg/cmd/controller/nginx.go - listen {{ $all.ListenPorts.Status }} default_server reuseport backlog={{ $all.BacklogSize }}; - {{ if $IsIPV6Enabled }}listen [::]:{{ $all.ListenPorts.Status }} default_server reuseport backlog={{ $all.BacklogSize }};{{ end }} - set $proxy_upstream_name "-"; - - location {{ $healthzURI }} { - access_log off; - return 200; - } - - location /nginx_status { - set $proxy_upstream_name "internal"; - - {{ if $cfg.EnableVtsStatus }} - vhost_traffic_status_display; - vhost_traffic_status_display_format html; - {{ else }} - access_log off; - stub_status on; - {{ end }} - } - - location / { - {{ if .CustomErrors }} - proxy_set_header X-Code 404; - {{ end }} - set $proxy_upstream_name "upstream-default-backend"; - proxy_pass http://upstream-default-backend; - } - - {{ template "CUSTOM_ERRORS" $all }} - } -} - -stream { - log_format log_stream {{ $cfg.LogFormatStream }}; - - {{ if $cfg.DisableAccessLog }} - access_log off; - {{ else }} - access_log {{ $cfg.AccessLogPath }} log_stream; - {{ end }} - - error_log {{ $cfg.ErrorLogPath }}; - - # TCP services - {{ range $i, $tcpServer := .TCPBackends }} - upstream tcp-{{ $tcpServer.Port }}-{{ $tcpServer.Backend.Namespace }}-{{ $tcpServer.Backend.Name }}-{{ $tcpServer.Backend.Port }} { - {{ range $j, $endpoint := $tcpServer.Endpoints }} - server {{ $endpoint.Address }}:{{ $endpoint.Port }}; - {{ end }} - } - server { - {{ range $address := $all.Cfg.BindAddressIpv4 }} - listen {{ $address }}:{{ $tcpServer.Port }}{{ if $tcpServer.Backend.ProxyProtocol.Decode }} proxy_protocol{{ end }}; - {{ else }} - listen {{ $tcpServer.Port }}{{ if $tcpServer.Backend.ProxyProtocol.Decode }} proxy_protocol{{ end }}; - {{ end }} - {{ if $IsIPV6Enabled }} - {{ range $address := $all.Cfg.BindAddressIpv6 }} - listen {{ $address }}:{{ $tcpServer.Port }}{{ if $tcpServer.Backend.ProxyProtocol.Decode }} proxy_protocol{{ end }}; - {{ else }} - listen [::]:{{ $tcpServer.Port }}{{ if $tcpServer.Backend.ProxyProtocol.Decode }} proxy_protocol{{ end }}; - {{ end }} - {{ end }} - proxy_timeout {{ $cfg.ProxyStreamTimeout }}; - proxy_pass tcp-{{ $tcpServer.Port }}-{{ $tcpServer.Backend.Namespace }}-{{ $tcpServer.Backend.Name }}-{{ $tcpServer.Backend.Port }}; - {{ if $tcpServer.Backend.ProxyProtocol.Encode }} - proxy_protocol on; - {{ end }} - } - - {{ end }} - - # UDP services - {{ range $i, $udpServer := .UDPBackends }} - upstream udp-{{ $udpServer.Port }}-{{ $udpServer.Backend.Namespace }}-{{ $udpServer.Backend.Name }}-{{ $udpServer.Backend.Port }} { - {{ range $j, $endpoint := $udpServer.Endpoints }} - server {{ $endpoint.Address }}:{{ $endpoint.Port }}; - {{ end }} - } - - server { - {{ range $address := $all.Cfg.BindAddressIpv4 }} - listen {{ $address }}:{{ $udpServer.Port }} udp; - {{ else }} - listen {{ $udpServer.Port }} udp; - {{ end }} - {{ if $IsIPV6Enabled }} - {{ range $address := $all.Cfg.BindAddressIpv6 }} - listen {{ $address }}:{{ $udpServer.Port }} udp; - {{ else }} - listen [::]:{{ $udpServer.Port }} udp; - {{ end }} - {{ end }} - proxy_responses 1; - proxy_timeout {{ $cfg.ProxyStreamTimeout }}; - proxy_pass udp-{{ $udpServer.Port }}-{{ $udpServer.Backend.Namespace }}-{{ $udpServer.Backend.Name }}-{{ $udpServer.Backend.Port }}; - } - - {{ end }} -} - -{{/* definition of templates to avoid repetitions */}} -{{ define "CUSTOM_ERRORS" }} - {{ $proxySetHeaders := .ProxySetHeaders }} - {{ range $errCode := .Cfg.CustomHTTPErrors }} - location @custom_{{ $errCode }} { - internal; - - proxy_intercept_errors off; - - proxy_set_header X-Code {{ $errCode }}; - proxy_set_header X-Format $http_accept; - proxy_set_header X-Original-URI $request_uri; - proxy_set_header X-Namespace $namespace; - proxy_set_header X-Ingress-Name $ingress_name; - proxy_set_header X-Service-Name $service_name; - - rewrite (.*) / break; - proxy_pass http://upstream-default-backend; - } - {{ end }} -{{ end }} - -{{/* CORS support from https://michielkalkman.com/snippets/nginx-cors-open-configuration.html */}} -{{ define "CORS" }} - {{ $cors := .CorsConfig }} - # Cors Preflight methods needs additional options and different Return Code - if ($request_method = 'OPTIONS') { - add_header 'Access-Control-Allow-Origin' '{{ $cors.CorsAllowOrigin }}' always; - {{ if $cors.CorsAllowCredentials }} add_header 'Access-Control-Allow-Credentials' '{{ $cors.CorsAllowCredentials }}' always; {{ end }} - add_header 'Access-Control-Allow-Methods' '{{ $cors.CorsAllowMethods }}' always; - add_header 'Access-Control-Allow-Headers' '{{ $cors.CorsAllowHeaders }}' always; - add_header 'Access-Control-Max-Age' 1728000; - add_header 'Content-Type' 'text/plain charset=UTF-8'; - add_header 'Content-Length' 0; - return 204; - } - - add_header 'Access-Control-Allow-Origin' '{{ $cors.CorsAllowOrigin }}' always; - {{ if $cors.CorsAllowCredentials }} add_header 'Access-Control-Allow-Credentials' '{{ $cors.CorsAllowCredentials }}' always; {{ end }} - add_header 'Access-Control-Allow-Methods' '{{ $cors.CorsAllowMethods }}' always; - add_header 'Access-Control-Allow-Headers' '{{ $cors.CorsAllowHeaders }}' always; - -{{ end }} - -{{/* definition of server-template to avoid repetitions with server-alias */}} -{{ define "SERVER" }} - {{ $all := .First }} - {{ $server := .Second }} - {{ range $address := $all.Cfg.BindAddressIpv4 }} - listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}}; - {{ else }} - listen {{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}}; - {{ end }} - {{ if $all.IsIPV6Enabled }} - {{ range $address := $all.Cfg.BindAddressIpv6 }} - listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{ end }}; - {{ else }} - listen [::]:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{ end }}; - {{ end }} - {{ end }} - set $proxy_upstream_name "-"; - - {{/* Listen on {{ $all.ListenPorts.SSLProxy }} because port {{ $all.ListenPorts.HTTPS }} is used in the TLS sni server */}} - {{/* This listener must always have proxy_protocol enabled, because the SNI listener forwards on source IP info in it. */}} - {{ if not (empty $server.SSLCertificate) }} - {{ range $address := $all.Cfg.BindAddressIpv4 }} - listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol {{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }}; - {{ else }} - listen {{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol {{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }}; - {{ end }} - {{ if $all.IsIPV6Enabled }} - {{ range $address := $all.Cfg.BindAddressIpv6 }} - {{ if not (empty $server.SSLCertificate) }}listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }}; - {{ else }} - {{ if not (empty $server.SSLCertificate) }}listen [::]:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }}; - {{ end }} - {{ end }} - {{/* comment PEM sha is required to detect changes in the generated configuration and force a reload */}} - # PEM sha: {{ $server.SSLPemChecksum }} - ssl_certificate {{ $server.SSLCertificate }}; - ssl_certificate_key {{ $server.SSLCertificate }}; - {{ if not (empty $server.SSLFullChainCertificate)}} - ssl_trusted_certificate {{ $server.SSLFullChainCertificate }}; - ssl_stapling on; - ssl_stapling_verify on; - {{ end }} - {{ end }} - - {{ if (and (not (empty $server.SSLCertificate)) $all.Cfg.HSTS) }} - more_set_headers "Strict-Transport-Security: max-age={{ $all.Cfg.HSTSMaxAge }}{{ if $all.Cfg.HSTSIncludeSubdomains }}; includeSubDomains{{ end }};{{ if $all.Cfg.HSTSPreload }} preload{{ end }}"; - {{ end }} - - - {{ if not (empty $server.CertificateAuth.CAFileName) }} - # PEM sha: {{ $server.CertificateAuth.PemSHA }} - ssl_client_certificate {{ $server.CertificateAuth.CAFileName }}; - ssl_verify_client {{ $server.CertificateAuth.VerifyClient }}; - ssl_verify_depth {{ $server.CertificateAuth.ValidationDepth }}; - {{ if not (empty $server.CertificateAuth.ErrorPage)}} - error_page 495 496 = {{ $server.CertificateAuth.ErrorPage }}; - {{ end }} - {{ end }} - - {{ if not (empty $server.ServerSnippet) }} - {{ $server.ServerSnippet }} - {{ end }} - - {{ range $location := $server.Locations }} - {{ $path := buildLocation $location }} - {{ $authPath := buildAuthLocation $location }} - - {{ if not (empty $location.Rewrite.AppRoot)}} - if ($uri = /) { - return 302 {{ $location.Rewrite.AppRoot }}; - } - {{ end }} - - {{ if not (empty $authPath) }} - location = {{ $authPath }} { - internal; - set $proxy_upstream_name "external-authentication"; - - proxy_pass_request_body off; - proxy_set_header Content-Length ""; - - {{ if not (empty $location.ExternalAuth.Method) }} - proxy_method {{ $location.ExternalAuth.Method }}; - proxy_set_header X-Original-URI $request_uri; - proxy_set_header X-Scheme $pass_access_scheme; - {{ end }} - - proxy_set_header Host $http_host; - proxy_set_header X-Original-URI $request_uri; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Original-Method $request_method; - proxy_set_header X-Auth-Request-Redirect $request_uri; - proxy_set_header X-Sent-From "nginx-ingress-controller"; - - proxy_ssl_server_name on; - proxy_pass_request_headers on; - client_max_body_size "{{ $location.Proxy.BodySize }}"; - {{ if isValidClientBodyBufferSize $location.ClientBodyBufferSize }} - client_body_buffer_size {{ $location.ClientBodyBufferSize }}; - {{ end }} - - set $target {{ $location.ExternalAuth.URL }}; - proxy_pass $target; - } - {{ end }} - - location {{ $path }} { - {{ if $all.Cfg.EnableVtsStatus }}{{ if $location.VtsFilterKey }} vhost_traffic_status_filter_by_set_key {{ $location.VtsFilterKey }};{{ end }}{{ end }} - - set $proxy_upstream_name "{{ buildUpstreamName $server.Hostname $all.Backends $location }}"; - - {{ $ing := (getIngressInformation $location.Ingress $path) }} - {{/* $ing.Metadata contains the Ingress metadata */}} - set $namespace "{{ $ing.Namespace }}"; - set $ingress_name "{{ $ing.Rule }}"; - set $service_name "{{ $ing.Service }}"; - - {{ if (or $location.Rewrite.ForceSSLRedirect (and (not (empty $server.SSLCertificate)) $location.Rewrite.SSLRedirect)) }} - # enforce ssl on server side - if ($pass_access_scheme = http) { - return 301 https://$best_http_host$request_uri; - } - {{ end }} - - {{ if $all.Cfg.EnableModsecurity }} - modsecurity on; - - modsecurity_rules_file /etc/nginx/modsecurity/modsecurity.conf; - {{ if $all.Cfg.EnableOWASPCoreRules }} - modsecurity_rules_file /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf; - {{ end }} - {{ end }} - - {{ if isLocationAllowed $location }} - {{ if gt (len $location.Whitelist.CIDR) 0 }} - if ({{ buildDenyVariable (print $server.Hostname "_" $path) }}) { - return 403; - } - {{ end }} - - port_in_redirect {{ if $location.UsePortInRedirects }}on{{ else }}off{{ end }}; - - {{ if not (empty $authPath) }} - # this location requires authentication - auth_request {{ $authPath }}; - auth_request_set $auth_cookie $upstream_http_set_cookie; - auth_request_set $redirect $upstream_http_redirect; - auth_request_set $user $upstream_http_remote_user; - proxy_set_header X-Forwarded-User $user; - auth_request_set $groups $upstream_http_remote_groups; - proxy_set_header Remote-Groups $groups; - - add_header Set-Cookie $auth_cookie; - {{- range $idx, $line := buildAuthResponseHeaders $location }} - {{ $line }} - {{- end }} - {{ end }} - - {{ if not (empty $location.ExternalAuth.SigninURL) }} - error_page 401 = {{ buildAuthSignURL $location.ExternalAuth.SigninURL }}; - {{ end }} - - {{/* if the location contains a rate limit annotation, create one */}} - {{ $limits := buildRateLimit $location }} - {{ range $limit := $limits }} - {{ $limit }}{{ end }} - - {{ if $location.BasicDigestAuth.Secured }} - {{ if eq $location.BasicDigestAuth.Type "basic" }} - auth_basic "{{ $location.BasicDigestAuth.Realm }}"; - auth_basic_user_file {{ $location.BasicDigestAuth.File }}; - {{ else }} - auth_digest "{{ $location.BasicDigestAuth.Realm }}"; - auth_digest_user_file {{ $location.BasicDigestAuth.File }}; - {{ end }} - proxy_set_header Authorization ""; - {{ end }} - - {{ if $location.CorsConfig.CorsEnabled }} - {{ template "CORS" $location }} - {{ end }} - - {{ if not (empty $location.Redirect.URL) }} - if ($uri ~* {{ $path }}) { - return {{ $location.Redirect.Code }} {{ $location.Redirect.URL }}; - } - {{ end }} - - client_max_body_size "{{ $location.Proxy.BodySize }}"; - {{ if isValidClientBodyBufferSize $location.ClientBodyBufferSize }} - client_body_buffer_size {{ $location.ClientBodyBufferSize }}; - {{ end }} - - {{/* By default use vhost as Host to upstream, but allow overrides */}} - {{ if not (empty $location.UpstreamVhost) }} - proxy_set_header Host "{{ $location.UpstreamVhost }}"; - {{ else }} - proxy_set_header Host $best_http_host; - {{ end }} - - - # Pass the extracted client certificate to the backend - {{ if not (empty $server.CertificateAuth.CAFileName) }} - {{ if $server.CertificateAuth.PassCertToUpstream }} - proxy_set_header ssl-client-cert $ssl_client_escaped_cert; - {{ else }} - proxy_set_header ssl-client-cert ""; - {{ end }} - proxy_set_header ssl-client-verify $ssl_client_verify; - proxy_set_header ssl-client-dn $ssl_client_s_dn; - {{ else }} - proxy_set_header ssl-client-cert ""; - proxy_set_header ssl-client-verify ""; - proxy_set_header ssl-client-dn ""; - {{ end }} - - # Allow websocket connections - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - - proxy_set_header X-Real-IP $the_real_ip; - {{ if $all.Cfg.ComputeFullForwardedFor }} - proxy_set_header X-Forwarded-For $full_x_forwarded_for; - {{ else }} - proxy_set_header X-Forwarded-For $the_real_ip; - {{ end }} - proxy_set_header X-Forwarded-Host $best_http_host; - proxy_set_header X-Forwarded-Port $pass_port; - proxy_set_header X-Forwarded-Proto $pass_access_scheme; - proxy_set_header X-Original-URI $request_uri; - proxy_set_header X-Scheme $pass_access_scheme; - - # Pass the original X-Forwarded-For - proxy_set_header X-Original-Forwarded-For {{ buildForwardedFor $all.Cfg.ForwardedForHeader }}; - - # mitigate HTTPoxy Vulnerability - # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/ - proxy_set_header Proxy ""; - - # Custom headers to proxied server - {{ range $k, $v := $all.ProxySetHeaders }} - proxy_set_header {{ $k }} "{{ $v }}"; - {{ end }} - - proxy_connect_timeout {{ $location.Proxy.ConnectTimeout }}s; - proxy_send_timeout {{ $location.Proxy.SendTimeout }}s; - proxy_read_timeout {{ $location.Proxy.ReadTimeout }}s; - - {{ if (or (eq $location.Proxy.ProxyRedirectFrom "default") (eq $location.Proxy.ProxyRedirectFrom "off")) }} - proxy_redirect {{ $location.Proxy.ProxyRedirectFrom }}; - {{ else }} - proxy_redirect {{ $location.Proxy.ProxyRedirectFrom }} {{ $location.Proxy.ProxyRedirectTo }}; - {{ end }} - proxy_buffering off; - proxy_buffer_size "{{ $location.Proxy.BufferSize }}"; - proxy_buffers 4 "{{ $location.Proxy.BufferSize }}"; - proxy_request_buffering "{{ $location.Proxy.RequestBuffering }}"; - - proxy_http_version 1.1; - - proxy_cookie_domain {{ $location.Proxy.CookieDomain }}; - proxy_cookie_path {{ $location.Proxy.CookiePath }}; - - # In case of errors try the next upstream server before returning an error - proxy_next_upstream {{ buildNextUpstream $location.Proxy.NextUpstream $all.Cfg.RetryNonIdempotent }}; - - {{/* rewrite only works if the content is not compressed */}} - {{ if $location.Rewrite.AddBaseURL }} - proxy_set_header Accept-Encoding ""; - {{ end }} - - {{/* Add any additional configuration defined */}} - {{ $location.ConfigurationSnippet }} - - {{ if not (empty $all.Cfg.LocationSnippet) }} - # Custom code snippet configured in the configuration configmap - {{ $all.Cfg.LocationSnippet }} - {{ end }} - - {{/* if we are sending the request to a custom default backend, we add the required headers */}} - {{ if (hasPrefix $location.Backend "custom-default-backend-") }} - proxy_set_header X-Code 503; - proxy_set_header X-Format $http_accept; - proxy_set_header X-Namespace $namespace; - proxy_set_header X-Ingress-Name $ingress_name; - proxy_set_header X-Service-Name $service_name; - {{ end }} - - - {{ if not (empty $location.Backend) }} - {{ buildProxyPass $server.Hostname $all.Backends $location }} - {{ else }} - # No endpoints available for the request - return 503; - {{ end }} - {{ else }} - # Location denied. Reason: {{ $location.Denied }} - return 503; - {{ end }} - } - - {{ end }} - - {{ if eq $server.Hostname "_" }} - # health checks in cloud providers require the use of port {{ $all.ListenPorts.HTTP }} - location {{ $all.HealthzURI }} { - access_log off; - return 200; - } - - # this is required to avoid error if nginx is being monitored - # with an external software (like sysdig) - location /nginx_status { - allow 127.0.0.1; - {{ if $all.IsIPV6Enabled }}allow ::1;{{ end }} - deny all; - - access_log off; - stub_status on; - } - - {{ end }} - -{{ end }} diff --git a/example/kube/ingress-controller/configs/nginx.tmpl.original b/example/kube/ingress-controller/configs/nginx.tmpl.original deleted file mode 100644 index ff6eedcd0..000000000 --- a/example/kube/ingress-controller/configs/nginx.tmpl.original +++ /dev/null @@ -1,879 +0,0 @@ -{{ $all := . }} -{{ $servers := .Servers }} -{{ $cfg := .Cfg }} -{{ $IsIPV6Enabled := .IsIPV6Enabled }} -{{ $healthzURI := .HealthzURI }} -{{ $backends := .Backends }} -{{ $proxyHeaders := .ProxySetHeaders }} -{{ $addHeaders := .AddHeaders }} - -{{ if $cfg.EnableModsecurity }} -load_module /etc/nginx/modules/ngx_http_modsecurity_module.so; -{{ end }} - -{{ if $cfg.EnableOpentracing }} -load_module /etc/nginx/modules/ngx_http_opentracing_module.so; -{{ end }} - -{{ if (and $cfg.EnableOpentracing (ne $cfg.ZipkinCollectorHost "")) }} -load_module /etc/nginx/modules/ngx_http_zipkin_module.so; -{{ end }} - -daemon off; - -worker_processes {{ $cfg.WorkerProcesses }}; -pid /run/nginx.pid; -{{ if ne .MaxOpenFiles 0 }} -worker_rlimit_nofile {{ .MaxOpenFiles }}; -{{ end}} - -{{/* http://nginx.org/en/docs/ngx_core_module.html#worker_shutdown_timeout */}} -{{/* avoid waiting too long during a reload */}} -worker_shutdown_timeout {{ $cfg.WorkerShutdownTimeout }} ; - -events { - multi_accept on; - worker_connections {{ $cfg.MaxWorkerConnections }}; - use epoll; -} - -http { - {{/* we use the value of the header X-Forwarded-For to be able to use the geo_ip module */}} - {{ if $cfg.UseProxyProtocol }} - real_ip_header proxy_protocol; - {{ else }} - real_ip_header {{ $cfg.ForwardedForHeader }}; - {{ end }} - - real_ip_recursive on; - {{ range $trusted_ip := $cfg.ProxyRealIPCIDR }} - set_real_ip_from {{ $trusted_ip }}; - {{ end }} - - {{/* databases used to determine the country depending on the client IP address */}} - {{/* http://nginx.org/en/docs/http/ngx_http_geoip_module.html */}} - {{/* this is require to calculate traffic for individual country using GeoIP in the status page */}} - geoip_country /etc/nginx/GeoIP.dat; - geoip_city /etc/nginx/GeoLiteCity.dat; - geoip_proxy_recursive on; - - {{ if $cfg.EnableVtsStatus }} - vhost_traffic_status_zone shared:vhost_traffic_status:{{ $cfg.VtsStatusZoneSize }}; - vhost_traffic_status_filter_by_set_key {{ $cfg.VtsDefaultFilterKey }}; - {{ end }} - - sendfile on; - - aio threads; - aio_write on; - - tcp_nopush on; - tcp_nodelay on; - - log_subrequest on; - - reset_timedout_connection on; - - keepalive_timeout {{ $cfg.KeepAlive }}s; - keepalive_requests {{ $cfg.KeepAliveRequests }}; - - client_header_buffer_size {{ $cfg.ClientHeaderBufferSize }}; - client_header_timeout {{ $cfg.ClientHeaderTimeout }}s; - large_client_header_buffers {{ $cfg.LargeClientHeaderBuffers }}; - client_body_buffer_size {{ $cfg.ClientBodyBufferSize }}; - client_body_timeout {{ $cfg.ClientBodyTimeout }}s; - - http2_max_field_size {{ $cfg.HTTP2MaxFieldSize }}; - http2_max_header_size {{ $cfg.HTTP2MaxHeaderSize }}; - - types_hash_max_size 2048; - server_names_hash_max_size {{ $cfg.ServerNameHashMaxSize }}; - server_names_hash_bucket_size {{ $cfg.ServerNameHashBucketSize }}; - map_hash_bucket_size {{ $cfg.MapHashBucketSize }}; - - proxy_headers_hash_max_size {{ $cfg.ProxyHeadersHashMaxSize }}; - proxy_headers_hash_bucket_size {{ $cfg.ProxyHeadersHashBucketSize }}; - - variables_hash_bucket_size {{ $cfg.VariablesHashBucketSize }}; - variables_hash_max_size {{ $cfg.VariablesHashMaxSize }}; - - underscores_in_headers {{ if $cfg.EnableUnderscoresInHeaders }}on{{ else }}off{{ end }}; - ignore_invalid_headers {{ if $cfg.IgnoreInvalidHeaders }}on{{ else }}off{{ end }}; - - {{ if $cfg.EnableOpentracing }} - opentracing on; - {{ end }} - - {{ if (and $cfg.EnableOpentracing (ne $cfg.ZipkinCollectorHost "")) }} - zipkin_collector_host {{ $cfg.ZipkinCollectorHost }}; - zipkin_collector_port {{ $cfg.ZipkinCollectorPort }}; - zipkin_service_name {{ $cfg.ZipkinServiceName }}; - {{ end }} - - include /etc/nginx/mime.types; - default_type text/html; - - {{ if $cfg.EnableBrotli }} - brotli on; - brotli_comp_level {{ $cfg.BrotliLevel }}; - brotli_types {{ $cfg.BrotliTypes }}; - {{ end }} - - {{ if $cfg.UseGzip }} - gzip on; - gzip_comp_level 5; - gzip_http_version 1.1; - gzip_min_length 256; - gzip_types {{ $cfg.GzipTypes }}; - gzip_proxied any; - gzip_vary on; - {{ end }} - - # Custom headers for response - {{ range $k, $v := $addHeaders }} - add_header {{ $k }} "{{ $v }}"; - {{ end }} - - server_tokens {{ if $cfg.ShowServerTokens }}on{{ else }}off{{ end }}; - - # disable warnings - uninitialized_variable_warn off; - - # Additional available variables: - # $namespace - # $ingress_name - # $service_name - log_format upstreaminfo {{ if $cfg.LogFormatEscapeJSON }}escape=json {{ end }}'{{ buildLogFormatUpstream $cfg }}'; - - {{/* map urls that should not appear in access.log */}} - {{/* http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log */}} - map $request_uri $loggable { - {{ range $reqUri := $cfg.SkipAccessLogURLs }} - {{ $reqUri }} 0;{{ end }} - default 1; - } - - {{ if $cfg.DisableAccessLog }} - access_log off; - {{ else }} - access_log {{ $cfg.AccessLogPath }} upstreaminfo if=$loggable; - {{ end }} - error_log {{ $cfg.ErrorLogPath }} {{ $cfg.ErrorLogLevel }}; - - {{ buildResolvers $cfg.Resolver }} - - {{/* Whenever nginx proxies a request without a "Connection" header, the "Connection" header is set to "close" */}} - {{/* when making the target request. This means that you cannot simply use */}} - {{/* "proxy_set_header Connection $http_connection" for WebSocket support because in this case, the */}} - {{/* "Connection" header would be set to "" whenever the original request did not have a "Connection" header, */}} - {{/* which would mean no "Connection" header would be in the target request. Since this would deviate from */}} - {{/* normal nginx behavior we have to use this approach. */}} - # Retain the default nginx handling of requests without a "Connection" header - map $http_upgrade $connection_upgrade { - default upgrade; - '' close; - } - - map {{ buildForwardedFor $cfg.ForwardedForHeader }} $the_real_ip { - {{ if $cfg.UseProxyProtocol }} - # Get IP address from Proxy Protocol - default $proxy_protocol_addr; - {{ else }} - default $remote_addr; - {{ end }} - } - - # trust http_x_forwarded_proto headers correctly indicate ssl offloading - map $http_x_forwarded_proto $pass_access_scheme { - default $http_x_forwarded_proto; - '' $scheme; - } - - map $http_x_forwarded_port $pass_server_port { - default $http_x_forwarded_port; - '' $server_port; - } - - map $http_x_forwarded_host $best_http_host { - default $http_x_forwarded_host; - '' $this_host; - } - - {{ if $all.IsSSLPassthroughEnabled }} - # map port {{ $all.ListenPorts.SSLProxy }} to 443 for header X-Forwarded-Port - map $pass_server_port $pass_port { - {{ $all.ListenPorts.SSLProxy }} 443; - default $pass_server_port; - } - {{ else }} - map $pass_server_port $pass_port { - 443 443; - default $pass_server_port; - } - {{ end }} - - # Obtain best http host - map $http_host $this_host { - default $http_host; - '' $host; - } - - {{ if $cfg.ComputeFullForwardedFor }} - # We can't use $proxy_add_x_forwarded_for because the realip module - # replaces the remote_addr too soon - map $http_x_forwarded_for $full_x_forwarded_for { - {{ if $all.Cfg.UseProxyProtocol }} - default "$http_x_forwarded_for, $proxy_protocol_addr"; - '' "$proxy_protocol_addr"; - {{ else }} - default "$http_x_forwarded_for, $realip_remote_addr"; - '' "$realip_remote_addr"; - {{ end}} - } - {{ end }} - - server_name_in_redirect off; - port_in_redirect off; - - ssl_protocols {{ $cfg.SSLProtocols }}; - - # turn on session caching to drastically improve performance - {{ if $cfg.SSLSessionCache }} - ssl_session_cache builtin:1000 shared:SSL:{{ $cfg.SSLSessionCacheSize }}; - ssl_session_timeout {{ $cfg.SSLSessionTimeout }}; - {{ end }} - - # allow configuring ssl session tickets - ssl_session_tickets {{ if $cfg.SSLSessionTickets }}on{{ else }}off{{ end }}; - - {{ if not (empty $cfg.SSLSessionTicketKey ) }} - ssl_session_ticket_key /etc/nginx/tickets.key; - {{ end }} - - # slightly reduce the time-to-first-byte - ssl_buffer_size {{ $cfg.SSLBufferSize }}; - - {{ if not (empty $cfg.SSLCiphers) }} - # allow configuring custom ssl ciphers - ssl_ciphers '{{ $cfg.SSLCiphers }}'; - ssl_prefer_server_ciphers on; - {{ end }} - - {{ if not (empty $cfg.SSLDHParam) }} - # allow custom DH file http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_dhparam - ssl_dhparam {{ $cfg.SSLDHParam }}; - {{ end }} - - {{ if not $cfg.EnableDynamicTLSRecords }} - ssl_dyn_rec_size_lo 0; - {{ end }} - - ssl_ecdh_curve {{ $cfg.SSLECDHCurve }}; - - {{ if .CustomErrors }} - # Custom error pages - proxy_intercept_errors on; - {{ end }} - - {{ range $errCode := $cfg.CustomHTTPErrors }} - error_page {{ $errCode }} = @custom_{{ $errCode }};{{ end }} - - proxy_ssl_session_reuse on; - - {{ if $cfg.AllowBackendServerHeader }} - proxy_pass_header Server; - {{ end }} - - {{ if not (empty $cfg.HTTPSnippet) }} - # Custom code snippet configured in the configuration configmap - {{ $cfg.HTTPSnippet }} - {{ end }} - - {{ range $name, $upstream := $backends }} - {{ if eq $upstream.SessionAffinity.AffinityType "cookie" }} - upstream sticky-{{ $upstream.Name }} { - sticky hash={{ $upstream.SessionAffinity.CookieSessionAffinity.Hash }} name={{ $upstream.SessionAffinity.CookieSessionAffinity.Name }} httponly; - - {{ if (gt $cfg.UpstreamKeepaliveConnections 0) }} - keepalive {{ $cfg.UpstreamKeepaliveConnections }}; - {{ end }} - - {{ range $server := $upstream.Endpoints }}server {{ $server.Address | formatIP }}:{{ $server.Port }} max_fails={{ $server.MaxFails }} fail_timeout={{ $server.FailTimeout }}; - {{ end }} - - } - - {{ end }} - - - upstream {{ $upstream.Name }} { - # Load balance algorithm; empty for round robin, which is the default - {{ if ne $cfg.LoadBalanceAlgorithm "round_robin" }} - {{ $cfg.LoadBalanceAlgorithm }}; - {{ end }} - - {{ if $upstream.UpstreamHashBy }} - hash {{ $upstream.UpstreamHashBy }} consistent; - {{ end }} - - {{ if (gt $cfg.UpstreamKeepaliveConnections 0) }} - keepalive {{ $cfg.UpstreamKeepaliveConnections }}; - {{ end }} - - {{ range $server := $upstream.Endpoints }}server {{ $server.Address | formatIP }}:{{ $server.Port }} max_fails={{ $server.MaxFails }} fail_timeout={{ $server.FailTimeout }}; - {{ end }} - } - - {{ end }} - - {{/* build the maps that will be use to validate the Whitelist */}} - {{ range $index, $server := $servers }} - {{ range $location := $server.Locations }} - {{ $path := buildLocation $location }} - - {{ if isLocationAllowed $location }} - {{ if gt (len $location.Whitelist.CIDR) 0 }} - - # Deny for {{ print $server.Hostname $path }} - geo $the_real_ip {{ buildDenyVariable (print $server.Hostname "_" $path) }} { - default 1; - - {{ range $ip := $location.Whitelist.CIDR }} - {{ $ip }} 0;{{ end }} - } - {{ end }} - {{ end }} - {{ end }} - {{ end }} - - {{ range $rl := (filterRateLimits $servers ) }} - # Ratelimit {{ $rl.Name }} - geo $the_real_ip $whitelist_{{ $rl.ID }} { - default 0; - {{ range $ip := $rl.Whitelist }} - {{ $ip }} 1;{{ end }} - } - - # Ratelimit {{ $rl.Name }} - map $whitelist_{{ $rl.ID }} $limit_{{ $rl.ID }} { - 0 {{ $cfg.LimitConnZoneVariable }}; - 1 ""; - } - {{ end }} - - {{/* build all the required rate limit zones. Each annotation requires a dedicated zone */}} - {{/* 1MB -> 16 thousand 64-byte states or about 8 thousand 128-byte states */}} - {{ range $zone := (buildRateLimitZones $servers) }} - {{ $zone }} - {{ end }} - - {{/* Build server redirects (from/to www) */}} - {{ range $hostname, $to := .RedirectServers }} - server { - {{ range $address := $all.Cfg.BindAddressIpv4 }} - listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}; - listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} ssl; - {{ else }} - listen {{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}; - listen {{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} ssl; - {{ end }} - {{ if $IsIPV6Enabled }} - {{ range $address := $all.Cfg.BindAddressIpv6 }} - listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}; - listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}; - {{ else }} - listen [::]:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}; - listen [::]:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}; - {{ end }} - {{ end }} - server_name {{ $hostname }}; - return 301 $scheme://{{ $to }}$request_uri; - } - {{ end }} - - {{ range $index, $server := $servers }} - - ## start server {{ $server.Hostname }} - server { - server_name {{ $server.Hostname }} {{ $server.Alias }}; - {{ template "SERVER" serverConfig $all $server }} - - {{ if not (empty $cfg.ServerSnippet) }} - # Custom code snippet configured in the configuration configmap - {{ $cfg.ServerSnippet }} - {{ end }} - - {{ template "CUSTOM_ERRORS" $all }} - } - ## end server {{ $server.Hostname }} - - {{ end }} - - # default server, used for NGINX healthcheck and access to nginx stats - server { - # Use the port {{ $all.ListenPorts.Status }} (random value just to avoid known ports) as default port for nginx. - # Changing this value requires a change in: - # https://github.com/kubernetes/ingress-nginx/blob/master/controllers/nginx/pkg/cmd/controller/nginx.go - listen {{ $all.ListenPorts.Status }} default_server reuseport backlog={{ $all.BacklogSize }}; - {{ if $IsIPV6Enabled }}listen [::]:{{ $all.ListenPorts.Status }} default_server reuseport backlog={{ $all.BacklogSize }};{{ end }} - set $proxy_upstream_name "-"; - - location {{ $healthzURI }} { - access_log off; - return 200; - } - - location /nginx_status { - set $proxy_upstream_name "internal"; - - {{ if $cfg.EnableVtsStatus }} - vhost_traffic_status_display; - vhost_traffic_status_display_format html; - {{ else }} - access_log off; - stub_status on; - {{ end }} - } - - location / { - {{ if .CustomErrors }} - proxy_set_header X-Code 404; - {{ end }} - set $proxy_upstream_name "upstream-default-backend"; - proxy_pass http://upstream-default-backend; - } - - {{ template "CUSTOM_ERRORS" $all }} - } -} - -stream { - log_format log_stream {{ $cfg.LogFormatStream }}; - - {{ if $cfg.DisableAccessLog }} - access_log off; - {{ else }} - access_log {{ $cfg.AccessLogPath }} log_stream; - {{ end }} - - error_log {{ $cfg.ErrorLogPath }}; - - # TCP services - {{ range $i, $tcpServer := .TCPBackends }} - upstream tcp-{{ $tcpServer.Port }}-{{ $tcpServer.Backend.Namespace }}-{{ $tcpServer.Backend.Name }}-{{ $tcpServer.Backend.Port }} { - {{ range $j, $endpoint := $tcpServer.Endpoints }} - server {{ $endpoint.Address }}:{{ $endpoint.Port }}; - {{ end }} - } - server { - {{ range $address := $all.Cfg.BindAddressIpv4 }} - listen {{ $address }}:{{ $tcpServer.Port }}{{ if $tcpServer.Backend.ProxyProtocol.Decode }} proxy_protocol{{ end }}; - {{ else }} - listen {{ $tcpServer.Port }}{{ if $tcpServer.Backend.ProxyProtocol.Decode }} proxy_protocol{{ end }}; - {{ end }} - {{ if $IsIPV6Enabled }} - {{ range $address := $all.Cfg.BindAddressIpv6 }} - listen {{ $address }}:{{ $tcpServer.Port }}{{ if $tcpServer.Backend.ProxyProtocol.Decode }} proxy_protocol{{ end }}; - {{ else }} - listen [::]:{{ $tcpServer.Port }}{{ if $tcpServer.Backend.ProxyProtocol.Decode }} proxy_protocol{{ end }}; - {{ end }} - {{ end }} - proxy_timeout {{ $cfg.ProxyStreamTimeout }}; - proxy_pass tcp-{{ $tcpServer.Port }}-{{ $tcpServer.Backend.Namespace }}-{{ $tcpServer.Backend.Name }}-{{ $tcpServer.Backend.Port }}; - {{ if $tcpServer.Backend.ProxyProtocol.Encode }} - proxy_protocol on; - {{ end }} - } - - {{ end }} - - # UDP services - {{ range $i, $udpServer := .UDPBackends }} - upstream udp-{{ $udpServer.Port }}-{{ $udpServer.Backend.Namespace }}-{{ $udpServer.Backend.Name }}-{{ $udpServer.Backend.Port }} { - {{ range $j, $endpoint := $udpServer.Endpoints }} - server {{ $endpoint.Address }}:{{ $endpoint.Port }}; - {{ end }} - } - - server { - {{ range $address := $all.Cfg.BindAddressIpv4 }} - listen {{ $address }}:{{ $udpServer.Port }} udp; - {{ else }} - listen {{ $udpServer.Port }} udp; - {{ end }} - {{ if $IsIPV6Enabled }} - {{ range $address := $all.Cfg.BindAddressIpv6 }} - listen {{ $address }}:{{ $udpServer.Port }} udp; - {{ else }} - listen [::]:{{ $udpServer.Port }} udp; - {{ end }} - {{ end }} - proxy_responses 1; - proxy_timeout {{ $cfg.ProxyStreamTimeout }}; - proxy_pass udp-{{ $udpServer.Port }}-{{ $udpServer.Backend.Namespace }}-{{ $udpServer.Backend.Name }}-{{ $udpServer.Backend.Port }}; - } - - {{ end }} -} - -{{/* definition of templates to avoid repetitions */}} -{{ define "CUSTOM_ERRORS" }} - {{ $proxySetHeaders := .ProxySetHeaders }} - {{ range $errCode := .Cfg.CustomHTTPErrors }} - location @custom_{{ $errCode }} { - internal; - - proxy_intercept_errors off; - - proxy_set_header X-Code {{ $errCode }}; - proxy_set_header X-Format $http_accept; - proxy_set_header X-Original-URI $request_uri; - proxy_set_header X-Namespace $namespace; - proxy_set_header X-Ingress-Name $ingress_name; - proxy_set_header X-Service-Name $service_name; - - rewrite (.*) / break; - proxy_pass http://upstream-default-backend; - } - {{ end }} -{{ end }} - -{{/* CORS support from https://michielkalkman.com/snippets/nginx-cors-open-configuration.html */}} -{{ define "CORS" }} - {{ $cors := .CorsConfig }} - # Cors Preflight methods needs additional options and different Return Code - if ($request_method = 'OPTIONS') { - add_header 'Access-Control-Allow-Origin' '{{ $cors.CorsAllowOrigin }}' always; - {{ if $cors.CorsAllowCredentials }} add_header 'Access-Control-Allow-Credentials' '{{ $cors.CorsAllowCredentials }}' always; {{ end }} - add_header 'Access-Control-Allow-Methods' '{{ $cors.CorsAllowMethods }}' always; - add_header 'Access-Control-Allow-Headers' '{{ $cors.CorsAllowHeaders }}' always; - add_header 'Access-Control-Max-Age' 1728000; - add_header 'Content-Type' 'text/plain charset=UTF-8'; - add_header 'Content-Length' 0; - return 204; - } - - add_header 'Access-Control-Allow-Origin' '{{ $cors.CorsAllowOrigin }}' always; - {{ if $cors.CorsAllowCredentials }} add_header 'Access-Control-Allow-Credentials' '{{ $cors.CorsAllowCredentials }}' always; {{ end }} - add_header 'Access-Control-Allow-Methods' '{{ $cors.CorsAllowMethods }}' always; - add_header 'Access-Control-Allow-Headers' '{{ $cors.CorsAllowHeaders }}' always; - -{{ end }} - -{{/* definition of server-template to avoid repetitions with server-alias */}} -{{ define "SERVER" }} - {{ $all := .First }} - {{ $server := .Second }} - {{ range $address := $all.Cfg.BindAddressIpv4 }} - listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}}; - {{ else }} - listen {{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}}; - {{ end }} - {{ if $all.IsIPV6Enabled }} - {{ range $address := $all.Cfg.BindAddressIpv6 }} - listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{ end }}; - {{ else }} - listen [::]:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{ end }}; - {{ end }} - {{ end }} - set $proxy_upstream_name "-"; - - {{/* Listen on {{ $all.ListenPorts.SSLProxy }} because port {{ $all.ListenPorts.HTTPS }} is used in the TLS sni server */}} - {{/* This listener must always have proxy_protocol enabled, because the SNI listener forwards on source IP info in it. */}} - {{ if not (empty $server.SSLCertificate) }} - {{ range $address := $all.Cfg.BindAddressIpv4 }} - listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol {{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }}; - {{ else }} - listen {{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol {{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }}; - {{ end }} - {{ if $all.IsIPV6Enabled }} - {{ range $address := $all.Cfg.BindAddressIpv6 }} - {{ if not (empty $server.SSLCertificate) }}listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }}; - {{ else }} - {{ if not (empty $server.SSLCertificate) }}listen [::]:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }}; - {{ end }} - {{ end }} - {{/* comment PEM sha is required to detect changes in the generated configuration and force a reload */}} - # PEM sha: {{ $server.SSLPemChecksum }} - ssl_certificate {{ $server.SSLCertificate }}; - ssl_certificate_key {{ $server.SSLCertificate }}; - {{ if not (empty $server.SSLFullChainCertificate)}} - ssl_trusted_certificate {{ $server.SSLFullChainCertificate }}; - ssl_stapling on; - ssl_stapling_verify on; - {{ end }} - {{ end }} - - {{ if (and (not (empty $server.SSLCertificate)) $all.Cfg.HSTS) }} - more_set_headers "Strict-Transport-Security: max-age={{ $all.Cfg.HSTSMaxAge }}{{ if $all.Cfg.HSTSIncludeSubdomains }}; includeSubDomains{{ end }};{{ if $all.Cfg.HSTSPreload }} preload{{ end }}"; - {{ end }} - - - {{ if not (empty $server.CertificateAuth.CAFileName) }} - # PEM sha: {{ $server.CertificateAuth.PemSHA }} - ssl_client_certificate {{ $server.CertificateAuth.CAFileName }}; - ssl_verify_client {{ $server.CertificateAuth.VerifyClient }}; - ssl_verify_depth {{ $server.CertificateAuth.ValidationDepth }}; - {{ if not (empty $server.CertificateAuth.ErrorPage)}} - error_page 495 496 = {{ $server.CertificateAuth.ErrorPage }}; - {{ end }} - {{ end }} - - {{ if not (empty $server.ServerSnippet) }} - {{ $server.ServerSnippet }} - {{ end }} - - {{ range $location := $server.Locations }} - {{ $path := buildLocation $location }} - {{ $authPath := buildAuthLocation $location }} - - {{ if not (empty $location.Rewrite.AppRoot)}} - if ($uri = /) { - return 302 {{ $location.Rewrite.AppRoot }}; - } - {{ end }} - - {{ if not (empty $authPath) }} - location = {{ $authPath }} { - internal; - set $proxy_upstream_name "external-authentication"; - - proxy_pass_request_body off; - proxy_set_header Content-Length ""; - - {{ if not (empty $location.ExternalAuth.Method) }} - proxy_method {{ $location.ExternalAuth.Method }}; - proxy_set_header X-Original-URI $request_uri; - proxy_set_header X-Scheme $pass_access_scheme; - {{ end }} - - proxy_set_header Host {{ $location.ExternalAuth.Host }}; - proxy_set_header X-Original-URL $scheme://$http_host$request_uri; - proxy_set_header X-Original-Method $request_method; - proxy_set_header X-Auth-Request-Redirect $request_uri; - proxy_set_header X-Sent-From "nginx-ingress-controller"; - - proxy_ssl_server_name on; - proxy_pass_request_headers on; - client_max_body_size "{{ $location.Proxy.BodySize }}"; - {{ if isValidClientBodyBufferSize $location.ClientBodyBufferSize }} - client_body_buffer_size {{ $location.ClientBodyBufferSize }}; - {{ end }} - - set $target {{ $location.ExternalAuth.URL }}; - proxy_pass $target; - } - {{ end }} - - location {{ $path }} { - {{ if $all.Cfg.EnableVtsStatus }}{{ if $location.VtsFilterKey }} vhost_traffic_status_filter_by_set_key {{ $location.VtsFilterKey }};{{ end }}{{ end }} - - set $proxy_upstream_name "{{ buildUpstreamName $server.Hostname $all.Backends $location }}"; - - {{ $ing := (getIngressInformation $location.Ingress $path) }} - {{/* $ing.Metadata contains the Ingress metadata */}} - set $namespace "{{ $ing.Namespace }}"; - set $ingress_name "{{ $ing.Rule }}"; - set $service_name "{{ $ing.Service }}"; - - {{ if (or $location.Rewrite.ForceSSLRedirect (and (not (empty $server.SSLCertificate)) $location.Rewrite.SSLRedirect)) }} - # enforce ssl on server side - if ($pass_access_scheme = http) { - return 301 https://$best_http_host$request_uri; - } - {{ end }} - - {{ if $all.Cfg.EnableModsecurity }} - modsecurity on; - - modsecurity_rules_file /etc/nginx/modsecurity/modsecurity.conf; - {{ if $all.Cfg.EnableOWASPCoreRules }} - modsecurity_rules_file /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf; - {{ end }} - {{ end }} - - {{ if isLocationAllowed $location }} - {{ if gt (len $location.Whitelist.CIDR) 0 }} - if ({{ buildDenyVariable (print $server.Hostname "_" $path) }}) { - return 403; - } - {{ end }} - - port_in_redirect {{ if $location.UsePortInRedirects }}on{{ else }}off{{ end }}; - - {{ if not (empty $authPath) }} - # this location requires authentication - auth_request {{ $authPath }}; - auth_request_set $auth_cookie $upstream_http_set_cookie; - add_header Set-Cookie $auth_cookie; - {{- range $idx, $line := buildAuthResponseHeaders $location }} - {{ $line }} - {{- end }} - {{ end }} - - {{ if not (empty $location.ExternalAuth.SigninURL) }} - error_page 401 = {{ buildAuthSignURL $location.ExternalAuth.SigninURL }}; - {{ end }} - - {{/* if the location contains a rate limit annotation, create one */}} - {{ $limits := buildRateLimit $location }} - {{ range $limit := $limits }} - {{ $limit }}{{ end }} - - {{ if $location.BasicDigestAuth.Secured }} - {{ if eq $location.BasicDigestAuth.Type "basic" }} - auth_basic "{{ $location.BasicDigestAuth.Realm }}"; - auth_basic_user_file {{ $location.BasicDigestAuth.File }}; - {{ else }} - auth_digest "{{ $location.BasicDigestAuth.Realm }}"; - auth_digest_user_file {{ $location.BasicDigestAuth.File }}; - {{ end }} - proxy_set_header Authorization ""; - {{ end }} - - {{ if $location.CorsConfig.CorsEnabled }} - {{ template "CORS" $location }} - {{ end }} - - {{ if not (empty $location.Redirect.URL) }} - if ($uri ~* {{ $path }}) { - return {{ $location.Redirect.Code }} {{ $location.Redirect.URL }}; - } - {{ end }} - - client_max_body_size "{{ $location.Proxy.BodySize }}"; - {{ if isValidClientBodyBufferSize $location.ClientBodyBufferSize }} - client_body_buffer_size {{ $location.ClientBodyBufferSize }}; - {{ end }} - - {{/* By default use vhost as Host to upstream, but allow overrides */}} - {{ if not (empty $location.UpstreamVhost) }} - proxy_set_header Host "{{ $location.UpstreamVhost }}"; - {{ else }} - proxy_set_header Host $best_http_host; - {{ end }} - - - # Pass the extracted client certificate to the backend - {{ if not (empty $server.CertificateAuth.CAFileName) }} - {{ if $server.CertificateAuth.PassCertToUpstream }} - proxy_set_header ssl-client-cert $ssl_client_escaped_cert; - {{ else }} - proxy_set_header ssl-client-cert ""; - {{ end }} - proxy_set_header ssl-client-verify $ssl_client_verify; - proxy_set_header ssl-client-dn $ssl_client_s_dn; - {{ else }} - proxy_set_header ssl-client-cert ""; - proxy_set_header ssl-client-verify ""; - proxy_set_header ssl-client-dn ""; - {{ end }} - - # Allow websocket connections - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - - proxy_set_header X-Real-IP $the_real_ip; - {{ if $all.Cfg.ComputeFullForwardedFor }} - proxy_set_header X-Forwarded-For $full_x_forwarded_for; - {{ else }} - proxy_set_header X-Forwarded-For $the_real_ip; - {{ end }} - proxy_set_header X-Forwarded-Host $best_http_host; - proxy_set_header X-Forwarded-Port $pass_port; - proxy_set_header X-Forwarded-Proto $pass_access_scheme; - proxy_set_header X-Original-URI $request_uri; - proxy_set_header X-Scheme $pass_access_scheme; - - # Pass the original X-Forwarded-For - proxy_set_header X-Original-Forwarded-For {{ buildForwardedFor $all.Cfg.ForwardedForHeader }}; - - # mitigate HTTPoxy Vulnerability - # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/ - proxy_set_header Proxy ""; - - # Custom headers to proxied server - {{ range $k, $v := $all.ProxySetHeaders }} - proxy_set_header {{ $k }} "{{ $v }}"; - {{ end }} - - proxy_connect_timeout {{ $location.Proxy.ConnectTimeout }}s; - proxy_send_timeout {{ $location.Proxy.SendTimeout }}s; - proxy_read_timeout {{ $location.Proxy.ReadTimeout }}s; - - {{ if (or (eq $location.Proxy.ProxyRedirectFrom "default") (eq $location.Proxy.ProxyRedirectFrom "off")) }} - proxy_redirect {{ $location.Proxy.ProxyRedirectFrom }}; - {{ else }} - proxy_redirect {{ $location.Proxy.ProxyRedirectFrom }} {{ $location.Proxy.ProxyRedirectTo }}; - {{ end }} - proxy_buffering off; - proxy_buffer_size "{{ $location.Proxy.BufferSize }}"; - proxy_buffers 4 "{{ $location.Proxy.BufferSize }}"; - proxy_request_buffering "{{ $location.Proxy.RequestBuffering }}"; - - proxy_http_version 1.1; - - proxy_cookie_domain {{ $location.Proxy.CookieDomain }}; - proxy_cookie_path {{ $location.Proxy.CookiePath }}; - - # In case of errors try the next upstream server before returning an error - proxy_next_upstream {{ buildNextUpstream $location.Proxy.NextUpstream $all.Cfg.RetryNonIdempotent }}; - - {{/* rewrite only works if the content is not compressed */}} - {{ if $location.Rewrite.AddBaseURL }} - proxy_set_header Accept-Encoding ""; - {{ end }} - - {{/* Add any additional configuration defined */}} - {{ $location.ConfigurationSnippet }} - - {{ if not (empty $all.Cfg.LocationSnippet) }} - # Custom code snippet configured in the configuration configmap - {{ $all.Cfg.LocationSnippet }} - {{ end }} - - {{/* if we are sending the request to a custom default backend, we add the required headers */}} - {{ if (hasPrefix $location.Backend "custom-default-backend-") }} - proxy_set_header X-Code 503; - proxy_set_header X-Format $http_accept; - proxy_set_header X-Namespace $namespace; - proxy_set_header X-Ingress-Name $ingress_name; - proxy_set_header X-Service-Name $service_name; - {{ end }} - - - {{ if not (empty $location.Backend) }} - {{ buildProxyPass $server.Hostname $all.Backends $location }} - {{ else }} - # No endpoints available for the request - return 503; - {{ end }} - {{ else }} - # Location denied. Reason: {{ $location.Denied }} - return 503; - {{ end }} - } - - {{ end }} - - {{ if eq $server.Hostname "_" }} - # health checks in cloud providers require the use of port {{ $all.ListenPorts.HTTP }} - location {{ $all.HealthzURI }} { - access_log off; - return 200; - } - - # this is required to avoid error if nginx is being monitored - # with an external software (like sysdig) - location /nginx_status { - allow 127.0.0.1; - {{ if $all.IsIPV6Enabled }}allow ::1;{{ end }} - deny all; - - access_log off; - stub_status on; - } - - {{ end }} - -{{ end }} diff --git a/example/kube/ingress-controller/deployment.yml b/example/kube/ingress-controller/deployment.yml index 61d564b2a..f67f5f8aa 100644 --- a/example/kube/ingress-controller/deployment.yml +++ b/example/kube/ingress-controller/deployment.yml @@ -26,10 +26,6 @@ spec: ports: - containerPort: 80 - containerPort: 443 - # volumeMounts: - # - mountPath: /etc/nginx/template - # name: nginx-template-volume - # readOnly: true env: - name: POD_NAME valueFrom: @@ -44,11 +40,3 @@ spec: - --ingress-class=nginx - --election-id=ingress-controller-leader-external - --default-backend-service=$(POD_NAMESPACE)/default-http-backend - # - --configmap=$(POD_NAMESPACE)/authelia-ingress-controller-config - volumes: - - name: nginx-template-volume - configMap: - name: authelia-ingress-controller-config - items: - - key: nginx.tmpl - path: nginx.tmpl diff --git a/server/src/lib/routes/verify/get_basic_auth.ts b/server/src/lib/routes/verify/get_basic_auth.ts index 6d8dda8cb..e92160b58 100644 --- a/server/src/lib/routes/verify/get_basic_auth.ts +++ b/server/src/lib/routes/verify/get_basic_auth.ts @@ -14,12 +14,12 @@ export default function (req: Express.Request, res: Express.Response, let username: string; let groups: string[]; let domain: string; - let path: string; + let originalUri: string; return new BluebirdPromise<[string, string]>(function (resolve, reject) { - const host = ObjectPath.get(req, "headers.host"); - domain = DomainExtractor.fromHostHeader(host); - path = + const originalUrl = ObjectPath.get(req, "headers.x-original-url"); + domain = DomainExtractor.fromUrl(originalUrl); + originalUri = ObjectPath.get(req, "headers.x-original-uri"); const authenticationMethod = MethodCalculator.compute(vars.config.authentication_methods, domain); @@ -59,7 +59,7 @@ export default function (req: Express.Request, res: Express.Response, }) .then(function (groupsAndEmails) { groups = groupsAndEmails.groups; - return AccessControl(req, vars, domain, path, username, groups); + return AccessControl(req, vars, domain, originalUri, username, groups); }) .then(function () { return BluebirdPromise.resolve({ diff --git a/server/src/lib/routes/verify/get_session_cookie.ts b/server/src/lib/routes/verify/get_session_cookie.ts index e96374c65..6571e8fe8 100644 --- a/server/src/lib/routes/verify/get_session_cookie.ts +++ b/server/src/lib/routes/verify/get_session_cookie.ts @@ -51,7 +51,7 @@ export default function (req: Express.Request, res: Express.Response, let username: string; let groups: string[]; let domain: string; - let path: string; + let originalUri: string; return new BluebirdPromise(function (resolve, reject) { username = authSession.userid; @@ -64,15 +64,15 @@ export default function (req: Express.Request, res: Express.Response, return; } - const host = ObjectPath.get(req, "headers.host"); - path = + const originalUrl = ObjectPath.get(req, "headers.x-original-url"); + originalUri = ObjectPath.get(req, "headers.x-original-uri"); - domain = DomainExtractor.fromHostHeader(host); + domain = DomainExtractor.fromUrl(originalUrl); const authenticationMethod = MethodCalculator.compute(vars.config.authentication_methods, domain); - vars.logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", domain, - path, username, groups.join(",")); + vars.logger.debug(req, "domain=%s, request_uri=%s, user=%s, groups=%s", domain, + originalUri, username, groups.join(",")); if (!authSession.first_factor) return reject(new Exceptions.AccessDeniedError( @@ -87,7 +87,7 @@ export default function (req: Express.Request, res: Express.Response, resolve(); }) .then(function () { - return AccessControl(req, vars, domain, path, username, groups); + return AccessControl(req, vars, domain, originalUri, username, groups); }) .then(function () { return verify_inactivity(req, authSession, diff --git a/server/src/lib/utils/DomainExtractor.ts b/server/src/lib/utils/DomainExtractor.ts index f2e8b8886..ea162a045 100644 --- a/server/src/lib/utils/DomainExtractor.ts +++ b/server/src/lib/utils/DomainExtractor.ts @@ -3,9 +3,4 @@ export class DomainExtractor { if (!url) return ""; return url.match(/https?:\/\/([^\/:]+).*/)[1]; } - - static fromHostHeader(host: string): string { - if (!host) return ""; - return host.split(":")[0]; - } } \ No newline at end of file