- PVSM.RU - https://www.pvsm.ru -
Очень хотелось назвать статью «Proxy-сервис на Go в 3 строчки», но я выше этого.
В действительности так и есть, основную логику можно уместить в трёх строках. Для нетерпеливых и тех, кто хочет увидеть самую суть:
proxy := httputil.NewSingleHostReverseProxy(url)
r.Header.Set(header, value)
proxy.ServeHTTP(w, r)
Под катом более подробный рассказ для новичков в языке Golang и тех, кому нужно создать обратный прокси в кратчайшие сроки.
Разберём, для чего нужен прокси-сервис, как его реализовать и что под капотом у стандартной библиотеки.
Обратный прокси [1] (reverse proxy) — это тип прокси-сервера, который получает запрос от клиента, перенаправляет его на один или несколько серверов и пересылает ответ обратно.
Отличительная черта обратного прокси в том, что он является входной точкой для соединения пользователя с серверами, с которыми сам прокси связан бизнес-логикой. Он определяет, на какие серверы будет передан запрос клиента. Логика построения сети за прокси остается скрыта от пользователя.
Обратный прокси (Reverse Proxy)
Для сравнения, обычный прокси связывает своих клиентов с любым сервером, который им необходим. В этом случае прокси находится перед пользователем и является просто посредником в выполнении запроса.
Обычный прокси (Forward Proxy)
Для чего использовать
Концепцию обратного прокси можно применять в различных ситуациях:
— балансировка нагрузки,
— A/B-тестирование,
— кэширование ресурсов,
— сжатие данных запроса,
— фильтрация трафика,
— авторизация.
Конечно, область применения не ограничивается этими шестью пунктами. Сам факт возможности обработки запроса как до, так и после проксирования даёт большой простор для творчества. В этой статье разберём использование обратного прокси для авторизации.
Мы разрабатываем панель управления виртуализацией VMmanager 6. В один прекрасный день мы решили дать пользователям бóльшую свободу в мониторинге и визуализации данных кластеров. Для этих целей выбрали Grafana [2].
Чтобы Grafana заработала с нашими данными, надо было настроить авторизацию. Сделать это несложно, если бы не три «но».
Чтобы соблюсти условия, решили поместить Grafana во внутреннюю сеть и написать обратный прокси. Он будет проверять права в сервисе авторизации и только после этого передавать запрос в Grafana.
Основная идея — переложить ответственность за авторизацию в Grafana на сервер обратного proxy (официальная документация [3]). Grafana будет принимать любой запрос как авторизованный, если он содержит определённый заголовок. Перед тем, как подставить этот заголовок, мы должны убедиться в правах текущего пользователя на работу с Grafana.
Цепочка вызовов «Grafana-proxy, или Туда и обратно»
Функция main довольно стандартна. Мы стартуем http-сервер, который будет принимать подключения на 4000 порту и обрабатывать любой адрес “/”, с которым произойдет подключение.
func main() {
http.HandleFunc("/", handlerProxy)
if err := http.ListenAndServe(":4000", nil); err != nil {
panic(err)
}
}
Основная часть работы происходит в обработчике запросов.
func handlerProxy(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.URL.Host)
if strings.HasPrefix(r.URL.String(), "/api") {
//Проверка прав в сервисе авторизации
}
url, err := url.Parse(fmt.Sprintf("http://%s/", grafanaHost))
if err != nil {
SendJSONError(w, err.Error())
return
}
proxy := httputil.NewSingleHostReverseProxy(url)
fmt.Println(r.URL.Host)
r.Header.Set(grafanaHeader, grafanaUser)
proxy.ServeHTTP(w, r)
}
Пройдемся по параметрам. Основные переменные в примере я вынес в константы:
grafanaUser = "admin" //Пользователь, под которым мы будем авторизовываться
grafanaHost = "grafana:3000" //Адрес расположения grafana
grafanaHeader = "X-GRAFANA-AUTH" //Header, наличие которого определяет успешную авторизацию
Для примера этого вполне достаточно, на практике может потребоваться выполнять предустановку этих значений. Вы можете передавать их proxy как параметры командной строки, затем использовать flag [4] или более продвинутые пакеты для их разбора. В контейнерной среде также часто используются переменные окружения для конфигурации сервисов, на этом пути вам поможет os.Getenv [5].
Далее идет проверка авторизации:
if strings.HasPrefix(r.URL.String(), "/api") {
err := CheckRights(r.Header)
if err != nil {
SendJSONError(w, err.Error())
return
}
}
Если запрос идёт на grafana.host/api, проверяем права текущего пользователя на использование Grafana. Проверка необходима, чтобы на каждый GET-запрос JS-скрипта или PNG-иконки не беспокоить точку авторизации. Статический контент мы будем проксировать без дополнительных проверок. Для этого передаем map с заголовками, в которых содержится сессия пользователя, в сервис авторизации. Это может быть обычный GET-запрос. Устройство сервиса авторизации здесь значения не имеет. Если данные авторизации не устраивают, закрываем соединение, возвращая ошибку.
После проверок формируем объект базового пути:
url, err := url.Parse(fmt.Sprintf("http://%s/", grafanaHost))
С помощью стандартного пакета httputil [6], расширяющего пакет http, формируем объект ReverseProxy.
proxy := httputil.NewSingleHostReverseProxy(url)
ReverseProxy — это обработчик запроса, который примет входящий запрос, отправит его в Grafana и передаст ответ обратно клиенту.
Он будет направлять все запросы по адресу “базовый путь + запрошенный url”. Если пользователь пришел по адресу proxy:4000/api/something, его запрос будет превращен в grafana:3000/api/something, где grafana:3000 — базовый путь, переданный в NewSingleHostReverseProxy, а /api/something — входящий запрос.
Добавляем авторизационный заголовок для Grafana и вызываем метод ServeHTTP, который сделает основную работу по обработке запроса.
r.Header.Set(grafanaHeader, grafanaUser)
proxy.ServeHTTP(w, r)
Под капотом ServeHTTP делает довольно много работы, например, обрабатывает заголовок X-Forwarded-For или 101 ответ сервера на смену протокола. Основная работа метода — отправить запрос на составной адрес и полученный ответ переложить в ResponseWriter.
Результат
Весь код доступен на github [7].
Эмулируем нашу систему с помощью Docker. Создадим два контейнера — proxy и Grafana в одной сети. Точку авторизации создавать не будем, считаем, что она всегда отвечает утвердительно. Контейнер Grafana будет недоступен вне сети, контейнер с proxy доступен на определённом порту.
Создаём сеть:
docker network create --driver=bridge --subnet=192.168.0.0/16 gnet
Поднимаем контейнер Grafana с настроенным режимом авторизации посредством заголовка:
docker run -d --name=grafana --network=gnet -e "GF_AUTH_PROXY_ENABLED=true" -e "GF_AUTH_PROXY_HEADER_NAME=X-GRAFANA-AUTH" grafana/grafana
Обращаю ваше внимание, что это демонстрационная и не окончательная конфигурация. Как минимум, необходимо установить пароль администратора и запретить автоматическую регистрацию пользователей.
Поднимаем reverse proxy:
docker run -d --name proxy -p 4000:4000 --network=gnet grafana_proxy:latest
В браузере переходим на localhost:4000.
Отлично, перед нами авторизованная Grafana.
Dockerfile для сборки контейнера с proxy и более подробная инструкция по поднятию контейнеров есть на github [7].
Автор: Дмитрий Сыроватский
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/340256
Ссылки в тексте:
[1] Обратный прокси: https://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%80%D0%B0%D1%82%D0%BD%D1%8B%D0%B9_%D0%BF%D1%80%D0%BE%D0%BA%D1%81%D0%B8
[2] Grafana: https://grafana.com/
[3] официальная документация: https://grafana.com/docs/auth/auth-proxy/
[4] flag: https://golang.org/pkg/flag/
[5] os.Getenv: https://gobyexample.com/environment-variables
[6] httputil: https://golang.org/pkg/net/http/httputil/
[7] доступен на github: https://github.com/460s/grafana_proxy
[8] Источник: https://habr.com/ru/post/480446/?utm_source=habrahabr&utm_medium=rss&utm_campaign=480446
Нажмите здесь для печати.