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

Читайте так же:

  • Windows 11 теперь может устанавливать WSL из Windows StoreWindows 11 теперь может устанавливать WSL из Windows Store Microsoft выпустила сборку Windows 11 22581 для разработчиков. В обновлении добавили возможность установить Windows Subsystem for Linux (WSL) в качестве приложения из Windows Store. Кроме этого, обновили коллекцию фонов Spotlight, работу виджетов и улучшили функции для людей с […]
  • Google раскроет для депутатов Госдумы внутренние правила YouTube, по которым удаляла аккаунты RTGoogle раскроет для депутатов Госдумы внутренние правила YouTube, по которым удаляла аккаунты RT Депутат государственной думы Виталий Пискарев рассказал, что Google согласилась раскрыть парламентариям внутренние правила, которые стали основанием для блокировки двух немецкоязычных youtube-каналов медиа-холдинга RT. Законодатель признался, что компания сделала это после длительных […]
  • Бесплатный вебинар «3 взгляда на будущее маркетинг-аналитики от ведущих игроков рынка»Бесплатный вебинар «3 взгляда на будущее маркетинг-аналитики от ведущих игроков рынка» 20 января компания OWOX проведет бесплатный вебинар о трендах маркетинг-аналитики в 2022 году. СЕО ведущих сервисов маркетинг-аналитики – OWOX, Renta и Tomi.ai – поделятся своим взглядом на развитие отрасли. Основные акценты – на качестве данных, переходу к post-cookie и […]
  • Ужас, летящий на крыльях ночиУжас, летящий на крыльях ночи Пчела-плотник, также известная как Ксилокопа, Большая древесная пчела, пчела-древоточец, черная пчела или древогнезд (Xylocopa) – род одиночных пчел семейства Настоящих пчел (Apidae), широко распространенных на нескольких континентах. От обычных медоносных пчел они отличаются темной […]