Настройка перехвата заголовков HAProxy CORS OPTIONS

С моей настройкой NGinx я смог перехватить запросы OPTIONS из предполетного билета ajax и ответить правильными заголовками CORS и ответом 200, чтобы запрос мог продолжить. Я пытаюсь объединить мои сторонние прокси в HAProxy, и у меня есть некоторые проблемы, связанные с тем, что эта часть головоломки работает.

Моя особая проблема заключается в том, что, хотя я могу добавить правильные опции CORS, когда есть сервер, способный правильно реагировать на запрос OPTIONS, некоторые из бэкэнд не могут обрабатывать/отвечать с ошибкой 405, когда запрос предполетной выпущен. В моем haproxy.cfg были добавлены следующие строки для добавления заголовков:

capture request header origin len 128
http-response add-header Access-Control-Allow-Origin %[capture.req.hdr(0)] if { capture.req.hdr(0) -m found }
rspadd Access-Control-Allow-Credentials:\ true if { capture.req.hdr(0) -m found }
rspadd Access-Control-Allow-Headers:\ Origin,\ X-Requested-With,\ Content-Type,\ Origin,\ User-Agent,\ If-Modified-Since,\ Cache-Control,\ Accept if { capture.req.hdr(0) -m found }
rspadd Access-Control-Allow-Methods:\ GET,\ POST,\ PUT,\ DELETE,\ OPTIONS if { capture.req.hdr(0) -m found }
rspadd Access-Control-Max-Age:\ 1728000 if { capture.req.hdr(0) -m found }

Решение, заданное в:

Как отправить ответ с помощью HAProxy без передачи запроса на веб-серверы работает, когда вы устанавливаете все правильные заголовки из клиентского запроса, но не динамичны, не является идеальным решением.

Любая помощь будет оценена!

Ответ 1

Вы можете использовать Lua, но вам нужно убедиться, что HAproxy построен с помощью USE_LUA, проверив haproxy -vv.

Это пример конфигурации, я сам не пробовал, но это даст вам представление о том, что вы можете сделать:

# haproxy.cfg

global
    lua-load cors.lua

frontend foo
    ...
    http-request use-service lua.cors-response if METH_OPTIONS { req.hdr(origin) -m found } { ... }

# cors.lua
core.register_service("cors-response", "http", function(applet)
    applet:set_status(200)
    applet:add_header("Content-Length", "0")
    applet:add_header("Access-Control-Allow-Origin", applet.headers["origin"][0])
    applet:add_header("Access-Control-Allow-Credentials", "true")
    applet:add_header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Origin, User-Agent, If-Modified-Since, Cache-Control, Accept")
    applet:add_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
    applet:add_header("Access-Control-Max-Age", "1728000")
    applet:start_response()
end)

Ответ 2

На основе отличного ответа от anine.io Я придумал следующее решение, которое позволяет определить список разрешенных источников, а также добавляет отсутствующий Acccess-Control-Allow-Origin Заголовок для всех HTTP-запросов. Ответ от anine.io показал только предварительный предлог CORS, но не рассматривал обычные запросы.

В haproxy.cfg загрузите файл cors.lua (при необходимости измените путь) в глобальном разделе

global
    lua-load /usr/local/etc/haproxy/cors.lua

Добавьте конфигурацию CORS в определение интерфейса.

frontend http-in
    # CORS configuration
    # capture origin HTTP header
    capture request header origin len 128
    # add Access-Control-Allow-Origin HTTP header to response if origin matches the list of allowed URLs
    http-response add-header Access-Control-Allow-Origin %[capture.req.hdr(0)] if !METH_OPTIONS { capture.req.hdr(0) -m reg -f /usr/local/etc/haproxy/cors-origins.lst }
    # if a preflight request is made, use CORS preflight backend
    http-request use-service lua.cors-response if METH_OPTIONS { capture.req.hdr(0) -m reg -f /usr/local/etc/haproxy/cors-origins.lst }

Создайте файл с именем cors.lua и сохраните его по указанному выше пути. Файл содержит предварительную проверку CORS и, если нет веских причин, не ограничивайте методы или заголовки, потому что вам нужно будет включать любые ограничения в отношении методов или заголовков в списках ACL, определенных в конфигурации CORS, в haproxy.conf. Примечание. В настоящее время браузеры не поддерживают подстановочный знак * для заголовка Access-Control-Allow-Methods. Файл cors.lua должен содержать следующий контент

core.register_service("cors-response", "http", function(applet)
    applet:set_status(200)
    applet:add_header("Content-Length", "0")
    applet:add_header("Access-Control-Allow-Origin", applet.headers["origin"][0])
    applet:add_header("Access-Control-Allow-Credentials", "true")
    applet:add_header("Access-Control-Allow-Headers", "*")
    applet:add_header("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, PATCH, OPTIONS")
    applet:add_header("Access-Control-Max-Age", "1728000")
    applet:start_response()
end)

Создайте файл с именем cors-origins.lst и сохраните его по указанному выше пути в конфигурации CORS. Файл должен содержать регулярные выражения (или просто простые строки). Если клиент отправляет заголовок Origin, он будет проверяться на соответствие этим регулярным выражениям, и только если они совпадут, будет возвращен предварительный предлог CORS из cors.lua (для HTTP OPTIONS запросов) или Access-Control-Allow-Origin со значением начала заголовок запроса клиента будет добавлен в ответ. Примером содержимого cors-origins.lst может быть

example.com
localhost.*
.*\.mydomain\.com:[8080|8443]

Проверьте конфигурацию с помощью http://test-cors.org/. Для запросов GET не должно быть предвыборки CORS. Для запросов, отличных от GET, запрос предварительной проверки CORS должен выполняться клиентом сначала (например, вызов HTTP OPTIONS), чтобы проверить, разрешен ли предполагаемый метод, заголовки и авторизация.

Подробнее о CORS см. Контроль доступа HTTP (CORS).

Ответ 3

Я хочу бросить свой собственный ответ на ринге здесь. Мы получили немалую боль, чтобы добраться до рабочей установки. Другие ответы на этот вопрос здесь были очень полезны, но не дали нам полной рабочей конфигурации.

Мы хотели разрешить любое происхождение. Если вам нужно белое происхождение, см. ответ от @Florian Feldhaus для полезного трюка регулярного выражения. Вместо использования белого списка мы возвращаем заголовок местоположения:

http-request set-header Access-Control-Allow-Origin %[capture.req.hdr(0)] if { capture.req.hdr(0) -m found }

Нам также необходимо было явно установить Access-Control-Allow-Headers и Access-Control-Expose-Headers. Поддержка браузера для подстановочных знаков в заголовках тезисов еще не совсем там.

Итак, вот что делает наша конфигурация:

  • обрабатывает предполетные запросы, используя this cors.lua script
  • обрабатывает обычный запрос, используя http-response set-header, чтобы добавить заголовки Access-Control-Allow- *
  • отрегулируйте tune.maxrewrite, чтобы соответствовать нашим заголовкам CORS (которые составляют > 1 КБ)

Шаги для 1) и 2) объясняются в других ответах здесь, но шаг 3) заставил нас долго размышлять. Я документировал полную конфигурацию и путешествие, которое привело нас туда в этот пост в блоге. Сообщение содержит ссылки на суть на github.