Adapt kube example to work without custom nginx template

pull/209/head
Clement Michaud 2018-04-24 23:59:15 +02:00
parent 48d6107b0b
commit 4be299d6eb
15 changed files with 173 additions and 1817 deletions

View File

@ -1,17 +1,21 @@
# Authelia on Kubernetes # Authelia on Kubernetes
Authelia is now available on Kube in order to protect your most critical 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 ## 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 don't, please follow the next section otherwise skip it and go
to the next. to the next.
### Set up a Kube cluster ### 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 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 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. 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 app2.kube.example.com
192.168.39.26 mail.kube.example.com 192.168.39.26 mail.kube.example.com
192.168.39.26 home.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 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? ## How does it work?
### Authentication via Authelia ### Authentication via Authelia
In a Kube clusters, the routing logic of requests is handled by ingress 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) In this example, [ingress-nginx](https://github.com/kubernetes/ingress-nginx)
controller which forward verification requests to Authelia in order to allow controller has been installed to handle the incoming requests. Some of them
or deny access. (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 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 `nginx.ingress.kubernetes.io/auth-url` that is filled with the URL of
Authelia's verification endpoint. Authelia's verification endpoint.
The ingress controller also requires the ingress provides the URL of the The ingress controller also requires the URL to the
authentication portal in case the user is not yet authenticated. 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. Those annotations can be seen in `apps/secure-ingress.yml` configuration.
### Production grade infrastructure ### 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) 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? ## 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 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 cluster and a SMTP server, you'll only need to install the ingress-controller
and Authelia whose configurations are respectively in `ingress-controller` and and Authelia whose kubernetes deployment configurations are respectively in
`authelia` directories. `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 ### I'm already using ingress-nginx
If you're already using ingress-nginx as your ingress controller, the only If you're already using ingress-nginx as your ingress controller, you only
thing you'll need to change is the nginx template used by the controller to need to install Authelia with its configuration and that's it!
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.
## Questions ## Questions

View File

@ -180,7 +180,8 @@ storage:
# Settings to connect to mongo server # Settings to connect to mongo server
mongo: mongo:
url: mongodb://mongo-service/authelia url: mongodb://mongo-service
database: authelia
# Configuration of the notification system. # Configuration of the notification system.
# #

View File

@ -18,7 +18,8 @@ spec:
spec: spec:
containers: containers:
- name: authelia - name: authelia
image: clems4ever/authelia:v3.7.0 image: localhost:5000/authelia:latest
imagePullPolicy: Always
ports: ports:
- containerPort: 80 - containerPort: 80
volumeMounts: volumeMounts:

View File

@ -19,7 +19,6 @@ start_apps() {
} }
start_ingress_controller() { start_ingress_controller() {
kubectl create configmap authelia-ingress-controller-config --namespace=authelia --from-file=ingress-controller/configs/nginx.tmpl
kubectl apply -f ingress-controller kubectl apply -f ingress-controller
} }
@ -43,12 +42,17 @@ start_ldap() {
kubectl apply -f ldap kubectl apply -f ldap
} }
start_docker_registry() {
kubectl apply -f docker-registry
}
# Create the Authelia namespace in the cluster # Create the Authelia namespace in the cluster
create_namespace() { create_namespace() {
kubectl apply -f namespace.yml kubectl apply -f namespace.yml
} }
create_namespace create_namespace
start_docker_registry
start_storage start_storage
start_ldap start_ldap
start_mailcatcher start_mailcatcher

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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: {}

View File

@ -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

View File

@ -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 }}

View File

@ -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 }}

View File

@ -26,10 +26,6 @@ spec:
ports: ports:
- containerPort: 80 - containerPort: 80
- containerPort: 443 - containerPort: 443
# volumeMounts:
# - mountPath: /etc/nginx/template
# name: nginx-template-volume
# readOnly: true
env: env:
- name: POD_NAME - name: POD_NAME
valueFrom: valueFrom:
@ -44,11 +40,3 @@ spec:
- --ingress-class=nginx - --ingress-class=nginx
- --election-id=ingress-controller-leader-external - --election-id=ingress-controller-leader-external
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend - --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

View File

@ -14,12 +14,12 @@ export default function (req: Express.Request, res: Express.Response,
let username: string; let username: string;
let groups: string[]; let groups: string[];
let domain: string; let domain: string;
let path: string; let originalUri: string;
return new BluebirdPromise<[string, string]>(function (resolve, reject) { return new BluebirdPromise<[string, string]>(function (resolve, reject) {
const host = ObjectPath.get<Express.Request, string>(req, "headers.host"); const originalUrl = ObjectPath.get<Express.Request, string>(req, "headers.x-original-url");
domain = DomainExtractor.fromHostHeader(host); domain = DomainExtractor.fromUrl(originalUrl);
path = originalUri =
ObjectPath.get<Express.Request, string>(req, "headers.x-original-uri"); ObjectPath.get<Express.Request, string>(req, "headers.x-original-uri");
const authenticationMethod = const authenticationMethod =
MethodCalculator.compute(vars.config.authentication_methods, domain); MethodCalculator.compute(vars.config.authentication_methods, domain);
@ -59,7 +59,7 @@ export default function (req: Express.Request, res: Express.Response,
}) })
.then(function (groupsAndEmails) { .then(function (groupsAndEmails) {
groups = groupsAndEmails.groups; groups = groupsAndEmails.groups;
return AccessControl(req, vars, domain, path, username, groups); return AccessControl(req, vars, domain, originalUri, username, groups);
}) })
.then(function () { .then(function () {
return BluebirdPromise.resolve({ return BluebirdPromise.resolve({

View File

@ -51,7 +51,7 @@ export default function (req: Express.Request, res: Express.Response,
let username: string; let username: string;
let groups: string[]; let groups: string[];
let domain: string; let domain: string;
let path: string; let originalUri: string;
return new BluebirdPromise(function (resolve, reject) { return new BluebirdPromise(function (resolve, reject) {
username = authSession.userid; username = authSession.userid;
@ -64,15 +64,15 @@ export default function (req: Express.Request, res: Express.Response,
return; return;
} }
const host = ObjectPath.get<Express.Request, string>(req, "headers.host"); const originalUrl = ObjectPath.get<Express.Request, string>(req, "headers.x-original-url");
path = originalUri =
ObjectPath.get<Express.Request, string>(req, "headers.x-original-uri"); ObjectPath.get<Express.Request, string>(req, "headers.x-original-uri");
domain = DomainExtractor.fromHostHeader(host); domain = DomainExtractor.fromUrl(originalUrl);
const authenticationMethod = const authenticationMethod =
MethodCalculator.compute(vars.config.authentication_methods, domain); MethodCalculator.compute(vars.config.authentication_methods, domain);
vars.logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", domain, vars.logger.debug(req, "domain=%s, request_uri=%s, user=%s, groups=%s", domain,
path, username, groups.join(",")); originalUri, username, groups.join(","));
if (!authSession.first_factor) if (!authSession.first_factor)
return reject(new Exceptions.AccessDeniedError( return reject(new Exceptions.AccessDeniedError(
@ -87,7 +87,7 @@ export default function (req: Express.Request, res: Express.Response,
resolve(); resolve();
}) })
.then(function () { .then(function () {
return AccessControl(req, vars, domain, path, username, groups); return AccessControl(req, vars, domain, originalUri, username, groups);
}) })
.then(function () { .then(function () {
return verify_inactivity(req, authSession, return verify_inactivity(req, authSession,

View File

@ -3,9 +3,4 @@ export class DomainExtractor {
if (!url) return ""; if (!url) return "";
return url.match(/https?:\/\/([^\/:]+).*/)[1]; return url.match(/https?:\/\/([^\/:]+).*/)[1];
} }
static fromHostHeader(host: string): string {
if (!host) return "";
return host.split(":")[0];
}
} }