8 лучших практик написания безопасного кода на Go

Подход «shift left» в безопасности (обеспечение безопасности на ранних этапах разработки) в трёх основных областях:

  • безопасность кода
  • управление зависимостями
  • безопасность во время выполнения / контейнеров

Практики

1. Используйте Go Modules

  • Концепция: Go Modules — это встроенное решение для управления зависимостями в Go (заменяет старые инструменты вроде Dep или Glide).
  • Польза для безопасности:
    • go.sum хранит контрольные суммы прямых и косвенных зависимостей. Это обеспечивает целостность: гарантируется, что код, который вы собираете сегодня, точно такой же, как и вчера (что предотвращает атаки на цепочку поставок, когда репозиторий изменяется).
    • Упрощает управление версиями и обновление уязвимых пакетов с помощью go get.
  • Тема для собеседования: “Я строго использую Go Modules, чтобы гарантировать воспроизводимость сборки и целостность через контрольные суммы.”

2. Используйте Reflection экономно

  • Риск: Reflection (reflect пакет) позволяет манипулировать объектами во время выполнения, обходя строгую статическую типизацию Go.
  • Недостатки:
    • Паники во время выполнения: Компилятор не ловит ошибки типов в коде с reflection; они проявляются только при запуске.
    • Читаемость: Код становится сложным для понимания и поддержки.
    • Производительность: Обычно медленнее, чем использование прямых типов.
  • Лучшие практики:
    • Предпочитайте строгую типизацию и интерфейсы.
    • Используйте генерацию кода (go generate) для создания шаблонного кода вместо динамического решения через reflection.
    • Если необходимо использовать: пишите обширные unit-тесты для покрытия крайних случаев (например, передача float вместо int).

3. Защищайтесь от инъекций

  • Концепция: Никогда не доверяйте пользовательскому вводу.
  • Решение: Используйте стандартную библиотеку Go.
    • HTML/шаблоны: Используйте html/template вместо text/template для веб-вывода. Он автоматически выполняет контекстно-зависимое экранирование (предотвращает XSS).
    • SQL: Используйте параметризацию database/sql (? или $1) вместо конкатенации строк, чтобы предотвратить SQL-инъекции.
  • Тема для собеседования: “Я всегда валидирую ввод и полагаюсь на встроенные механизмы экранирования стандартной библиотеки, а не на ручную очистку.”

4. Сканирование на уязвимости (Shift Left)

  • Реальность: Большая часть бинарного файла вашего приложения — это сторонние библиотеки, а не ваш собственный код. Хакеры нацеливаются на эти зависимости.
  • Действия:
    • Shift Left: Не ждите, пока команда безопасности просканирует код в продакшене.
    • Инструменты: Используйте инструменты (например, Snyk или плагины IDE) для сканирования go.mod во время разработки.
    • Исправления: активно отслеживайте CVE и обновляйте минорные версии зависимостей для устранения известных уязвимостей.

5. Избегайте unsafe и cgo

  • CGO:
    • Позволяет Go вызывать C-код.
    • Недостатки: усложняет сборку (требуется GCC), ломает лёгкость кросс-компиляции и вводит проблемы безопасности памяти из C в Go.
  • Пакет unsafe:
    • Позволяет обходить систему типов и читать/писать произвольную память.
    • Риски: повреждение памяти, мусорные данные и ошибки сегментации. Демонстрация показала, как арифметика указателей в unsafe может возвращать мусорные данные или аварийно завершать приложение при неправильном выравнивании памяти.
  • Тема для собеседования: “Я избегаю unsafe и cgo, чтобы поддерживать гарантии безопасности памяти Go и простоту сборки. Если нужен низкоуровневый доступ, сначала ищу нативное решение на Go.”

6. Используйте стандартные криптографические пакеты

  • Золотое правило: не пишите свою криптографию.
  • Решение: Используйте стандартную библиотеку Go (crypto/*, net/http).
    • Она рецензируется экспертами, хорошо протестирована и регулярно обновляется.
    • Поддерживает современные стандарты хеширования, шифрования и инфраструктуры открытых ключей (PKI).
  • Контекст: Большинство популярных веб-фреймворков на Go — это просто обёртки вокруг стандартной библиотеки net/http, потому что она уже подходит для продакшена.

7. Минимизируйте поверхность атаки контейнера

  • Проблема: Стандартные базовые образы (например, ubuntu или centos) огромные (1 ГБ+) и содержат инструменты, которые любят хакеры (curl, bash, grep).
  • Решение: Используйте Multi-Stage Builds в Docker.
    • Этап 1 (Build): Используйте тяжёлый образ (с Go, GCC, git) для компиляции бинарника.
    • Этап 2 (Run): Копируйте только скомпилированный бинарник в лёгкий базовый образ.
  • Рекомендации по базовым образам:
    • scratch: пустой образ (самый маленький, только статические бинарники).
    • distroless (Google): содержит только зависимости приложения, без shell.
    • alpine: маленький, но использует musl вместо glibc (следите за совместимостью).
  • Инструмент: Используйте dive для проверки слоёв Docker и ненужного места.

8. Соображения безопасности во время выполнения

При запуске контейнера (Kubernetes/Docker):

  1. Файловая система только для чтения: монтируйте root FS как read-only, чтобы атакующие не могли загружать вредоносное ПО или изменять бинарник.
  2. Принцип пользователя: никогда не запускайте от root (UID 0). Создайте пользователя в Dockerfile (например, UID 10001).
  3. Уберите возможности: удалите все Linux capabilities и добавьте только необходимые (например, NET_BIND_SERVICE).
  4. Ограничения ресурсов: задайте лимиты CPU/RAM (Cgroups) для предотвращения DoS-атак или майнинга криптовалют.
  5. Сетевые политики: Zero-Trust сеть. По умолчанию блокируйте весь трафик и явно разрешайте только необходимые соединения между подами.

Как вы пишете безопасный код на Go?

  1. Целостность зависимостей: “Использую Go Modules для воспроизводимых сборок.”
  2. Безопасность ввода: “Полагаюсь на стандартную библиотеку для предотвращения инъекций (SQL/XSS).”
  3. Типовая безопасность: “Избегаю unsafe и reflection, чтобы компилятор ловил ошибки.”
  4. Цепочка поставок: “Cканирую зависимости на CVE во время разработки/cборки.”
  5. Развёртывание: “Использую multi-stage Docker сборки для доставки минимальных образов без shell (Distroless/Scratch) и никогда не запускаю их от root.”