HAProxy в качестве обратного прокси с разгрузкой SSL для двух приложений на порту 443

Вопрос или проблема

Я испытываю некоторые проблемы с HAProxy, работающим в качестве обратного прокси и перенаправляющим трафик на два разных приложения на порту 443. Приложение 2 работает нормально с конфигурацией ниже. Приложение 1 вызывает некоторые незначительные проблемы. При вводе в браузере адреса домена: “domain.com/application1” я получаю ошибку Time Out, и адрес в браузере меняется на domain.com:82/application1, где 82 — это желаемый порт бэкенда, на котором слушает приложение 1. Теперь это становится довольно странным для меня. Удаление “:82” из “domain.com:82/application1” позволяет мне получить доступ к приложению 1 в веб-браузере. Я предполагаю, что я ошибся в какой-то строке команды перенаправления, прокси-протокола или SSL-выгрузки, но не вижу, где именно. Одним из ограничений является то, что мне нужно вести учет исходящего IP-адреса. Ниже моя конфигурация HAProxy и Nginx:

HAProxy:

global
        log /dev/log    local0
        log /dev/log    local1 notice
        chroot /var/lib/haproxy
        stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
        stats timeout 30s
        user haproxy
        group haproxy
        daemon
        ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
        ssl-default-bind-options no-sslv3
        tune.ssl.default-dh-param 2048

defaults
        log     global
        mode    tcp

        option  tcplog
        option  dontlognull
        option forwardfor header X-Forwarded-For

        timeout connect 5000
        timeout client  50000
        timeout server  50000

        errorfile 400 /etc/haproxy/errors/400.http
        errorfile 403 /etc/haproxy/errors/403.http
        errorfile 408 /etc/haproxy/errors/408.http
        errorfile 500 /etc/haproxy/errors/500.http
        errorfile 502 /etc/haproxy/errors/502.http
        errorfile 503 /etc/haproxy/errors/503.http
        errorfile 504 /etc/haproxy/errors/504.http

frontend http-in
        bind :80
        mode http

        option forwardfor header X-Forwarded-For
        option httplog

        acl is_letsencrypt path_beg -i /.well-known/acme-challenge/

        redirect scheme https code 301 if !{ ssl_fc } !is_letsencrypt
        use_backend be_letsencrypt if is_letsencrypt

frontend https_in
        bind *:443
        option http-server-close

        acl tls req.ssl_hello_type 1
        acl is_application1 req.ssl_sni -i domain.com

        tcp-request inspect-delay 5s
        tcp-request content accept if tls

        use_backend recir_application1 if is_application1
        use_backend recir_application2 if !{ req.ssl_hello_type 1 } !{ req.len 0 }

backend be_letsencrypt
        mode http
        server localhost 127.0.0.1:81

backend recir_application1
        server loopback-for-tls abns@haproxy-application1 send-proxy

backend recir_application2
        server loopback-for-tls abns@haproxy-default send-proxy

frontend ssl-default
        bind abns@haproxy-default accept-proxy
        use_backend be_application2

frontend application1
        mode http
        bind abns@haproxy-application1 accept-proxy ssl crt /etc/letsencrypt/domain.com/haproxy.pem
        use_backend be_application1

backend be_application2
        server localhost 127.0.0.1:4545

backend be_application1
       mode http
       server localhost 127.0.0.1:82 send-proxy

и Nginx:

upstream php-handler {
    server unix:/run/php/php8.3-fpm.sock;
}

server {
    listen 127.0.0.1:82 proxy_protocol;
    listen [::]:82 proxy_protocol;
    server_name localhost mydomain.com;

# Настройки HSTS
    # ВНИМАНИЕ: добавляйте опцию preload только после того, как ознакомитесь с
    # последствиями на https://hstspreload.org/. Эта опция
    # добавит домен в жестко закодированный список, который распространен
    # во всех основных браузерах, и удаление из этого списка
    # может занять несколько месяцев.
    # add_header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;" всегда;

    # Путь к корню вашей установки
    root /var/www/;

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    # Следующие 2 правила нужны только для приложения user_webfinger.
    # Раскомментируйте, если планируете использовать это приложение.
    # rewrite ^/.well-known/host-meta /webapplication1/public.php?service=host-meta last;
    # rewrite ^/.well-known/host-meta.json /webapplication1/public.php?service=host-meta-json last;

    location ^~ /.well-known {
        # Правила в этом блоке - это адаптация правил
        # в `.htaccess`, касающихся `/.well-known`.

        location = /.well-known/carddav { return 301 /remote.php/dav/; }
        location = /.well-known/caldav  { return 301 /remote.php/dav/; }

        location /.well-known/acme-challenge    { try_files $uri $uri/ =404; }
        location /.well-known/pki-validation    { try_files $uri $uri/ =404; }

        # Пусть API webapplication1 для `/.well-known` обрабатывает все остальные
        # запросы, передавая их контроллеру переднего плана.
        return 301 /index.php$request_uri;
    }
    location ^~ /webapplication1 {
        # укажите максимальный размер загрузки и увеличьте тайм-аут загрузки:
        client_max_body_size 512M;
        client_body_timeout 300s;
        fastcgi_buffers 64 4K;

        # Включите gzip, но не удаляйте заголовки ETag
        gzip on;
        gzip_vary on;
        gzip_comp_level 4;
        gzip_min_length 256;
        gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
        gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml;

        # Pagespeed не поддерживается webapplication1, поэтому если ваш сервер собран
        # с модулем `ngx_pagespeed`, раскомментируйте эту строку, чтобы отключить его.
        #pagespeed off;

        # Настройки позволяют вам оптимизировать ширину канала HTTP2.
        # См. https://blog.cloudflare.com/delivering-http-2-upload-speed-improvements/
        # для рекомендаций по настройкам
        client_body_buffer_size 512k;

        # HTTP-заголовки ответа заимствованы из `.htaccess` webapplication1
        add_header Referrer-Policy                   "no-referrer"       всегда;
        add_header X-Content-Type-Options            "nosniff"           всегда;
        add_header X-Download-Options                "noopen"            всегда;
        add_header X-Frame-Options                   "SAMEORIGIN"        всегда;
        add_header X-Permitted-Cross-Domain-Policies "none"              всегда;
        add_header X-Robots-Tag                      "noindex, nofollow" всегда;
        add_header X-XSS-Protection                  "1; mode=block"     всегда;

        # Удалите X-Powered-By, что является утечкой информации
        fastcgi_hide_header X-Powered-By;

        # Установите MIME-типы для .mjs и .wasm
        # Либо добавьте его в список mime.types по умолчанию
        # и явно включите этот список, либо добавьте расширение файла
        # только для webapplication1, как ниже:
        include mime.types;
        types {
        text/javascript js mjs;
        application/wasm wasm;
        }

        # Укажите, как обрабатывать директории -- указание `/webapplication1/index.php$request_uri`
        # здесь как запасного варианта означает, что Nginx всегда будет вести себя,
        # как требуется, когда клиент запрашивает путь, который соответствует директории, существующей
        # на сервере. В частности, если в этой директории есть файл index.php,
        # этот файл будет правильно обслужен; если его нет, запрос передается
        # контроллеру переднего плана. Это последовательное поведение означает, что нам не нужно
        # указывать специальные правила для определенных путей (например, для изображений и других ресурсов,
        # `/updater`, `/ocm-provider`, `/ocs-provider`), и поэтому
        # `try_files $uri $uri/ /webapplication1/index.php$request_uri`
        # всегда обеспечивает требуемое поведение.
        index index.php index.html /webapplication1/index.php$request_uri;

        # Правило, заимствованное из `.htaccess`, для обработки клиентов Microsoft DAV
        location = /webapplication1 {
            if ( $http_user_agent ~ ^DavClnt ) {
                return 302 /webapplication1/remote.php/webdav/$is_args$args;
            }
        }

        # Правила, заимствованные из `.htaccess`, чтобы скрыть определенные пути от клиентов
        location ~ ^/webapplication1/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)    { return 404; }
        location ~ ^/webapplication1/(?:\.|autotest|occ|issue|indie|db_|console)                  { return 404; }

        # Убедитесь, что этот блок, который передает файлы PHP на процесс PHP, находится выше блоков
        # , которые обрабатывают статические ресурсы (как видно ниже). Если этот блок не объявлен первым,
        # Nginx столкнется с бесконечным циклом переписывания, когда он добавляет
        # `/webapplication1/index.php` к URI, что приведет к коду ответа HTTP 500.
        location ~ \.php(?:$|/) {
            # Требуется для поддержки устаревших версий
            rewrite ^/webapplication1/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+|.+\/richdocume);

            fastcgi_split_path_info ^(.+?\.php)(/.*)$;
            set $path_info $fastcgi_path_info;

            try_files $fastcgi_script_name =404;

            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $path_info;
            fastcgi_param HTTPS on;

            fastcgi_param modHeadersAvailable true;         # Избегайте двойной отправки заголовков безопасности
            fastcgi_param front_controller_active true;     # Включите красивые URL
            fastcgi_pass php-handler;

            fastcgi_intercept_errors on;
            fastcgi_request_buffering off;

            fastcgi_max_temp_file_size 0;
        }

        location ~ \.(?:css|js|svg|gif|png|jpg|ico|wasm|tflite|map)$ {
            try_files $uri /webapplication1/index.php$uri$is_args$args;
            proxy_set_header Cache-Control "public, max-age=7200";
            # Добавьте заголовки для предоставления заголовков безопасности
            # Снова используйте 'proxy_set_header' (не 'add_header'), поскольку заголовки должны проходить через прокси.
            proxy_set_header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;";
            proxy_set_header X-Content-Type-Options nosniff;
            proxy_set_header X-Frame-Options "SAMEORIGIN";
            proxy_set_header X-XSS-Protection "1; mode=block";
            proxy_set_header X-Robots-Tag none;
            proxy_set_header X-Download-Options noopen;
            proxy_set_header X-Permitted-Cross-Domain-Policies none;

            # По желанию: не регистрировать доступ к ресурсам
            access_log off;

            location ~ \.wasm$ {
                default_type application/wasm;
            }
        }

        location ~ \.woff2?$ {
            try_files $uri /webapplication1/index.php$request_uri;
            expires 7d;         # Политика Cache-Control заимствована из `.htaccess`
            access_log off;     # По желанию: не регистрировать доступ к ресурсам
        }

        # Правило, заимствованное из `.htaccess`
        location /webapplication1/remote {
            return 301 /webapplication1/remote.php$request_uri;
        }

        location /webapplication1 {
            try_files $uri $uri/ /webapplication1/index.php$request_uri;
        }
    }
}

Спасибо за вашу помощь.
Кришан

Ответ или решение

Для решения проблемы с конфигурацией HAProxy, которая будет работать в качестве обратного прокси с SSL-офладингом для двух приложений на порту 443, важно корректно настроить маршрутизацию трафика, а также убедиться, что все настройки правильно обрабатывают исходящие HTTP заголовки. Ниже представлена возможная конфигурация для исправления проблемы.

HAProxy Конфигурация

global
    log /dev/log    local0
    log /dev/log    local1 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
    ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
    ssl-default-bind-options no-sslv3
    tune.ssl.default-dh-param 2048

defaults
    log     global
    mode    http
    option  httplog
    option  forwardfor header X-Forwarded-For
    timeout connect 5000ms
    timeout client  50000ms
    timeout server  50000ms

frontend http-in
    bind *:80
    acl is_letsencrypt path_beg -i /.well-known/acme-challenge/
    redirect scheme https code 301 if !{ ssl_fc } !is_letsencrypt
    use_backend be_letsencrypt if is_letsencrypt

frontend https-in
    bind *:443 ssl crt /etc/letsencrypt/domain.com/haproxy.pem
    option http-server-close

    acl is_application1 hdr(host) -i domain.com
    acl is_application2 hdr(host) -i application2.domain.com

    use_backend be_application1 if is_application1
    use_backend be_application2 if is_application2

backend be_letsencrypt
    mode http
    server letsencrypt 127.0.0.1:81

backend be_application1
    mode http
    server application1 127.0.0.1:82 send-proxy

backend be_application2
    mode http
    server application2 127.0.0.1:4545 send-proxy

Nginx Конфигурация

Убедитесь, что конфигурация Nginx для первого приложения, слушающего на порту 82, настроена на поддержку proxy_protocol, так как HAProxy отправляет IP-адреса клиента.

upstream php-handler {
    server unix:/run/php/php8.3-fpm.sock;
}

server {
    listen 127.0.0.1:82 proxy_protocol;
    listen [::]:82 proxy_protocol;
    server_name localhost domain.com;

    # Root и другие настройки
    root /var/www/;

    location ~ ^/application1 {
        try_files $uri /application1/index.php?$args; 
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass php-handler;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param HTTPS on;
        fastcgi_param modHeadersAvailable true;
        fastcgi_param front_controller_active true; 
    }
}

Объяснение внесенных изменений

  • Настройки ACL: Мы определили два отдельных ACL для каждого приложения, используя заголовок Host. Это позволяет HAProxy правильно маршрутизировать запросы на соответствующий бэкенд.

  • Параметр send-proxy: Используется для передачи IP-адресов клиента от HAProxy к Nginx. Убедитесь, что сервер Nginx настроен для поддержки proxy_protocol.

  • Использование try_files в Nginx: Это гарантирует правильную обработку запросов, перенаправляя их на index.php приложения, если файл не найден.

Заключение

С этими изменениями ваша установка HAProxy и Nginx должна работать корректно. Вы сможете получить доступ к обоим приложениям через HTTPS, и параметры send-proxy и proxy_protocol обеспечат правильную передачу исходящего IP-адреса клиента. Обязательно протестируйте доступ к каждому приложению, чтобы убедиться, что конфигурация выполнена правильно.

Оцените материал
Добавить комментарий

Капча загружается...