Golang и прокси через прокси

И снова у нас будет увлекательная история о том, как я женил ужа с ежом. В целом тут будет много специфической информации о тезнологиях, с которыми каждый ИТ-специалист среднего уровня сталкивается ежедневно, посему сей опус может быть сложноват для восприятия неподготовленным читателем, однако как известно я пишу больше для себя заметки, но и с вами всегда рад поделиться. За сим начнём.

Итак, есть замечательный язык программирования go lang от небезиветсной корпрации добра Google. Сам язык имеет как свои плюсы, так и минусы, о которых я конечно же рассказывать не буду. В одном из своих предыдущих высеров я как-то упоминал, что для ускорения разработки в целом и сокращения количества написанного программного кода принято использовать так называемые библиотеки функций. В go lang данный феномен называется модулями. Соответственно программист пишет какой-то модуль, складывает его куда-то и после этого все желающие, у кого есть доступ до этого куда-то, могут не писать этот самый код, а использовать уже готовые функции и методы, предоставляемые этим модулем. Всё просто и прозрачно. На первый взгляд.

Однако диавол, как все знают, кроется в деталях. И такой деталью является то, что люди между собой делятся не всем, что они написали и некоторые модули могут находиться где-то в приватной системе хранения, куда требуется аусвайз любого толка. В своё время компания Oracle придумала для именования пакетов такую схему <доменное имя>/<пакет>/<подпакет>/.... например example.com/api/auth. Google решила, что это как-будто бы здорово и переняла опыт, попутно добавив внутри компилятора возможность эти самые пакеты скачивать из интернета базируясь на вышеописанное схеме. Так, в случае с примером выше, при запуске команды go get example.com/api/auth компилятор отправится по данному пути, магическим образом в конце добавит все интересующие его адреса и скачает к себе готовый модуль, если таковой имеется.

Первая из интересных особенностей – go lang модули распространяются просто упаковкой исходных кодов, соответственно мы можем просто упаковать наши тексты в zip и спокойно распространять их архивом. Однако это много ручной работы, каждый раз собирать архив. И тут нам на помощь приходит особенность современных систем управления версиями по типу Gitlab. Если спросить у последнего по определённому адресу – вместо самого git репозитория можно получить готовый zip со всеми исходными кодами, актуальными на дату запроса. Удобно? Удобно конечно, роботы работыют за нас. Но доступ в эту самую систему контроля и управления версиями чаще всего закрыт тройной авторизацией, а которой go компилятор не знает ничего. И тут возникает вторая особенность, связанная с форматом модуля. Мы хотим получить example.com/api/auth, а он доступен либо через https://git.example.com/test/api/auth либо вообще через ssh://git@git.example.com/test/api/auth. Сам по себе go lang не умеет работать с таким формато импортов, однако спасибо разработчикам – компилятор имеет костыль. Если по адресу пакета вместо данных будет специальным образом сформированная строка с тегом meta – go компилятор возьмёт её и попытается с локально указанными секретами сходить куда его послали. Итак, магический тег выглядит условно следующим образом

И в целом всё, компилятор сам спросит метадату, сам сходит в git репозиторий и сам же заберёт все данные для сборки модуля, при этом не надо явно нигде указывать пути.

И в целом это всё прекрасно работает в рамках локальных машин пользователей и даже некоего CI/CD проекта, который имеет на борту ssh ключ для скачивания всех нужных зависимостей.

Но тут мы сталкиваемся с тем, что качать одно и то же 100500 раз и нагружать конечный сервер это как-то не комильфо и задумывается о некоем промежуточном гноилище, которое будет в себе хранить уже готовые для использования артефакты. Долго ли, коротко ли – нашёлся проект Athens. Простейшая прокся, у которой можно спросить какой-то модуль. В случае если в кеше его нет – он куда-то сходит и скачает к себе, если в кеше есть – просто отдаст никуда не ходя. И пока ты работаешь с публичными модулями – всё замечательно, но как только тебе нужно проксировать ещё и приватные – начинаются приседания.

Итак, перво-наперво нужно для себя решить как Athens будет эти модули забирать и откуда. В нашем случае есть условный Gitlab где-то в интернете. Забирать можно, как водится, по ssh и https. В примере выше метатег говорит, что исходники модуля находятся по адресу ssh://git@git.example.com/test/api/auth, но мы не хотим иметь на сервере, например, ключ ssh для клонирования исходников, а хотим сделать gitlab token, по которому обезьянка будет забирать всё, что её интересует.

Соответственно перво-наперво настраиваем git для пользователя, от которого запущен athens:

~/.gitconfig

[url "https://git.example.com/"]
  insteadOf git@git.example.com:
  insteadOf ssh://git@git.example.com/
[credential "https://git.example.com"]
  username = gitlab-token
[credential]
  helper = store

~/.git-credentials

https://gitlab-token:gplat-XXXXXXXXX@git.example.com

После этого настраиваем сами афины

~/.env

http_proxy=http://proxy:3128
https_proxy=http://proxy:3128
no_proxy=127.0.0.1,localhost,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,10.233.0.1,.local,.svc,.monitoring.svc
ATHENS_GONOSUM_PATTERNS=example.com/*
GO_ENV=production
GO111MODULE=on
ATHENS_GLOBAL_ENDPOINT=https://athens.local

~/config.toml

GoBinary = "go"
GoEnv = "production"
GoBinaryEnvVars = ["GOPROXY=direct","GOROOT=/usr/local/go","GOSUMDB=sum.golang.org"]
GoGetWorkers = 10
GoGetDir = ""
ProtocolWorkers = 30
LogLevel = "debug"
LogFormat = "plain"
CloudRuntime = "none"
EnablePprof = false
PprofPort = ":3001"
FilterFile = ""
RobotsFile = "robots.txt"
Timeout = 300
StorageType = "disk"
Port = ":3000"
UnixSocket = ""
GlobalEndpoint = "http://localhost:3000"
BasicAuthUser = ""
BasicAuthPass = ""
HomeTemplatePath = "/var/lib/athens/home.html"
ForceSSL = false
ValidatorHook = ""
PathPrefix = ""
NETRCPath = ""
GithubToken = "" 
HGRCPath = ""
TraceExporter = ""
TraceExporterURL = "http://localhost:14268"
StatsExporter = "prometheus"
SumDBs = ["https://sum.golang.org"]
NoSumPatterns = ["example.com/*"]
DownloadMode = "sync"
NetworkMode = "strict"
DownloadURL = ""
SingleFlightType = "memory"
IndexType = "none"
ShutdownTimeout = 60

[SingleFlight]
[Storage]
  [Storage.Disk]
    RootPath = "/athens-data/"

Теперь ставим перед всем этим nginx, заворачиваем ssl и пробуем что у нас получается на уже локальной тачке

export GOPROXY="https://athens.local"
export GOSUMDB="off"
export GO111MODULE="on"
cd /tmp
mkdir t
cd t
go mod init example.in/test
go get -v -u example.com/api/auth@0.0.1

В случае успеха athens сходит в наш вымышленный gitlab с паролями, которые мы ему указали, скачает в себя нужную версию, отмеченную тегом в репозитории и отдаст готовые данные пользователю. Если что-то пошло не так – добро пожаловать в удивительный мир программирования, друг, изучай что там в логах и почему так произошло. Возможно к конфигурации git ещё потребуется добавить файлик ~/.netrc, но о нём ты можешь уже узнать из интернета.