Стадия жизненного цикла модуля: Общедоступная версия

У модуля есть требования для установки

Как сконфигурировать балансировщик нагрузки для проверки доступности IngressNginxController?

В ситуации, когда Ingress-контроллер размещен за балансировщиком нагрузки, рекомендуется сконфигурировать балансировщик для проверки доступности узлов Ingress-контроллер с помощью HTTP-запросов или TCP-подключений. В то время как тестирование с помощью TCP-подключений представляет собой простой и универсальный механизм проверки доступности, мы рекомендуем использовать проверку на основе HTTP-запросов со следующими параметрами:

  • протокол: HTTP;
  • путь: /healthz;
  • порт: 80 (в случае использования инлета HostPort нужно указать номер порта, соответствующий параметру httpPort).

Как настроить работу через MetalLB с доступом только из внутренней сети?

Пример MetalLB с настройками доступа только из внутренней сети:

apiVersion: deckhouse.io/v2
kind: IngressNginxController
metadata:
  name: main
spec:
  ingressClass: "nginx"
  inlet: "LoadBalancer"
  loadBalancer:
    sourceRanges:
    - 192.168.0.0/24

Для работы необходимо включить параметр svcSourceRangeCheck в модуле cni-cilium.

Как добавить дополнительные поля для логирования в nginx-controller?

Пример добавления дополнительных полей:

apiVersion: deckhouse.io/v2
kind: IngressNginxController
metadata:
  name: main
spec:
  ingressClass: "nginx"
  inlet: "LoadBalancer"
  additionalLogFields:
    my-cookie: "$cookie_MY_COOKIE"

Как включить HorizontalPodAutoscaling для IngressNginxController?

Режим HPA возможен только для контроллеров с инлетом LoadBalancer или LoadBalancerWithProxyProtocol.

Режим HPA возможен только при minReplicas != maxReplicas, в противном случае deployment hpa-scaler не создается.

Для включения HPA используйте атрибуты minReplicas и maxReplicas в IngressNginxController CR.

IngressNginxController разворачивается с помощью DaemonSet. DaemonSet не предоставляет возможности горизонтального масштабирования, поэтому создается дополнительный deployment hpa-scaler и HPA resource, который следит за предварительно созданной метрикой prometheus-metrics-adapter-d8-ingress-nginx-cpu-utilization-for-hpa. Если CPU utilization превысит 50%, HPA закажет новую реплику для hpa-scaler (с учетом minReplicas и maxReplicas).

Deployment hpa-scaler обладает HardPodAntiAffinity (запрет на размещение подов с одинаковыми лейблами на одном узле), поэтому он попытается выделить для себя новый узел (если это возможно в рамках своей группы узлов), куда автоматически будет размещен еще один instance Ingress-контроллера.

  • Минимальное реальное количество реплик IngressNginxController не может быть меньше минимального количества узлов в группе узлов, куда он разворачивается.
  • Максимальное реальное количество реплик IngressNginxController не может быть больше максимального количества узлов в группе узлов, куда он разворачивается.

Как использовать IngressClass с установленными IngressClassParameters?

Начиная с версии 1.1 IngressNginxController, Deckhouse создает объект IngressClass самостоятельно. Если вы хотите использовать свой IngressClass с установленными IngressClassParameters, достаточно добавить к нему label ingress-class.deckhouse.io/external: "true":

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  labels:
    ingress-class.deckhouse.io/external: "true"
  name: my-super-ingress
spec:
  controller: ingress-nginx.deckhouse.io/my-super-ingress
  parameters:
    apiGroup: elbv2.k8s.aws
    kind: IngressClassParams
    name: awesome-class-cfg

В этом случае, при указании данного IngressClass в CRD IngressNginxController, Deckhouse не будет создавать объект, а использует существующий.

Как отключить сборку детализированной статистики Ingress-ресурсов?

По умолчанию Deckhouse собирает подробную статистику со всех Ingress-ресурсов в кластере. Этот процесс может приводить к высокой нагрузке системы мониторинга.

Для отключения сбора статистики добавьте лейбл ingress.deckhouse.io/discard-metrics: "true" к соответствующему пространству имён или Ingress-ресурсу.

Пример отключения сбора статистики (метрик) для всех Ingress-ресурсов в пространстве имен review-1:

d8 k label ns review-1 ingress.deckhouse.io/discard-metrics=true

Пример отключения сбора статистики (метрик) для всех Ingress-ресурсов test-site в пространстве имен development:

d8 k label ingress test-site -n development ingress.deckhouse.io/discard-metrics=true

Как корректно вывести из эксплуатации (drain) узел с запущенным IngressNginxController?

Доступно два способа корректного вывода из эксплуатации узла, на котором запущен IngressNginxController.

  1. С помощью аннотации.

    Аннотация будет автоматически удалена после завершения операции.

    d8 k annotate node <node_name> update.node.deckhouse.io/draining=user
  2. С помощью d8 k drain.

    При использовании стандартной команды d8 k drain необходимо указать флаг --force даже при наличии --ignore-daemonsets, поскольку IngressNginxController развёрнут с использованием Advanced DaemonSet:

    d8 k drain <node_name> --delete-emptydir-data --ignore-daemonsets --force

Как включить Web Application Firewall (WAF)?

Для защиты веб-приложений от L7-атак используется программное обеспечение известное как Web Application Firewall (WAF). В ingress-nginx контроллер встроен WAF под названием ModSecurity (проект Open Worldwide Application Security).

По умолчанию ModSecurity выключен.

Включение ModSecurity

Для включения ModSecurity необходимо задать параметры в кастомном ресурсе IngressNginxController, в секции config:

apiVersion: deckhouse.io/v2
kind: IngressNginxController
metadata:
  name: <имя_контроллера>
spec:
  config:
    enable-modsecurity: "true"
    modsecurity-snippet: |
      Include /etc/nginx/modsecurity/modsecurity.conf

После применения настроек ModSecurity начнет работать для всего трафика, проходящего через данный ingress-nginx контроллер. При этом используется режим аудита (DetectionOnly) и базовая рекомендуемая конфигурация.

Настройка ModSecurity

ModSecurity можно настраивать двумя способами:

  1. Для всего ingress-nginx контроллера
    • необходимые директивы описываются в секции config.modsecurity-snippet в кастомном ресурсе IngressNginxController, как в примере выше.
  2. Для каждого кастомного ресурса Ingress по отдельности
    • необходимые директивы описываются в аннотации nginx.ingress.kubernetes.io/modsecurity-snippet: | непосредственно в манифестах Ingress.

Использование snippet-аннотаций не рекомендуется из-за уязвимостей безопасности. Аннотация nginx.ingress.kubernetes.io/modsecurity-snippet относится к ним и не работает, если snippet-аннотации отключены. Ограничения можно задать с помощью параметра annotationPolicy, например установив allowSnippetAnnotations: false.

Чтобы включить выполнение правил (а не только логирование), добавьте директиву SecRuleEngine On по примеру ниже:

apiVersion: deckhouse.io/v2
kind: IngressNginxController
metadata:
  name: <имя_контролера>
spec:
  config:
    enable-modsecurity: "true"
    modsecurity-snippet: |
      Include /etc/nginx/modsecurity/modsecurity.conf
      SecRuleEngine On

Добавление SecRule

Пример правила для ограничения количества аргументов в URL-адресе запроса. Если количество аргументов превышает 10, сервер отклоняет запрос с кодом ошибки 400 (Bad request).

apiVersion: deckhouse.io/v2
kind: IngressNginxController
metadata:
  name: <имя_контролера>
spec:
  config:
    enable-modsecurity: "true"
    modsecurity-snippet: |
      Include /etc/nginx/modsecurity/modsecurity.conf
      SecRuleEngine On
      SecRule &ARGS "@gt 10" "id:100100,phase:2,deny,status:400,log,auditlog,severity:WARNING,msg:\"too many args\""

Полный перечень и описание директив вы можете найти в официальной документации.

Включение OWASP Core Rule Set (CRS)

Для включения OWASP Core Rule Set (CRS) необходимо добавить config.enable-owasp-modsecurity-crs в кастомном ресурсе IngressNginxController, как показано на примере ниже.

apiVersion: deckhouse.io/v2
kind: IngressNginxController
metadata:
  name: <name_of_the_controller>
spec:
  config:
    enable-modsecurity: "true"
    enable-owasp-modsecurity-crs: "true"
    modsecurity-snippet: |
      Include /etc/nginx/modsecurity/modsecurity.conf
      SecRuleEngine On

Дополнительные аннотации Ingress

Зачем нужны дополнительные аннотации?

Snippet-аннотации (nginx.ingress.kubernetes.io/configuration-snippet, nginx.ingress.kubernetes.io/server-snippet, nginx.ingress.kubernetes.io/auth-snippet) предназначены для вставки произвольных директив в конфигурацию NGINX: их содержимое переносится в неё как есть, без какой-либо проверки. Контроллер не отличает корректные изменения от ошибочных или злонамеренных — любое из них попадает в общую конфигурацию NGINX, которая обслуживает все Ingress-ресурсы контроллера. Ошибка в snippet-аннотации одного Ingress-ресурса может нарушить работу других, а пользователь с правом создавать Ingress-ресурсы может выполнить произвольные директивы NGINX и код на Lua: прочитать файлы контроллера, включая токен его ServiceAccount и TLS-ключи всех Ingress-ресурсов, или обратиться к внутренним сервисам кластера. На этом основана, в частности, уязвимость CVE-2021-25742. По этим причинам в upstream-версии ingress-nginx snippet-аннотации по умолчанию отключены.

В модуле snippet-аннотации по умолчанию разрешены: если в annotationPolicy не указано иное, контроллер получает параметры allow-snippet-annotations: "true" и annotations-risk-level: Critical. Описанные ниже аннотации покрывают наиболее распространённые сценарии использования snippet-аннотаций и позволяют отказаться от них, установив annotationPolicy.allowSnippetAnnotations: false. В отличие от snippet-аннотаций:

  • значения аннотаций проходят проверку, а Ingress-ресурс с некорректным значением отклоняется;
  • конфигурация NGINX формируется по фиксированным шаблонам, и значения аннотаций не попадают в неё в исходном виде;
  • аннотации продолжают работать при allowSnippetAnnotations: false.

Если snippet-аннотации необходимо оставить включёнными, их содержимое можно ограничить параметром annotationPolicy.valueWordBlocklist: контроллер отклоняет Ingress-ресурс, если значение любой его аннотации nginx.ingress.kubernetes.io/ содержит одно из перечисленных слов, например load_module, lua_package, _by_lua, location, root, proxy_pass или serviceaccount. Такой список отсеивает заведомо опасные конструкции, но не делает snippet-аннотации безопасными. Он применяется ко всем аннотациям, включая описанные ниже, и не должен содержать слов, которые могут понадобиться в их значениях.

Аннотации реализованы в Deckhouse и отсутствуют в upstream-версии ingress-nginx; их поддерживают все версии контроллера, доступные в модуле.

Уровни риска

При заданном параметре annotationPolicy.riskLevel контроллер отклоняет Ingress-ресурсы, содержащие аннотации с более высоким уровнем риска; если параметр не задан, допускаются аннотации любого уровня.

Аннотация Уровень риска
nginx.ingress.kubernetes.io/ingress-nginx-hsts Не проверяется
nginx.ingress.kubernetes.io/ingress-nginx-hsts-max-age, nginx.ingress.kubernetes.io/ingress-nginx-hsts-include-subdomains, nginx.ingress.kubernetes.io/ingress-nginx-hsts-preload Low
nginx.ingress.kubernetes.io/proxy-ssl-use-controller-certificate Не проверяется
nginx.ingress.kubernetes.io/auth-proxy-ssl-use-controller-certificate Medium
nginx.ingress.kubernetes.io/auth-proxy-ssl-protocols, nginx.ingress.kubernetes.io/auth-proxy-ssl-server-name, nginx.ingress.kubernetes.io/auth-proxy-ssl-name Low
nginx.ingress.kubernetes.io/auth-proxy-connect-timeout, nginx.ingress.kubernetes.io/auth-proxy-send-timeout, nginx.ingress.kubernetes.io/auth-proxy-read-timeout Low
nginx.ingress.kubernetes.io/auth-response-headers-to-client Medium
nginx.ingress.kubernetes.io/auth-error-status-codes Low
nginx.ingress.kubernetes.io/proxy-request-headers Medium
nginx.ingress.kubernetes.io/response-headers-by-status Medium
nginx.ingress.kubernetes.io/limit-req-status-code, nginx.ingress.kubernetes.io/limit-conn-status-code Low
nginx.ingress.kubernetes.io/fetch-metadata-protection, nginx.ingress.kubernetes.io/fetch-metadata-sites, nginx.ingress.kubernetes.io/fetch-metadata-deny-modes, nginx.ingress.kubernetes.io/fetch-metadata-allowed-methods, nginx.ingress.kubernetes.io/fetch-metadata-status-code Low
nginx.ingress.kubernetes.io/redirect-preserve Low
nginx.ingress.kubernetes.io/response-html-head-injection High

Для каждого примера ниже в скобках указан наибольший уровень риска среди всех его аннотаций, включая upstream-аннотации, то есть минимальное значение riskLevel, при котором пример работает.

Директивы, которые не нужно заменять

Некоторые директивы дублируют действия самого контроллера, и их достаточно удалить:

  • proxy_set_header Authorization $http_authorization; и proxy_set_header X-Auth-Token $http_x_auth_token; в nginx.ingress.kubernetes.io/auth-snippet — контроллер передаёт сервису аутентификации все заголовки исходного запроса;
  • proxy_set_header X-Real-IP $remote_addr; в nginx.ingress.kubernetes.io/auth-snippet — контроллер устанавливает этот заголовок самостоятельно.

Статические заголовки, которые добавляются во все ответы (например, X-Frame-Options или Content-Security-Policy), задаются upstream-аннотацией nginx.ingress.kubernetes.io/custom-headers со ссылкой на ConfigMap. Заголовки X-Frame-Options, X-Content-Type-Options и Content-Security-Policy разрешены по умолчанию; остальные необходимо перечислить в параметре global-allowed-response-headers в spec.config, значение которого дополняет список по умолчанию.

Совместное использование со snippet-аннотациями

Ingress-ресурс отклоняется, если одна из аннотаций ниже и snippet-аннотация того же ресурса задают одну и ту же директиву. Повторное задание большинства директив в одном location NGINX считает ошибкой конфигурации, а для остальных объединяет значения или игнорирует второе из них.

Аннотация Snippet-аннотация Директивы, которые в ней не допускаются
nginx.ingress.kubernetes.io/proxy-ssl-use-controller-certificate configuration-snippet proxy_ssl_certificate, proxy_ssl_certificate_key, proxy_ssl_protocols, proxy_ssl_session_reuse
nginx.ingress.kubernetes.io/auth-proxy-ssl-use-controller-certificate auth-snippet proxy_ssl_certificate, proxy_ssl_certificate_key
nginx.ingress.kubernetes.io/auth-proxy-ssl-protocols, nginx.ingress.kubernetes.io/auth-proxy-ssl-server-name, nginx.ingress.kubernetes.io/auth-proxy-ssl-name auth-snippet proxy_ssl_protocols, proxy_ssl_server_name, proxy_ssl_name соответственно
nginx.ingress.kubernetes.io/auth-proxy-connect-timeout, nginx.ingress.kubernetes.io/auth-proxy-send-timeout, nginx.ingress.kubernetes.io/auth-proxy-read-timeout auth-snippet proxy_connect_timeout, proxy_send_timeout, proxy_read_timeout соответственно
nginx.ingress.kubernetes.io/limit-req-status-code, nginx.ingress.kubernetes.io/limit-conn-status-code configuration-snippet limit_req_status, limit_conn_status соответственно
nginx.ingress.kubernetes.io/response-html-head-injection configuration-snippet sub_filter_once

Комментарии в snippet-аннотациях не учитываются, однако имя директивы внутри значения в кавычках принимается за директиву. Параметр location-snippet из spec.config вставляется в каждый location без такой проверки, и задавать в нём эти директивы также нельзя.

HSTS: nginx.ingress.kubernetes.io/ingress-nginx-hsts

Было (Critical):

nginx.ingress.kubernetes.io/configuration-snippet: |
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Стало (уровень риска не проверяется):

nginx.ingress.kubernetes.io/ingress-nginx-hsts: "true"

Аннотация добавляет заголовок Strict-Transport-Security: max-age=31536000; includeSubDomains во все ответы location-блоков Ingress-ресурса. Параметры политики задаются следующими аннотациями (уровень Low), добавленными в версии модуля v1.3.0; при отсутствии ingress-nginx-hsts: "true" Ingress-ресурс с этими аннотациями отклоняется:

  • nginx.ingress.kubernetes.io/ingress-nginx-hsts-max-age — значение max-age в секундах, от 0 до 63072000;
  • nginx.ingress.kubernetes.io/ingress-nginx-hsts-include-subdomains — по умолчанию true;
  • nginx.ingress.kubernetes.io/ingress-nginx-hsts-preload — по умолчанию false; для значения true нужны includeSubDomains и max-age не меньше 31536000.
nginx.ingress.kubernetes.io/ingress-nginx-hsts: "true"
nginx.ingress.kubernetes.io/ingress-nginx-hsts-max-age: "63072000"
nginx.ingress.kubernetes.io/ingress-nginx-hsts-preload: "true"

Политика HSTS действует для всего хоста, и во всех его Ingress-ресурсах она должна быть одинаковой.

Аннотация имеет приоритет над общим для контроллера параметром hsts: в location-блоках Ingress-ресурса он не применяется, а заголовок Strict-Transport-Security, полученный от бэкенда или заданный аннотацией nginx.ingress.kubernetes.io/custom-headers, заменяется. Для отдельных кодов ответа другое значение по-прежнему можно задать аннотацией nginx.ingress.kubernetes.io/response-headers-by-status.

Клиентский сертификат контроллера для бэкенда: nginx.ingress.kubernetes.io/proxy-ssl-use-controller-certificate

Было (Critical):

nginx.ingress.kubernetes.io/configuration-snippet: |
  proxy_ssl_certificate /etc/nginx/ssl/client.crt;
  proxy_ssl_certificate_key /etc/nginx/ssl/client.key;
  proxy_ssl_protocols TLSv1.2;
  proxy_ssl_session_reuse on;

Стало (Low — из-за backend-protocol; уровень риска самой аннотации не проверяется):

nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
nginx.ingress.kubernetes.io/proxy-ssl-use-controller-certificate: "true"

Аннотация добавляет эти четыре директивы в location-блоки Ingress-ресурса: контроллер предъявляет HTTPS-бэкенду клиентский сертификат, выпущенный для него модулем. Поскольку сертификат удостоверяет контроллер, аннотация допускается только в Ingress-ресурсах модулей Deckhouse, то есть если имя пространства имён начинается с d8-, а у Ingress-ресурса есть метка heritage: deckhouse и непустая метка module. Аннотацию нельзя сочетать с nginx.ingress.kubernetes.io/proxy-ssl-secret, которая задаёт собственные параметры TLS для соединения с бэкендом: такой Ingress-ресурс отклоняется.

Клиентский сертификат контроллера для внешней аутентификации: nginx.ingress.kubernetes.io/auth-proxy-ssl-use-controller-certificate

Добавлена в версии модуля v1.3.0.

Было (Critical):

nginx.ingress.kubernetes.io/auth-snippet: |
  proxy_ssl_certificate /etc/nginx/ssl/client.crt;
  proxy_ssl_certificate_key /etc/nginx/ssl/client.key;

Стало (High — из-за auth-url; уровень самой аннотации — Medium):

nginx.ingress.kubernetes.io/auth-url: "https://auth.example.com/check"
nginx.ingress.kubernetes.io/auth-proxy-ssl-use-controller-certificate: "true"

Аннотация добавляет директивы proxy_ssl_certificate и proxy_ssl_certificate_key с сертификатом контроллера во внутренний location, через который отправляется запрос аутентификации; на соединение с бэкендом она не влияет. Для аннотации требуется nginx.ingress.kubernetes.io/auth-url со схемой https. Как и nginx.ingress.kubernetes.io/proxy-ssl-use-controller-certificate, аннотация допускается только в Ingress-ресурсах модулей Deckhouse.

TLS при внешней аутентификации: nginx.ingress.kubernetes.io/auth-proxy-ssl-protocols, nginx.ingress.kubernetes.io/auth-proxy-ssl-server-name, nginx.ingress.kubernetes.io/auth-proxy-ssl-name

Добавлены в версии модуля v1.3.0.

Было (Critical):

nginx.ingress.kubernetes.io/auth-snippet: |
  proxy_ssl_protocols TLSv1.2 TLSv1.3;
  proxy_ssl_server_name off;
  proxy_ssl_name auth.internal;

Стало (High — из-за auth-url; уровень самих аннотаций — Low):

nginx.ingress.kubernetes.io/auth-url: "https://auth.example.com/check"
nginx.ingress.kubernetes.io/auth-proxy-ssl-protocols: "TLSv1.2 TLSv1.3"
nginx.ingress.kubernetes.io/auth-proxy-ssl-server-name: "false"
nginx.ingress.kubernetes.io/auth-proxy-ssl-name: "auth.internal"

Аннотации задают директивы proxy_ssl_protocols, proxy_ssl_server_name (по умолчанию on) и proxy_ssl_name в location аутентификации. Допустимые протоколы — TLSv1, TLSv1.1, TLSv1.2 и TLSv1.3; имя должно быть корректным DNS-именем. Для аннотаций требуется nginx.ingress.kubernetes.io/auth-url со схемой https.

Таймауты внешней аутентификации: nginx.ingress.kubernetes.io/auth-proxy-connect-timeout, nginx.ingress.kubernetes.io/auth-proxy-send-timeout, nginx.ingress.kubernetes.io/auth-proxy-read-timeout

Добавлены в версии модуля v1.3.0.

Было (Critical):

nginx.ingress.kubernetes.io/auth-snippet: |
  proxy_connect_timeout 2;
  proxy_send_timeout 2;
  proxy_read_timeout 2;

Стало (Low):

nginx.ingress.kubernetes.io/auth-proxy-connect-timeout: "2"
nginx.ingress.kubernetes.io/auth-proxy-send-timeout: "2"
nginx.ingress.kubernetes.io/auth-proxy-read-timeout: "2"

Аннотации задают директивы proxy_connect_timeout, proxy_send_timeout и proxy_read_timeout в location аутентификации (в секундах, от 1 до 3600). По умолчанию таймаут установки соединения — 15 секунд, таймауты отправки и чтения — 60 секунд. Аннотации nginx.ingress.kubernetes.io/proxy-*-timeout действуют только на соединение с бэкендом. При превышении таймаута клиент получает ответ 500.

Передача клиенту заголовков ответа аутентификации: nginx.ingress.kubernetes.io/auth-response-headers-to-client

Добавлена в версии модуля v1.3.0.

Было (Critical):

nginx.ingress.kubernetes.io/configuration-snippet: |
  auth_request_set $token_rotation $upstream_http_x_token_rotation_pending;
  add_header X-Token-Rotation-Pending $token_rotation always;

Стало (Medium):

nginx.ingress.kubernetes.io/auth-response-headers-to-client: "X-Token-Rotation-Pending"

Аннотация принимает перечисленные через запятую заголовки ответа сервиса аутентификации и добавляет их в ответ клиенту при любом коде ответа. Заголовки, которых нет в ответе сервиса аутентификации, не добавляются. Для передачи заголовков бэкенду предназначена стандартная аннотация nginx.ingress.kubernetes.io/auth-response-headers. Заголовки добавляются директивой add_header: если заголовок с тем же именем задан аннотацией nginx.ingress.kubernetes.io/custom-headers или nginx.ingress.kubernetes.io/response-headers-by-status, клиент получает оба значения.

Коды ошибок аутентификации: nginx.ingress.kubernetes.io/auth-error-status-codes

Добавлена в версии модуля v1.3.0.

Было (Critical):

nginx.ingress.kubernetes.io/configuration-snippet: |
  auth_request_set $auth_status $upstream_status;
  error_page 500 = @auth_error;
nginx.ingress.kubernetes.io/server-snippet: |
  location @auth_error {
    internal;
    if ($auth_status = 429) { return 429; }
    return 500;
  }

Стало (Low):

nginx.ingress.kubernetes.io/auth-error-status-codes: "429"

Если сервис аутентификации возвращает код, отличный от 2xx, 401 и 403, NGINX отвечает клиенту кодом 500. Аннотация задаёт список кодов через запятую (от 400 до 599, кроме 401 и 403), которые передаются клиенту без изменений. Необходимые переменную, директиву error_page и внутренний location контроллер создаёт автоматически. К таким ответам также добавляются заголовки из nginx.ingress.kubernetes.io/response-headers-by-status и nginx.ingress.kubernetes.io/ingress-nginx-hsts. Собственные страницы ошибок продолжают работать: если возвращаемый таким образом код, в том числе 500 для остальных кодов, указан в nginx.ingress.kubernetes.io/custom-http-errors или, при отсутствии этой аннотации, в customErrors, клиент получает соответствующую страницу ошибки, но уже без этих заголовков.

Заголовки запроса к бэкенду: nginx.ingress.kubernetes.io/proxy-request-headers

Добавлена в версии модуля v1.3.0.

Было (Critical):

nginx.ingress.kubernetes.io/configuration-snippet: |
  proxy_set_header X-Cluster-Id 00ce4b6f-5031-4483-836e-0a10db23fb21;
  proxy_set_header X-Request-Context /$1/$2;

Стало (Medium):

nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/proxy-request-headers: |
  X-Cluster-Id: 00ce4b6f-5031-4483-836e-0a10db23fb21
  X-Request-Context: /$1/$2

Аннотация принимает заголовки в формате Name: value, по одному на строку; для каждой строки добавляется директива proxy_set_header со значением в кавычках. Значения могут содержать только печатаемые символы ASCII, кроме " и \. Символ $ допускается только в ссылках $1–$9 на захваты регулярного выражения из пути location; другие переменные NGINX использовать нельзя. Захваты требуют use-regex: "true" или nginx.ingress.kubernetes.io/rewrite-target и должны присутствовать в каждом пути Ingress-ресурса. Значения захватов сохраняются сразу при входе запроса в location и не меняются при последующих сопоставлениях с регулярными выражениями (например, в nginx.ingress.kubernetes.io/rewrite-target). Не допускаются заголовки, которые устанавливает сам контроллер: Host, Connection, X-Forwarded-*, X-Original-Forwarded-Host, X-Real-IP, X-Request-ID, заголовки ssl-client-* аннотации nginx.ingress.kubernetes.io/auth-tls-secret, заголовок Accept-Encoding, который очищает nginx.ingress.kubernetes.io/response-html-head-injection, и другие. Не допускаются и заголовки, перечисленные в nginx.ingress.kubernetes.io/auth-response-headers того же Ingress-ресурса, так как их значения передаёт сервис аутентификации. Если заголовок с тем же именем передаёт внешняя аутентификация, заданная параметром global-auth-url в spec.config, заголовок из аннотации не отправляется. Заголовок из additionalHeaders с тем же именем в location-блоках Ingress-ресурса заменяется значением из аннотации.

Заголовки ответа для отдельных кодов: nginx.ingress.kubernetes.io/response-headers-by-status

Добавлена в версии модуля v1.3.0.

Было (Critical):

nginx.ingress.kubernetes.io/configuration-snippet: |
  more_set_headers -s 429 "Retry-After: 60";

Стало (Medium):

nginx.ingress.kubernetes.io/response-headers-by-status: |
  429 Retry-After: 60
  503 Retry-After: 30

Аннотация принимает строки в формате <код> Name: value, по одной на заголовок, с кодом от 200 до 599; для каждой строки добавляется директива more_set_headers -s <код> "Name: value";. Требования к значениям те же, что и в nginx.ingress.kubernetes.io/proxy-request-headers, но символ $ запрещён. Для заголовков, которые нужно добавлять во все ответы, используйте nginx.ingress.kubernetes.io/custom-headers. Заголовки устанавливаются после заголовков nginx.ingress.kubernetes.io/custom-headers и nginx.ingress.kubernetes.io/ingress-nginx-hsts, и для указанных кодов ответа приоритет имеют значения этой аннотации.

Коды ответа при ограничении запросов: nginx.ingress.kubernetes.io/limit-req-status-code, nginx.ingress.kubernetes.io/limit-conn-status-code

Добавлены в версии модуля v1.3.0.

Было (Critical):

nginx.ingress.kubernetes.io/configuration-snippet: |
  limit_req_status 429;
  limit_conn_status 429;

Стало (Low):

nginx.ingress.kubernetes.io/limit-rps: "10"
nginx.ingress.kubernetes.io/limit-req-status-code: "429"
nginx.ingress.kubernetes.io/limit-conn-status-code: "429"

Аннотации задают код ответа на запросы, отклонённые ограничениями Ingress-ресурса: limit-req-status-code — для запросов сверх лимита limit-rps или limit-rpm, limit-conn-status-code — для соединений сверх лимита limit-connections. Допустимы коды от 400 до 599. Если аннотации не заданы, действуют общие для контроллера параметры limit-req-status-code и limit-conn-status-code из spec.config (по умолчанию 503). На nginx.ingress.kubernetes.io/global-rate-limit (только в версии контроллера 1.10) аннотации не влияют: код ответа для неё задаётся параметром global-rate-limit-status-code в spec.config.

Защита Fetch Metadata: nginx.ingress.kubernetes.io/fetch-metadata-protection

Добавлена в версии модуля v1.3.0.

Было (Critical):

nginx.ingress.kubernetes.io/configuration-snippet: |
  set $deny_no_cors "";
  if ($http_sec_fetch_mode = "no-cors") { set $deny_no_cors "N"; }
  if ($http_sec_fetch_site ~ ^(same-site|cross-site)$) { set $deny_no_cors "${deny_no_cors}S"; }
  if ($deny_no_cors = "NS") { return 403; }
  set $deny_cross_site "";
  if ($request_method !~ ^(GET|HEAD|OPTIONS)$) { set $deny_cross_site "M"; }
  if ($http_sec_fetch_site ~ ^(same-site|cross-site)$) { set $deny_cross_site "${deny_cross_site}S"; }
  if ($deny_cross_site = "MS") { return 403; }

Стало (Low):

nginx.ingress.kubernetes.io/fetch-metadata-protection: "true"

Аннотация отклоняет запрос, если значение заголовка Sec-Fetch-Site входит в список сайтов и при этом либо значение Sec-Fetch-Mode входит в список запрещённых режимов, либо метод запроса не входит в список разрешённых. Значения по умолчанию воспроизводят поведение snippet-аннотации: запросы с другого сайта (same-site или cross-site), которые выполняются в режиме no-cors или используют метод, отличный от GET, HEAD и OPTIONS, получают ответ 403, а запросы с того же источника (same-origin) и переходы, инициированные пользователем (Sec-Fetch-Site: none), не затрагиваются. Запросы без заголовка Sec-Fetch-Site не отклоняются.

Политику можно изменить следующими аннотациями. Каждая принимает список значений через запятую, значения не должны повторяться; при отсутствии fetch-metadata-protection: "true" Ingress-ресурс с этими аннотациями отклоняется:

  • nginx.ingress.kubernetes.io/fetch-metadata-sites — значения Sec-Fetch-Site, к которым применяется политика: same-origin, same-site, cross-site, none. По умолчанию — same-site, cross-site.
  • nginx.ingress.kubernetes.io/fetch-metadata-deny-modes — запрещённые значения Sec-Fetch-Mode: cors, navigate, no-cors, same-origin, websocket. По умолчанию — no-cors; пустая строка "" означает, что режим не проверяется.
  • nginx.ingress.kubernetes.io/fetch-metadata-allowed-methods — методы, которые не отклоняются: GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH. По умолчанию — GET, HEAD, OPTIONS; пустая строка "" означает, что отклоняются запросы с любым методом, а * — что метод не проверяется. Сочетание * с пустым значением fetch-metadata-deny-modes недопустимо, так как такая политика ничего не запрещает.
  • nginx.ingress.kubernetes.io/fetch-metadata-status-code — код ответа от 400 до 499. По умолчанию — 403.

Политика, которая отклоняет переходы с других сайтов с любым методом запроса и возвращает код 429:

nginx.ingress.kubernetes.io/fetch-metadata-protection: "true"
nginx.ingress.kubernetes.io/fetch-metadata-sites: "cross-site"
nginx.ingress.kubernetes.io/fetch-metadata-deny-modes: "navigate"
nginx.ingress.kubernetes.io/fetch-metadata-allowed-methods: "*"
nginx.ingress.kubernetes.io/fetch-metadata-status-code: "429"

Политика, которая отклоняет все запросы с других сайтов:

nginx.ingress.kubernetes.io/fetch-metadata-protection: "true"
nginx.ingress.kubernetes.io/fetch-metadata-sites: "cross-site"
nginx.ingress.kubernetes.io/fetch-metadata-allowed-methods: ""

Для каждой уникальной политики контроллер генерирует блоки map один раз и использует их во всех Ingress-ресурсах с этой политикой; в location выполняется только проверка результата: if (...) { return <код>; }. В конфигурацию NGINX попадают только значения из перечисленных выше фиксированных списков.

Проверка выполняется до списков разрешённых адресов, ограничений частоты запросов, аутентификации и перенаправлений permanent-redirect, temporal-redirect и ssl-redirect, и отклонённый запрос получает код политики, даже если на этих этапах ответ был бы иным. На предварительный CORS-запрос (preflight) аннотация nginx.ingress.kubernetes.io/enable-cors отвечает до проверки, и такой запрос не отклоняется.

Перенаправления с сохранением запроса: nginx.ingress.kubernetes.io/redirect-preserve

Добавлена в версии модуля v1.3.0.

Было (Critical):

nginx.ingress.kubernetes.io/configuration-snippet: |
  if ($host = 'old.example.com') {
    rewrite ^ https://new.example.com$request_uri permanent;
  }

Стало — отдельный Ingress-ресурс для old.example.com (Medium — из-за permanent-redirect; уровень самой аннотации — Low):

nginx.ingress.kubernetes.io/permanent-redirect: "https://new.example.com"
nginx.ingress.kubernetes.io/redirect-preserve: "request-uri"

Аннотация дополняет адрес перенаправления из nginx.ingress.kubernetes.io/permanent-redirect или nginx.ingress.kubernetes.io/temporal-redirect частями исходного запроса:

  • request-uri — путь и строку запроса (return 301 https://new.example.com$request_uri;); адрес перенаправления не должен содержать путь, даже /;
  • query — только строку запроса ($is_args$args); адрес перенаправления не должен содержать строку запроса.

Пример перенаправления для точного пути /login с сохранением строки запроса:

# path: /login, pathType: Exact
nginx.ingress.kubernetes.io/permanent-redirect: "https://new.example.com/login"
nginx.ingress.kubernetes.io/redirect-preserve: "query"

Вставка HTML-кода в страницу: nginx.ingress.kubernetes.io/response-html-head-injection

Добавлена в версии модуля v1.3.0.

Было (Critical):

nginx.ingress.kubernetes.io/configuration-snippet: |
  sub_filter "</head>" "<script src=\"https://counter.example.com/tag.js\" async></script></head>";
  sub_filter_once on;
  sub_filter_types text/html;
  proxy_set_header Accept-Encoding "";

Стало (High):

apiVersion: v1
kind: ConfigMap
metadata:
  name: metrics-injection
data:
  content.html: |
    <script src="https://counter.example.com/tag.js" async></script>
---
# Аннотации Ingress
nginx.ingress.kubernetes.io/response-html-head-injection: "metrics-injection"

Аннотация принимает имя ConfigMap из пространства имён Ingress-ресурса или namespace/name ConfigMap из другого пространства имён и вставляет содержимое её ключей в ответы с типом text/html:

  • head-start.html — сразу после первого тега <head>;
  • head-end.html или content.html — перед первым тегом </head>; можно задать только один из этих ключей;
  • body-end.html — перед первым тегом </body>.

Ключи можно задавать одновременно; остальные ключи ConfigMap игнорируются. Пример со счётчиком в разделе <head> и виджетом чата в конце страницы:

apiVersion: v1
kind: ConfigMap
metadata:
  name: page-injection
data:
  head-end.html: |
    <script src="https://counter.example.com/tag.js" async></script>
  body-end.html: |
    <script src="https://chat.example.com/widget.js" async></script>

Теги ищутся без учёта регистра, причём тег <head> распознаётся только без атрибутов. Если нужного тега на странице нет, соответствующий фрагмент не вставляется. Аннотация предназначена для приложений, HTML-код которых нельзя изменить, например для добавления счётчика веб-аналитики.

HTML-код не попадает в конфигурацию NGINX: контроллер сохраняет его в файлы, Lua-модуль контроллера читает их, а директива sub_filter вставляет содержимое через переменную. NGINX может только читать эти файлы, но не изменять их. Изменения в ConfigMap применяются автоматически; файлы, которые не использует ни одна конфигурация, удаляются через час. Суммарный размер всех ключей ограничен 64 КиБ текста в кодировке UTF-8. Для вставки у бэкенда запрашивается ответ без сжатия. Страница отдаётся без изменений, если ConfigMap отсутствует, в ней не задан ни один из ключей, одновременно заданы head-end.html и content.html или превышен допустимый размер. ConfigMap из другого пространства имён используется, только если включён параметр annotationPolicy.allowCrossNamespaceResources; в противном случае контроллер её не читает, и страница также отдаётся без изменений.

Уровень риска аннотации — High: вставленный HTML-код выполняется в контексте источника (origin) приложения со всеми его правами. Если приложение отправляет заголовок Content-Security-Policy, политика должна разрешать вставленный скрипт.