Nginx полностью открывает Cors с базовой аутентификацией без if

Это возникало время от времени здесь и там, но ни один вопрос не охватывает этот вариант использования.

Соответствующий раздел http:

    map $http_origin $origin_with_default {
        default '*';
        ~. $http_origin;
    }
    map $request_method $es_target {
        default '';
        POST 'search';
        GET 'search';
        HEAD 'search';
        OPTIONS 'options';
    }
    root         /app;

Соответствующий раздел server:

server {
  location ~* /(.*)/_search {
    limit_except OPTIONS {
      auth_basic "Read Users";
      auth_basic_user_file /etc/nginx/htpasswd_read;
    }
    rewrite ^ /internal/$es_target;
  }
  location /internal {
    return 405;
  }
  location /internal/search/ {
    internal;
    proxy_pass http://elasticsearch/;
    proxy_http_version 1.1;
    proxy_set_header Connection "Keep-Alive";
    proxy_set_header Proxy-Connection "Keep-Alive";

    proxy_hide_header Access-Control-Allow-Origin;
    proxy_hide_header Access-Control-Allow-Credentials;
    proxy_hide_header Access-Control-Allow-Headers;
    proxy_hide_header Access-Control-Allow-Credentials;

    include "cors.headers";
  }
  location /internal/options {
    internal;
    add_header 'Content-Type' 'text/plain; charset=utf-8';
    add_header 'Content-Length' 0;
    add_header 'Access-Control-Max-Age' 1728000;
    include "cors.headers";
    return 204;
  }
}

Наконец, файл cors.headers:

add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Allow-Origin $origin_with_default always;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always;
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
  1. POST https://example.com/index_name/_search дает 401. Как и ожидалось.
  2. OPTIONS https://example.com/index_name/_search возвращает заголовки опций. Это также ожидаемо.
  3. Однако POST https://u:[email protected]/index_name/_search выдает ошибку 404, а в журнале сервера есть open() "/app/index_name/_search" failed (2: No such file or directory),. До того, как я добавил раздел rewrite ^ /internal/$es_target; и location /internal/search/, когда proxy_pass был сразу после limit_except внутри location ~* /(.*)/_search {, это работало. Из-за 1) и 2) я считаю, что переписывание и сопоставление местоположения работают. Но почему он пытается обслуживать файл вместо прокси-прохода?
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
2
0
112
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вот рабочий конфиг. Его нужно было limit_except удалить, каждое местоположение переключилось на соответствие регулярным выражениям, потребляя все - и последнее местоположение важно, чтобы оно было последним, иначе оно попадет в цикл перезаписи. «Если директива proxy_pass указана с URI, то, когда запрос передается на сервер, часть нормализованного URI запроса, соответствующая местоположению, заменяется URI, указанным в директиве» часть модуля перезаписи не является чем-то Я смог приступить к работе.

Нам все еще нужен раздел http:

    map $http_origin $origin_with_default {
        default '*';
        ~. $http_origin;
    }
    map $request_method $es_target {
        default 'invalid';
        POST 'search';
        GET 'search';
        HEAD 'search';
        OPTIONS 'options';
    }

А потом приходит server

server {
  location ~ /internal/search/(?<search>.*) {
    internal;
    auth_basic "Read Users";
    auth_basic_user_file /etc/nginx/htpasswd_read;
    proxy_pass http://elasticsearch/$search;
    proxy_http_version 1.1;
    proxy_set_header Connection "Keep-Alive";
    proxy_set_header Proxy-Connection "Keep-Alive";

    proxy_hide_header Access-Control-Allow-Origin;
    proxy_hide_header Access-Control-Allow-Credentials;
    proxy_hide_header Access-Control-Allow-Headers;
    proxy_hide_header Access-Control-Allow-Credentials;

    include "cors.headers";
  }
  location ~ /internal/options {
    internal;
    add_header 'Content-Type' 'text/plain; charset=utf-8';
    add_header 'Content-Length' 0;
    add_header 'Access-Control-Max-Age' 1728000;
    include "cors.headers";
    return 204;
  }
  location ~ /internal/invalid {
    return 405;
  }
  location ~* /_search$ {
    rewrite (.*) /internal/$es_target$1;
  }
}
Ответ принят как подходящий

Следующий ответ потребует минимального понимания двух базовых концепций nginx — фаз обработки запросов (описанных в руководство по развитию) и обработчика содержимого местоположения (который может быть как локальным, когда запрос обслуживается с использованием некоторого содержимого локального файла, так и нет, когда ответ поступает от какого-либо восходящего потока — прокси-сервера HTTP, демона FastCGI или uWSGI и т. д.).

Несмотря на то, что у меня довольно большой опыт настройки nginx, limit_except — это не та директива, которую я использовал часто. Чтобы понять его поведение, я провел пару тестов. Вот список директив nginx, которые я собираюсь использовать, и этапы обработки запросов, на которых они регистрируют свои обработчики, в порядке выполнения:

  • rewrite - NGX_HTTP_REWRITE_PHASE
  • auth_basic - NGX_HTTP_ACCESS_PHASE
  • try_files - NGX_HTTP_PRECONTENT_PHASE
  • proxy_pass - NGX_HTTP_CONTENT_PHASE

Из всех вышеперечисленных директив только auth_basic и proxy_pass разрешено использовать внутри блока limit_except. Трюк try_files "" <location>, который я собираюсь использовать, описан в ответе это на ServerFault, поэтому я бы пропустил его подробное описание здесь.

TL;DR Решение будет предоставлено в следующей части ответа; директива limit_except не может быть использована для решения проблемы.


limit_except директивный анализ поведения

Я буду использовать следующую конфигурацию для анализа поведения директивы limit_except:

server {
    listen 8080;
    return 200 "upstream: request URI is \"$request_uri\", request method is $request_method";
}
server {
    listen 80;
    root /var/www/html;
    index index.html;
    ... locations will vary during the tests
}

В каталоге /var/www/html я размещу один файл index.html с одной текстовой строкой index.

Вот так.

location / {
    limit_except GET {
        proxy_pass http://127.0.0.1:8080;
    }
}
> curl http://127.0.0.1/
index
> curl -X POST http://127.0.0.1/
upstream: request URI is "/", request method is POST

Для запроса GET nginx использует обработчик локального контента. Для запроса POST nginx использует обработчик контента http_proxy_module.

location / {
    limit_except GET {}
    proxy_pass http://127.0.0.1:8080;
}
> curl http://127.0.0.1/
upstream: request URI is "/", request method is GET
> curl -X POST http://127.0.0.1/
upstream: request URI is "/", request method is POST

Здесь nginx использует определенный обработчик контента http_proxy_module для обоих запросов. Мы пока не нашли ничего, чего нельзя ожидать. Пойдем дальше.

location / {
    rewrite ^ /internal;
    limit_except GET {}
    proxy_pass http://127.0.0.1:8080;
}
location /internal {
    return 200 internal;
}
> curl http://127.0.0.1/
internal
> curl -X POST http://127.0.0.1/
upstream: request URI is "/", request method is POST

Правила rewrite полностью игнорируются, если запрос подпадает под условие limit_except. Это похоже на то, чего мы не ожидали. Однако быстрый поиск дает нам трассировку nginx билет, ссылающуюся на комментарий следующий:

The problem that rewrite module directives (set, if) are not inherited into the limit_except block, and not executed there. ... This behaviour is basically identical to a nested location block.

Теперь давайте проверим поведение директивы try_files. Для этого мы добавим следующий блок map

map $request_method $loc_name {
    POST    pst;
    default def;
}

и два названных места

location @def { return 200 def; }
location @pst { return 200 pst; }

к нашей конфигурации.

location / {
    try_files "" @$loc_name;
}
> curl http://127.0.0.1/
def
> curl -X POST http://127.0.0.1/
pst

Безусловный переход к указанному местоположению работает должным образом.

location / {
    proxy_pass http://127.0.0.1:8080;
    try_files "" @$loc_name;
}
> curl http://127.0.0.1/
def
> curl -X POST http://127.0.0.1/
pst

Это также ожидаемо, так как NGX_HTTP_PRECONTENT_PHASE, где try_files прикрепляет свой обработчик, выполняется раньше, чем NGX_HTTP_CONTENT_PHASE.

location / {
    limit_except GET {}
    try_files "" @$loc_name;
}
> curl http://127.0.0.1/
def
> curl -X POST http://127.0.0.1/
(HTTP 405 Not Allowed)

Похоже, nginx пытается использовать локальный обработчик контента для запроса POST.

location / {
    limit_except GET {}
    proxy_pass http://127.0.0.1:8080;
    try_files "" @$loc_name;
}
> curl http://127.0.0.1/
def
> curl -X POST http://127.0.0.1/
upstream: request URI is "/", request method is POST

Плохие новости. Обработчик NGX_HTTP_PRECONTENT_PHASE, определенный в основном расположении, не выполняется, если запрос подпадает под условие limit_except. Это похоже на поведение вложенных локаций, хотя, в отличие от вложенных локаций, мы не можем использовать директиву try_files внутри блока limit_except.

Похоже, у директивы limit_except есть какие-то ограниченные варианты использования. Означает ли это, что проблема неразрешима? Нет. Это означает, что директиву limit_except нельзя использовать для ее решения, и нам нужно найти какой-то другой способ. Никогда не сдавайся :)


Решение

При желании вы можете включить/выключить базовую аутентификацию, используя метод, который я только что описал, здесь. Добавьте дополнительный блок map в вашу конфигурацию:

map $es_target $realm {
    search    "Read Users";
    default   off;
}

Теперь вы можете включить условную базовую аутентификацию следующим образом:

location ~ /_search$ {
    auth_basic $realm;
    auth_basic_user_file /etc/nginx/htpasswd_read;
}

Однако вы не можете использовать директиву rewrite в этом блоке, поскольку эта директива выполняется во время NGX_HTTP_REWRITE_PHASE, а директива auth_basic регистрирует свой обработчик позже NGX_HTTP_ACCESS_PHASE. В то время как для обычных директив allow/deny есть способ выполнить все проверки, используя только директивы модуля перезаписи (общий пример — здесь), для базовой аутентификации такого способа нет. К счастью, мы все еще можем использовать наш try_filesобманывать (который будет выполнен позже NGX_HTTP_PRECONTENT_PHASE). Если случайно вы используете пакет OpenResty или модуль lua-nginx, у вас есть дополнительные параметры, описанные в вышеупомянутом ответе.

Я вижу, вы уже столкнулись с проблемами с правильным URI, который должен быть передан в восходящий поток. Ваш оригинальный proxy_pass http://elasticsearch/; будет передавать / для каждого запроса, а proxy_pass http://elasticsearch; будет передавать переписанный URI. Хотя ваш исходный URI запроса всегда доступен через переменную $request_uri (которая не изменяется с помощью директивы rewrite, в отличие от директивы $uri), и что-то вроде proxy_pass http://elasticsearch$request_uri; тоже должно работать, мы будем использовать именованные местоположения (мы не ограничены, но таким образом, мы вообще должны предотвратить любые изменения URI). Вот полное решение (я немного оптимизирую ваш первый блок map, чтобы предотвратить (какой-то дорогой) вызов библиотеки регулярных выражений):

map $http_origin $origin_with_default {
    ''      '*';
    default $http_origin;
}
map $request_method $es_target {
    POST 'search';
    GET 'search';
    HEAD 'search';
    OPTIONS 'options';
    default 'wrong';
}
map $es_target $realm {
    search    "Read Users";
    default   off;
}
location ~ /_search$ {
    auth_basic $realm;
    auth_basic_user_file /etc/nginx/htpasswd_read;
    try_files "" @$es_target;
}
location @search {
    proxy_pass http://elasticsearch;
    proxy_http_version 1.1;
    proxy_set_header Connection "Keep-Alive";
    proxy_set_header Proxy-Connection "Keep-Alive";

    proxy_hide_header Access-Control-Allow-Origin;
    proxy_hide_header Access-Control-Allow-Credentials;
    proxy_hide_header Access-Control-Allow-Headers;
    proxy_hide_header Access-Control-Allow-Credentials;

    include "cors.headers";
}
location @options {
    add_header 'Content-Type' 'text/plain; charset=utf-8';
    add_header 'Content-Length' 0;
    add_header 'Access-Control-Max-Age' 1728000;
    include "cors.headers";
    return 204;
}
location @wrong {
    return 405;
}

Я пытался, чтобы узнать, принимает ли try_files переменную или нет, но мне это совершенно не удалось. Это фантастика, я проверю, и если это сработает, я перейду к этому.

chx 11.05.2022 08:37

Другие вопросы по теме