cors-anywhere на чистом конфиге nginx

от автора

Если вы сталкивались с CORS, то знаете всю ту боль, которую испытывает разработчик, когда нужно сходить к API на другом домене. Если конфигурация сервера не доступна для настройки, то использовали какое-нибудь решение на основе не менее популярного решения cors-anywhere.

Пятница вечер делать нечего

Не многим изестно, что директива proxy_pass поддерживает не только локальные домены и потоки (aka upstream), но и внешние источники, например:

proxy_pass https://api.github.com/$request_uri

Так зародилась идея написать универсальный (с некоторыми оговорками) конфиг для nginx, который поддерживает любой переданный домен.

Чем мы можем управлять

Мы можем объявлять новые переменные на основе глобальных c поддержой регулярных выражений с помощью map:

map $request_url $my_request_path {   ~*/(.*)$ $1;   default  ""; }

Так, при запросе к http://example.com/api в переменной $my_request_path будет лежать api.

Мы можем отправлять клиенту дополнительные заголовки с помощью add_header:

add_header X-Request-Path $my_request_path always;

Теперь у нас добавился заголовок X-Request-Path с значением api.

С помощью директивы proxy_set_header добавлять заголовки к запросу, который отправляется proxy_pass. А с помощью proxy_hide_header скрывать заголвки, которые мы получили от proxy_pass.

С помощью директивы if обрабатывать выражения, например, при запросе методом OPTIONS отдавать сразу нужный код ответа:

if ($request_method = OPTIONS) {   return 204; }

Собираем все вместе

Для начала объявим $proxy_uri который мы будем извлекать из $request_uri:

map $request_uri $proxy_uri {   ~*/http://(.*)/(.+)$  "http://$1/$2";   ~*/https://(.*)/(.+)$ "https://$1/$2";   ~*/http://(.*)$       "http://$1/";   ~*/https://(.*)$      "https://$1/";   ~*/(.*)/(.+)$         "https://$1/$2";   ~*/(.*)$              "https://$1/";   default               ""; }

Если коротко это работает так: при запросе http://example.com/example.ru, в переменной $proxy_uri будет лежать https://example.ru

Из полученного $proxy_uri извлечем часть, которая будет соответствовать заголовку Origin:

map $proxy_uri $proxy_origin {   ~*(.*)/.*$ $1;   default    ""; }

Для заголовка Forwarded нам понадобится обработать сразу 2 переменные:

map $remote_addr $proxy_forwarded_addr {   ~^[0-9.]+$        "for=$remote_addr";   ~^[0-9A-Fa-f:.]+$ "for=\"[$remote_addr]\"";   default           "for=unknown"; }  map $http_forwarded $proxy_add_forwarded {   ""      "$proxy_forwarded_addr";   default "$http_forwarded, $proxy_forwarded_addr"; }

Обработка заголовока X-Forwarded-For уже встроена в nginx

Теперь мы можем перейти к объявлению нашего проксирующего сервера:

server {   listen 443 ssl;       server_name cors.example.com;      proxy_http_version         1.1;   proxy_pass_request_headers on;   proxy_pass_request_body    on;      proxy_redirect             off;   resolver                   77.88.8.8 77.88.8.1 8.8.8.8 8.8.4.4 valid=1d;      location / {     if ($proxy_uri = "") {       # empty uri       return 403;     }          # add proxy cors headers     add_header Access-Control-Allow-Headers "*" always;     add_header Access-Control-Allow-Methods "*" always;     add_header Access-Control-Allow-Origin  "*" always;      if ($request_method = OPTIONS) {       return 204;     }          proxy_set_header Host                $proxy_host;     proxy_set_header Origin              $proxy_origin;     proxy_set_header X-Forwarded-For     $proxy_add_x_forwarded_for;     proxy_set_header X-Forwarded-Proto   $scheme;     proxy_set_header Forwarded           "$proxy_add_forwarded;proto=$scheme";          proxy_pass $proxy_uri;   } }

Мы получили минимально рабочий проксирующий сервер, у которого обрабатывается CORS Preflight Request и добавляются соответствующие заголовки.

Делаем красиво

Все бы хорошо, но если у сервера, к которому мы проксируем, будет настроена обработка CORS, то его заголовки будут передаваться клиенту. Давайте скроем все возможные:

# hide original cors proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; proxy_hide_header Access-Control-Request-Headers; proxy_hide_header Access-Control-Request-Method;

Хорошо бы еще передавать IP клиента, чтобы хоть как-то обходить rate limit, который может возникнуть, если несколько пользователей будут обращаться к одному ресурсу:

proxy_set_header X-Real-IP           $remote_addr; proxy_set_header X-Client-IP         $remote_addr; proxy_set_header CF-Connecting-IP    $remote_addr; proxy_set_header Fastly-Client-IP    $remote_addr; proxy_set_header True-Client-IP      $remote_addr; proxy_set_header X-Cluster-Client-IP $remote_addr;

Мы же не говорим про анонимность, верно?)

И, напоследок, немного улучшим производительность выключив кэш/буферизацию/etc:

sendfile                   on; tcp_nodelay                on; tcp_nopush                 on;  etag                       off; if_modified_since          off;  proxy_buffering            off; proxy_cache                off; proxy_cache_convert_head   off; proxy_max_temp_file_size   0; client_max_body_size       0;  proxy_read_timeout         1m; proxy_connect_timeout      1m; reset_timedout_connection  on;  gzip                       off; gzip_proxied               off; # brotli                   off;
Конфиг полностью
map $request_uri $proxy_uri {   ~*/http://(.*)/(.+)$  "http://$1/$2";   ~*/https://(.*)/(.+)$ "https://$1/$2";   ~*/http://(.*)$       "http://$1/";   ~*/https://(.*)$      "https://$1/";   ~*/(.*)/(.+)$         "https://$1/$2";   ~*/(.*)$              "https://$1/";   default               ""; }  map $proxy_uri $proxy_origin {   ~*(.*)/.*$ $1;   default    ""; }  map $remote_addr $proxy_forwarded_addr {   ~^[0-9.]+$        "for=$remote_addr";   ~^[0-9A-Fa-f:.]+$ "for=\"[$remote_addr]\"";   default           "for=unknown"; }  map $http_forwarded $proxy_add_forwarded {   ""      "$proxy_forwarded_addr";   default "$http_forwarded, $proxy_forwarded_addr"; }  server {   listen 443 ssl;      ssl_certificate /etc/letsencrypt/live/cors.example.com/fullchain.pem;   ssl_certificate_key /etc/letsencrypt/live/cors.example.com/privkey.pem;   ssl_trusted_certificate /etc/letsencrypt/live/cors.example.com/chain.pem;      server_name cors.example.com;      sendfile                   on;   tcp_nodelay                on;   tcp_nopush                 on;      etag                       off;   if_modified_since          off;      proxy_buffering            off;   proxy_cache                off;   proxy_cache_convert_head   off;   proxy_max_temp_file_size   0;   client_max_body_size       0;      proxy_http_version         1.1;   proxy_pass_request_headers on;   proxy_pass_request_body    on;      proxy_read_timeout         1m;   proxy_connect_timeout      1m;   reset_timedout_connection  on;      proxy_redirect             off;   resolver                   77.88.8.8 77.88.8.1 8.8.8.8 8.8.4.4 valid=1d;      gzip                       off;   gzip_proxied               off;   # brotli                   off;      location / {     if ($proxy_uri = "") {       return 403;     }          # add proxy cors     add_header Access-Control-Allow-Headers "*" always;     add_header Access-Control-Allow-Methods "*" always;     add_header Access-Control-Allow-Origin  "*" always;      if ($request_method = "OPTIONS") {       return 204;     }          # pass client to proxy     proxy_set_header Host                $proxy_host;     proxy_set_header Origin              $proxy_origin;     proxy_set_header X-Real-IP           $remote_addr;     proxy_set_header X-Client-IP         $remote_addr;     proxy_set_header CF-Connecting-IP    $remote_addr;     proxy_set_header Fastly-Client-IP    $remote_addr;     proxy_set_header True-Client-IP      $remote_addr;     proxy_set_header X-Cluster-Client-IP $remote_addr;     proxy_set_header X-Forwarded-For     $proxy_add_x_forwarded_for;     proxy_set_header X-Forwarded-Proto   $scheme;     proxy_set_header Forwarded           "$proxy_add_forwarded;proto=$scheme";          # hide original cors     proxy_hide_header Access-Control-Allow-Credentials;     proxy_hide_header Access-Control-Allow-Headers;     proxy_hide_header Access-Control-Allow-Methods;     proxy_hide_header Access-Control-Allow-Origin;     proxy_hide_header Access-Control-Expose-Headers;     proxy_hide_header Access-Control-Max-Age;     proxy_hide_header Access-Control-Request-Headers;     proxy_hide_header Access-Control-Request-Method;          proxy_pass $proxy_uri;   } }


ссылка на оригинал статьи https://habr.com/ru/post/651253/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *