Перейти к содержанию

Go-зависимости

Правила добавления и поддержки внешних модулей. Каждая зависимость — это чужой код, который ты взял на supply-chain-ответственность. Прежде чем делать go get, пройди vetting.

Содержание

Принципы

  • Stdlib first. Прежде чем тянуть внешний модуль, проверь, нет ли нужного в стандартной библиотеке. net/http, log/slog, errors, encoding/json закрывают большую часть задач.
  • Минимум transitive-зависимостей. Библиотека, которая сама тащит 10+ модулей, — красный флаг. Выбирай узкие фокусированные решения.
  • Supply chain-проверка обязательна. Любой новый модуль проходит vetting (см. ниже). Это не бюрократия: одна необдуманная зависимость — один CVE или bootstrap malware в прод.
  • Старение зависимостей — баг. Модуль без обновлений полгода + findings в govulncheck — апгрейд или замена, а не «оставим пока».

Как добавить зависимость

Последовательность в репозитории сервиса:

go get github.com/<org>/<module>@<version>
go mod tidy
go build ./...
go test ./...
govulncheck ./...
git add go.mod go.sum
git commit -m "feat: добавил <module> для <причина>"

Правила:

  • Всегда коммить и go.mod, и go.sum одним PR. go.sum фиксирует checksum'ы — без него подмена в прокси-кэше пройдёт незамеченной.
  • Не редактируй go.mod руками — используй go get/go mod tidy.
  • indirect-секция go.mod управляется go mod tidy; вручную туда не пиши.

Vetting checklist

Перед тем, как выполнить go get, пройди по чек-листу:

  • Лицензия совместима. MIT, Apache-2.0, BSD-3-Clause, ISC, MPL-2.0 — принимаем. GPL/LGPL/AGPL — нельзя (копилефт вирусится на сервис). Без лицензии — нельзя (юридически нет права использовать).
  • Поддерживается. Commits за последние 6 месяцев, issues не висят годами, есть релизы.
  • Adoption. Звёзд > 500, либо это well-known модуль (например, golang.org/x/*). Единичный репозиторий одного автора — повод трижды подумать.
  • Нет известных CVE. govulncheck ./... после go get — чисто.
  • Код читаемый. Бегло прошёлся по исходнику, не увидел obfuscated-фрагментов, init()-функций с сетевыми вызовами, криптовалютных hook'ов, shell-callout'ов.
  • Совместим с Go 1.22+. go.mod модуля не требует более старой версии Go, тесты проходят на нашей версии.
  • Автор не в denylist. Если известен случай supply-chain-атаки от этого автора (event-stream и прочие прецеденты) — не тянем.
  • Нет альтернативы в stdlib. Финальный sanity-check.

Если хотя бы один пункт не сошёлся — обсуждай в PR, прежде чем добавлять.

Популярные choices

Текущий стек. Перед тем как взять альтернативу любому пункту — обосновывай в PR.

Область Модуль
HTTP-роутер github.com/go-chi/chi/v5
Postgres driver github.com/jackc/pgx/v5
Миграции github.com/golang-migrate/migrate/v4
Event bus github.com/ThreeDotsLabs/watermill + watermill-sql, watermill-kafka, watermill-cqrs
Redis client github.com/redis/go-redis/v9
Validation github.com/go-playground/validator/v10
JWT (если нужен) github.com/golang-jwt/jwt/v5
Testing helpers github.com/stretchr/testify
Integration containers github.com/testcontainers/testcontainers-go
Env-config github.com/kelseyhightower/envconfig
OpenTelemetry go.opentelemetry.io/otel, go.opentelemetry.io/otel/sdk
Prometheus client github.com/prometheus/client_golang
ULID github.com/oklog/ulid/v2
UUID (если нужен) github.com/google/uuid

Не тянуть (есть лучше):

Не надо Вместо
github.com/pkg/errors errors + fmt.Errorf("...%w", err)
github.com/sirupsen/logrus log/slog
go.uber.org/zap log/slog
github.com/julienschmidt/httprouter github.com/go-chi/chi/v5
github.com/gorilla/mux github.com/go-chi/chi/v5
github.com/jinzhu/gorm, gorm.io/gorm pgx + ручной SQL
github.com/lib/pq github.com/jackc/pgx/v5

go.mod convention

module github.com/<org>/<service>-service

go 1.22

require (
    github.com/go-chi/chi/v5 v5.x.x
    github.com/jackc/pgx/v5 v5.x.x
    // ...
)

Правила:

  • Module path соответствует hosting'у репозитория сервиса.
  • Go-версия1.22 или новее, синхронно между всеми сервисами. Повышаем одновременно или в пределах одного квартала.
  • Indirect-секцию управляет go mod tidy, вручную не правим.
  • replace-директивы допустимы только для локальной разработки (тест libver до релиза) и должны удаляться до merge. В main — никаких replace.

Upgrade (security patches)

Minor/patch-обновление:

go get -u github.com/<module>
go mod tidy
go test ./...
govulncheck ./...

Правила:

  • Читай release notes. Даже patch может нести breaking в документе (deprecation warning, изменение defaults).
  • Прогоняй тесты. Unit + integration.
  • Прогоняй govulncheck ./... после каждого upgrade'а.
  • Один PR = один модуль для значимых апгрейдов. Батч-апгрейды — только если все модули обновляются до patch'а.

Массовый ежемесячный sweep: go list -u -m all → отдельный PR на каждую цепочку зависимостей, где есть security fix.

Major version upgrade

Major (v1v2) — почти всегда breaking.

Процесс:

  1. Отдельный PR, не смешивать с feature.
  2. Обновить import path (для semver-v2+ модулей путь включает /v2).
  3. Пройти по breaking-changes релизных notes, починить код.
  4. Прогнать полный test suite с integration: testcontainers Postgres, Kafka.
  5. Canary deploy. 1 pod 1 час → 10% 24 часа → 100%. Смотри http_request_errors_total, go_goroutines, память.
  6. Если регрессия — откат, issue в upstream.

Security response

При появлении CVE в зависимости, которая попадает в prod:

  • P1 (critical / high). Hotfix в тот же день. go get <module>@latest
  • govulncheck, canary, deploy. PR merge через expedited review.
  • P2 (medium). В течение недели. Обычный PR-flow, но приоритет выше обычных задач.
  • P3 (low). В ближайшем sweep-PR.

Если upstream не отвечает / не фиксит критичный CVE:

  • Fork. Создай github.com/<your-org>/<module> с patch'ем, укажи в PR ссылку на upstream issue и apologize в code comment.
  • В go.mod используется fork через обычный путь (fork публикуется с правильным module path), не через replace. replace — не для долгоживущих состояний.

Vendor vs module

  • Module-only — стандарт. go.mod + go.sum достаточно, Go-proxy кэширует артефакты. CI и прод скачивают модули из прокси.
  • Vendor — только если нужен air-gapped build (нет доступа в интернет из build-окружения). В текущем стеке не используется. Если потребуется — добавляется в сервис-репо через go mod vendor и CI-проверка go mod verify.

Смешивать режимы (vendor в одном сервисе, не-vendor в другом) — нельзя.

CI security checks

В пайплайне сервиса должны стоять следующие проверки (blocker'ы):

  • govulncheck ./... — блокирует merge при known CVE.
  • go mod verify — checksum'ы в go.sum соответствуют скачиваемым модулям.
  • go mod tidy -diff — нет лишних или недостающих записей в go.mod/go.sum (gated: go mod tidy в CI должен быть no-op).
  • trivy fs --scanners vuln . — дополнительный layer (ловит CVE, которые govulncheck не видит из-за слабого symbol-coverage).

SBOM — как артефакт релиза:

syft . -o spdx-json > sbom.json

Прикладывается к каждому tagged-релизу сервиса.

Anti-patterns

  • go get без коммита go.sum. Checksum'ы нет — кэш прокси может отдать другую версию модуля, CI этого не заметит.
  • Игнор govulncheck-findings. «Это не на hot-path» — так не работает: findings это known CVE в символах, которые ты дотащил. Фикси или reproducible-exclude с обоснованием.
  • Pin на точную patch-версию без апгрейда. v1.2.3 forever — через полгода куча пропущенных security-patch'ей.
  • Зависимости на v0.x. По semver-гарантий нет, breaking change возможен в любой patch. Тянем только если альтернативы нет, и в PR — явный комментарий «после стабилизации апгрейд».
  • Shell-callout'ы в Go-библиотеках. Если библиотека вызывает внешние бинарники (exec.Command("sh", "-c", ...)) — это canary: посмотри код и аргументы руками, или выбери другую библиотеку.
  • «Глубокие» вендоры. Модуль, сам зависящий от десятков v0.x и forked-репо других авторов. Одно падение любого — сломанный build сервиса.
  • replace в main. replace переопределяет публикованный модуль на локальный/форкнутый. Долгоживущее replace скрывает от govulncheck настоящее состояние, и следующий разработчик не поймёт, откуда берётся код.

См. также

  • security.md — supply chain вписан в общую security-модель.
  • commits-and-prs.md — формат commit-message для upgrade-PR'ов.