apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-controller-config-template
  namespace: ingress
  labels:
    app: ingress-controller
    component: config-template
data:
  nginx.tmpl: |
    {{ $cfg := .cfg }}
    daemon off;

    worker_processes {{ $cfg.workerProcesses }};

    pid /run/nginx.pid;

    worker_rlimit_nofile 131072;

    pcre_jit on;

    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 -}}
        set_real_ip_from    {{ $cfg.proxyRealIpCidr }};
        real_ip_header      proxy_protocol;
        {{ else }}
        real_ip_header      X-Forwarded-For;
        set_real_ip_from    0.0.0.0/0;
        {{ end -}}

        real_ip_recursive   on;

        {{/* 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 $geoip_country_code country::*;
        {{ end -}}

        # lua section to return proper error codes when custom pages are used
        lua_package_path '.?.lua;./etc/nginx/lua/?.lua;/etc/nginx/lua/vendor/lua-resty-http/lib/?.lua;';
        init_by_lua_block {
            require("error_page")
        }

        sendfile            on;
        aio                 threads;
        tcp_nopush          on;
        tcp_nodelay         on;

        log_subrequest      on;

        reset_timedout_connection on;

        keepalive_timeout {{ $cfg.keepAlive }}s;

        types_hash_max_size 2048;
        server_names_hash_max_size {{ $cfg.serverNameHashMaxSize }};
        server_names_hash_bucket_size {{ $cfg.serverNameHashBucketSize }};

        map_hash_bucket_size 128;
        map_hash_max_size 2048;

        include /etc/nginx/mime.types;
        default_type text/html;
        {{ 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;
        {{- end }}

        client_max_body_size "{{ $cfg.bodySize }}";

        log_format upstreaminfo '{{ if $cfg.useProxyProtocol }}$proxy_protocol_addr{{ else }}$remote_addr{{ end }} - '
            '[$proxy_add_x_forwarded_for] - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" '
            '$request_length $request_time $upstream_addr $upstream_response_length $upstream_response_time $upstream_status';

        {{/* map urls that should not appear in access.log */}}
        {{/* http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log */}}
        map $request $loggable {
            {{- range $reqUri := $cfg.skipAccessLogUrls }}
            {{ $reqUri }} 0;{{ end }}
            default 1;
        }

        access_log /var/log/nginx/access.log upstreaminfo if=$loggable;
        error_log  /var/log/nginx/error.log {{ $cfg.errorLogLevel }};

        {{ if not (empty .defResolver) }}# Custom dns resolver.
        resolver {{ .defResolver }} valid=30s;
        {{ end }}

        map $http_upgrade $connection_upgrade {
            default upgrade;
            ''      close;
        }

        # 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 a response error watching the header Content-Type
        map $http_accept $httpAccept {
            default          html;
            application/json json;
            application/xml  xml;
            text/plain       text;
        }

        map $httpAccept $httpReturnType {
            default          text/html;
            json             application/json;
            xml              application/xml;
            text             text/plain;
        }

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

        # 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 .sslDHParam) }}
        # allow custom DH file http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_dhparam
        ssl_dhparam {{ .sslDHParam }};
        {{ end }}

        {{- if not $cfg.enableDynamicTlsRecords }}
        ssl_dyn_rec_size_lo 0;
        {{ end }}

        {{- if .customErrors }}
        # Custom error pages
        proxy_intercept_errors on;
        {{ end }}

        {{- range $errCode := $cfg.customHttpErrors }}
        error_page {{ $errCode }} = @custom_{{ $errCode }};{{ end }}

        # In case of errors try the next upstream server before returning an error
        proxy_next_upstream                     error timeout invalid_header http_502 http_503 http_504{{ if $cfg.retryNonIdempotent }} non_idempotent{{ end }};

        {{range $name, $upstream := .upstreams}}
        upstream {{$upstream.Name}} {
            {{ if $cfg.enableStickySessions -}}
            sticky hash=sha1 httponly;
            {{ else -}}
            least_conn;
            {{- end }}
            {{ range $server := $upstream.Backends }}server {{ $server.Address }}:{{ $server.Port }} max_fails={{ $server.MaxFails }} fail_timeout={{ $server.FailTimeout }};
            {{ end }}
        }
        {{ 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 }}

        {{ range $server := .servers }}
        server {
            server_name {{ $server.Name }};
            listen 80{{ if $cfg.useProxyProtocol }} proxy_protocol{{ end }};
            {{ if $server.SSL }}listen 443 {{ if $cfg.useProxyProtocol }}proxy_protocol{{ end }} ssl {{ if $cfg.enableSpdy }}spdy{{ end }} {{ if $cfg.useHttp2 }}http2{{ 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.SSLCertificateKey }};
            {{- end }}

            {{ if (and $server.SSL $cfg.hsts) -}}
            more_set_headers                            "Strict-Transport-Security: max-age={{ $cfg.hstsMaxAge }}{{ if $cfg.hstsIncludeSubdomains }}; includeSubDomains{{ end }}; preload";
            {{- end }}

            {{ if $cfg.enableVtsStatus }}vhost_traffic_status_filter_by_set_key $geoip_country_code country::$server_name;{{ end }}

            {{- range $location := $server.Locations }}
            {{ $path := buildLocation $location }}
            location {{ $path }} {
                {{ if gt (len $location.Whitelist.CIDR) 0 }}
                {{- range $ip := $location.Whitelist.CIDR }}
                allow {{ $ip }};{{ end }}
                deny all;
                {{ end -}}

                {{ if (and $server.SSL $location.Redirect.SSLRedirect) -}}
                # enforce ssl on server side
                if ($scheme = http) {
                    return 301 https://$host$request_uri;
                }
                {{- end }}
                {{/* if the location contains a rate limit annotation, create one */}}
                {{ $limits := buildRateLimit $location }}
                {{- range $limit := $limits }}
                {{ $limit }}{{ end }}

                {{ if $location.Auth.Secured }}
                {{ if eq $location.Auth.Type "basic" }}
                auth_basic "{{ $location.Auth.Realm }}";
                auth_basic_user_file {{ $location.Auth.File }};
                {{ else }}
                #TODO: add nginx-http-auth-digest module
                auth_digest "{{ $location.Auth.Realm }}";
                auth_digest_user_file {{ $location.Auth.File }};
                {{ end }}
                proxy_set_header Authorization "";
                {{- end }}

                proxy_set_header Host                   $host;

                # Pass Real IP
                proxy_set_header X-Real-IP              $remote_addr;

                # Allow websocket connections
                proxy_set_header                        Upgrade           $http_upgrade;
                proxy_set_header                        Connection        $connection_upgrade;

                proxy_set_header X-Forwarded-For        $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Host       $host;
                proxy_set_header X-Forwarded-Port       $server_port;
                proxy_set_header X-Forwarded-Proto      $pass_access_scheme;

                # mitigate HTTPoxy Vulnerability
                # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/
                proxy_set_header Proxy                  "";

                proxy_connect_timeout                   {{ $cfg.proxyConnectTimeout }}s;
                proxy_send_timeout                      {{ $cfg.proxySendTimeout }}s;
                proxy_read_timeout                      {{ $cfg.proxyReadTimeout }}s;

                proxy_redirect                          off;
                proxy_buffering                         off;
                # proxy_buffer_size                       {{ $cfg.proxyBufferSize }};

                proxy_http_version                      1.1;

                {{/* rewrite only works if the content is not compressed */}}
                {{ if $location.Redirect.AddBaseURL -}}
                proxy_set_header                        Accept-Encoding     "";
                {{- end }}

                {{- buildProxyPass $location }}
            }
            {{ end }}

            {{ if eq $server.Name "_" }}
            # 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;
                deny all;

                access_log off;
                stub_status on;
            }
            {{ end }}
            {{ template "CUSTOM_ERRORS" $cfg }}
        }
        {{ end }}

        # default server, used for NGINX healthcheck and access to nginx stats
        server {
            # Use the port 18080 (random value just to avoid known ports) as default port for nginx.
            # Changing this value requires a change in:
            # https://github.com/kubernetes/contrib/blob/master/ingress/controllers/nginx/nginx/command.go#L104
            listen 18080 default_server reuseport backlog={{ .backlogSize }};

            location /healthz {
                access_log off;
                return 200;
            }

            location /nginx_status {
                {{ if $cfg.enableVtsStatus -}}
                vhost_traffic_status_display;
                vhost_traffic_status_display_format html;
                {{ else }}
                access_log off;
                stub_status on;
                {{- end }}
            }

            location / {
                proxy_pass             http://upstream-default-backend;
            }
            {{- template "CUSTOM_ERRORS" $cfg }}
        }

        # default server for services without endpoints
        server {
            listen 8181;

            location / {
                {{ if .customErrors }}
                content_by_lua_block {
                    openURL(503)
                }
                {{ else }}
                return 503;
                {{ end }}
            }
        }
    }

    stream {
    # TCP services
    {{ range $i, $tcpServer := .tcpUpstreams }}
        upstream tcp-{{ $tcpServer.Upstream.Name }} {
            {{ range $server := $tcpServer.Upstream.Backends }}server {{ $server.Address }}:{{ $server.Port }};
            {{ end }}
        }

        server {
            listen {{ $tcpServer.Path }};
            proxy_connect_timeout  {{ $cfg.proxyConnectTimeout }};
            proxy_timeout          {{ $cfg.proxyReadTimeout }};
            proxy_pass             tcp-{{ $tcpServer.Upstream.Name }};
        }
    {{ end }}

    # UDP services
    {{ range $i, $udpServer := .udpUpstreams }}
        upstream udp-{{ $udpServer.Upstream.Name }} {
            {{ range $server := $udpServer.Upstream.Backends }}server {{ $server.Address }}:{{ $server.Port }};
            {{ end }}
        }

        server {
            listen {{ $udpServer.Path }} udp;
            proxy_timeout          10s;
            proxy_responses        1;
            proxy_pass             udp-{{ $udpServer.Upstream.Name }};
        }
    {{ end }}
    }

    {{/* definition of templates to avoid repetitions */}}
    {{ define "CUSTOM_ERRORS" }}
            {{ range $errCode := .customHttpErrors }}
            location @custom_{{ $errCode }} {
                internal;
                content_by_lua_block {
                    openURL({{ $errCode }})
                }
            }
            {{ end }}
    {{ end }}
