Learning Platform
Глоссарий Troubleshooting
Урок 09.04 · 20 мин
Начальный
SecurityOWASPSecretsBest practices

Security checklist: что делать каждый день, чтобы не открыть инцидент

В трёх предыдущих уроках мы прошли механизмы: Basic Auth, API keys, JWT, OAuth2. В этом — практический чек-лист правил, которые Junior DE должен знать наизусть. Большинство security-инцидентов в индустрии случается не из-за сложных уязвимостей, а из-за нарушения базовой гигиены: ключ закоммитили в git, токен в логе, пароль в URL.

Урок устроен как двенадцать правил с объяснением «почему» и пример, что бывает при нарушении. Прочтите, отметьте мысленно, что уже соблюдаете, что — нет. К каждому правилу — действие, которое можно сделать сегодня.


Правило 1: HTTPS only, всегда, без исключений

Любой запрос к external API — только по https://. Никаких http:// ни в production, ни в staging, ни в локальной разработке (для production-API).

Почему: HTTP передаёт всё открытым текстом. Любой узел между вами и сервером (Wi-Fi роутер в кафе, провайдер интернета, прокси на работе) видит и заголовки (Authorization!), и тело запроса.

Что делать сегодня:

# Проверьте все URL в codebase
grep -r "http://" --include="*.py" .

# Если нашли -- заменить на https://
# Только два валидных исключения:
# 1. localhost при разработке
# 2. внутренние сервисы за VPN/private network (но всё равно лучше TLS)

В requests можно явно отключить fallback на HTTP:

# Если редирект приведёт с https на http -- упасть
session = requests.Session()
session.mount("http://", requests.adapters.HTTPAdapter(max_retries=0))

Правило 2: Никогда не коммитить секреты в git

API keys, токены, пароли, приватные ключи — никогда в код, никогда в git.

Почему: git rewrite не помогает — copies клонов уже у других людей, история кэшируется в GitHub. Один раз закоммитил — ключ скомпрометирован навсегда.

Что делать сегодня:

# 1. Установить git-secrets или talisman локально
brew install git-secrets
git secrets --install
git secrets --register-aws

# 2. Добавить в .gitignore
echo ".env" >> .gitignore
echo ".env.*.local" >> .gitignore
echo "*.pem" >> .gitignore
echo "credentials.json" >> .gitignore

# 3. Включить GitHub Secret Scanning (бесплатно для public repo)
# Settings -> Code security -> Secret scanning

# 4. Прогнать историю репо через truffleHog или gitleaks
gitleaks detect --source . -v

Если случайно закоммитили — ротировать немедленно, не пытаться «удалить из истории».


Правило 3: Используйте secrets manager в production

Локально — .env с правами 600. В production — никаких .env на боевых машинах.

Почему: .env файл лежит на диске. Любой с доступом к машине (sysadmin, скомпрометированный CI runner, утёкший backup) получает все ваши ключи разом. Secrets manager даёт: централизованный access control, audit log, ротацию, шифрование на rest.

Secrets management по стадиям
Local dev.env с правами 600 (chmod 600 .env), путь в .gitignore. На macOS/Windows можно использовать Keychain. На Linux -- Secret Service / pass
CI/CDGitHub Actions secrets, GitLab CI/CD variables. Передаются в пайплайн как environment variables, маскируются в логах
Staging/ProdAWS Secrets Manager, HashiCorp Vault, Doppler, GCP Secret Manager. Получение секретов через IAM-роль контейнера/пода -- никаких static credentials в коде
Bonus: rotationAWS Secrets Manager + Lambda могут автоматически ротировать ключи (например, RDS пароли). Vault имеет dynamic secrets -- выдаёт ключ на 1 час и сам отзывает

Что делать сегодня: если используете .env в production — это технический долг, нужно мигрировать в secrets manager. Минимальная альтернатива (если budget ограничен) — хранить шифрованный envfile в репо (sops, git-crypt) и расшифровывать на старте сервиса.


Атаки: OWASP Top 10 и сетевые угрозы

Правило 4: Least privilege scopes — запрашивай минимум

Если приложение читает email, не запрашивайте доступ к календарю и контактам. Если читает — не запрашивайте write. Если работает с одним проектом — не давайте org-wide права.

Почему: компрометация — вопрос «когда», не «если». Минимальные scopes ограничивают радиус взрыва. Утёк токен с read:profile — потеряли немного. Утёк токен с admin:org — потеряли всё.

Что делать сегодня: пройдите по всем интеграциям в проекте, выпишите запрошенные scopes, для каждого спросите «реально ли используется?» Часто ответ — нет, можно сократить.

# Плохо
scope = "user repo gist admin:org admin:repo_hook"

# Хорошо: только то, что реально используем
scope = "repo:status read:repo"

Аналогично для API keys: некоторые провайдеры (Stripe, AWS, GitHub) поддерживают restricted keys — ключ только для чтения определённых ресурсов. Используйте их.


Правило 5: Tokens с истечением, refresh — короткий

Лучший секрет — короткоживущий. Стандартные параметры:

  • Access token: 15-60 минут
  • Refresh token: 1-30 дней (для long-lived sessions — до 90)
  • API key: ротация раз в 30-90 дней

Почему: даже если токен утёк, окно атаки ограничено его сроком жизни.

Что делать сегодня: проверить все ваши токены. Если где-то выдаются на год — спросите, нужно ли это. Если есть API keys без ротации — поставить календарную напоминалку «ротировать раз в квартал».


Правило 6: Не логировать секреты — даже фрагменты

logger.debug(f"Using token {token[:10]}...") — это утечка. Первые 10 символов часто содержат префикс (sk_live_) и часть с энтропией. В сочетании с другими логами этого может хватить для атаки.

Почему: логи попадают в хранилища, к которым доступ имеет больше людей, чем к production-секретам. Утёкшие фрагменты позволяют поиск по архивам и social engineering.

Что делать сегодня:

# Плохо
logger.debug(f"Token: {api_key}")
logger.info(f"Auth header: {headers}")  # headers содержит Authorization

# Хорошо: логировать факт использования, без значения
logger.debug("Using API token")
logger.info("Auth header set")

# Если очень нужно -- last 4 символов для трейсинга
logger.debug(f"Token ending in ...{api_key[-4:]}")

Для structured-логирования — фильтр на уровне formatter:

import logging

class SecretFilter(logging.Filter):
    SENSITIVE_KEYS = ("password", "token", "secret", "api_key", "authorization")

    def filter(self, record):
        if hasattr(record, "msg") and isinstance(record.msg, str):
            for key in self.SENSITIVE_KEYS:
                if key in record.msg.lower():
                    record.msg = "[FILTERED]"
        return True

logging.getLogger().addFilter(SecretFilter())

В production-grade логировании используют libraries вроде logtail, structlog с auto-redaction.


Правило 7: Secure storage на клиенте

Хранение токенов:

Где хранить токены
OS keyringmacOS Keychain, Windows Credential Manager, Linux Secret Service. Шифрование на уровне OS, доступ только нашему приложению
Encrypted configЗашифрованный файл конфига (sops, age, gpg). Расшифровывается при запуске. Подходит для headless-сценариев
Plain text .envМинимально приемлемо для локальной разработки. chmod 600 обязательно. Никогда в production
HARD NOХардкод в коде, в репозитории, в Slack/email, в plain config -- никогда
# Использование keyring в Python
import keyring

# Сохранить
keyring.set_password("my-app", "api_token", "sk_live_xxx")

# Прочитать
token = keyring.get_password("my-app", "api_token")

Правило 8: Audit access logs

Большие платформы (AWS, Google Cloud, Stripe, GitHub) предоставляют audit logs — кто, когда, какой токен, какой endpoint. Используйте их.

Почему: единственный способ обнаружить компрометацию до катастрофы. Если ваш ETL обычно делает 1000 запросов в день, а внезапно начал делать 50000 — это red flag.

Что делать сегодня: для каждой интеграции, где есть audit log:

  1. Включить логирование (часто off by default)
  2. Настроить alert на аномалии (по объёму запросов, по новым IP, по неожиданным endpoints)
  3. Раз в месяц просматривать summary

Для собственных API — внедрить аналогичное логирование на стороне сервера. Каждый запрос -> who (user_id из JWT), when, what (endpoint + method), result (status code).


Правило 9: Rate limiting со стороны клиента

Не полагайтесь на сервер в защите от вашего же сервиса. Сервер вернёт 429 — но это уже после того, как вы DDoS-нули его.

Почему: bug в коде -> бесконечный цикл -> 1000 RPS на partner API -> его рейт-лимит -> бан вашего IP/токена -> инцидент.

Что делать сегодня: для каждой интеграции с external API — поставить rate limiter на стороне клиента, чуть строже, чем у сервера.

# pyrate-limiter
from pyrate_limiter import Duration, Rate, Limiter

# Если у API лимит 100 RPS, ставим себе 80 (запас)
rates = [Rate(80, Duration.SECOND)]
limiter = Limiter(rates)

@limiter.as_decorator()
def make_request(item_id):
    return requests.get(f"https://api.example.com/items/{item_id}")

В деталях rate-limiting и retry разберём в module 08.


Правило 10: Timeout на ВСЕ запросы

requests.get(url) без timeout=N — плохо. По умолчанию requests ждёт ответа бесконечно. Если сервер завис, ваш скрипт тоже завис.

# Плохо
response = requests.get("https://slow-api.example.com/data")

# Хорошо
response = requests.get(
    "https://slow-api.example.com/data",
    timeout=10,  # 10 секунд на весь запрос
)

# Лучше: разные timeouts для connect и read
response = requests.get(
    "https://slow-api.example.com/data",
    timeout=(3, 30),  # 3s на connect, 30s на read
)

Что делать сегодня: grep -r "requests\." . | grep -v "timeout" — найти все запросы без timeout.


Правило 11: HTTPS verify=True (никогда не отключайте проверку сертификатов)

В requests параметр verify=True по умолчанию. Иногда люди отключают его, потому что «у нас self-signed сертификат, выдаёт ошибку».

# НИКОГДА
response = requests.get("https://internal.example.com/", verify=False)

Почему: без проверки сертификата нет защиты от MITM. Любой может подделать internal.example.com и читать ваш трафик.

Правильное решение для self-signed:

# Указать путь к CA-сертификату
response = requests.get(
    "https://internal.example.com/",
    verify="/path/to/ca-bundle.pem",
)

Или добавить self-signed CA в системный truststore.


Правило 12: Регулярная ротация и runbook на компрометацию

Все токены в production должны иметь plan ротации. У всех команд должен быть runbook — пошаговая инструкция «что делать, если ключ утёк».

Минимальный runbook:

  1. Идентифицировать ключ (какой именно скомпрометирован, какие сервисы используют)
  2. Сгенерировать новый ключ через UI/API провайдера
  3. Обновить secrets manager с новым ключом
  4. Деплой приложений с новым ключом
  5. Проверить, что весь трафик идёт с новым (по логам)
  6. Отозвать старый ключ
  7. Аудит логов за период действия скомпрометированного ключа — что было прочитано/изменено

Время от выявления до отзыва старого ключа должно быть минуты, не часы. Если runbook занимает день, переделать.


OWASP API Security Top 10 (2023)

OWASP — некоммерческая организация, публикующая лучшие практики безопасности веб-приложений. API Security Top 10 — это рейтинг самых частых уязвимостей в API. Junior DE стоит знать названия и суть.

OWASP API Top 10 (2023) -- обзор
API1: BOLABroken Object Level Authorization. /users/123/data доступен любому залогиненному, без проверки 'это его данные?'. Самая частая уязвимость API в индустрии
API2: Broken AuthДыры в самом auth: weak passwords, JWT без проверки подписи, alg=none, токены без exp
API3: Broken Property AuthПоля объекта доступные для записи, которые не должны быть. PATCH /user с {is_admin: true} -- и пользователь становится админом
API4: Resource ConsumptionНет rate limiting -> DDoS возможен. Нет лимитов на размер upload -> залить терабайт
API5: Broken Function AuthEndpoint /admin/delete-all доступен обычным пользователям, потому что забыли проверку роли
API6: Sensitive Business FlowsEndpoint можно использовать для абуза: купить все билеты на концерт через автоматизацию, накрутить отзывы
API7: SSRFServer-Side Request Forgery. Endpoint принимает URL и делает запрос -- атакующий заставляет сервер ходить во внутреннюю сеть
API8: MisconfigurationDefault credentials, CORS allow *, debug mode в проде, открытый swagger с production-данными
API9: InventoryНе знаем, какие версии API ещё живут. /v1 deprecated, но всё ещё работает с известной уязвимостью
API10: Unsafe ConsumptionИспользуем третий-сторонний API, не валидируя его ответы. Если партнёр скомпрометирован, его ответы могут быть вредоносными

Для Junior DE ключевое:

  • API1 (BOLA) и API3 (BOPLA) — даже если вы не пишете API, понимайте, как они работают, чтобы не наступать на них с другой стороны
  • API4 — реальная угроза для вашего ETL-сервиса. Без rate-limiting на стороне клиента вы случайно DDoS-нете партнёра
  • API10 — ваш кейс ежедневно. Валидируйте ответы партнёров, не доверяйте слепо

Чеклист на каждый PR

Перед мерджем кода с интеграциями API:

PR-checklist для API-интеграций
Все URL -- HTTPSgrep по диффу: 'http://' -- должно быть пусто или только localhost
Нет хардкода секретовAPI keys/tokens читаются из os.environ или secrets manager, не литералы в коде
Все запросы с timeoutКаждый requests/httpx вызов имеет timeout=N. Без него -- отклонить PR
Нет log с секретамиПоиск по диффу: token, password, api_key -- не должны попадать в logger.* без [REDACTED]
Scopes минимальныЕсли меняли OAuth scopes или API key permissions -- реально нужны новые? Или legacy?
Errors обрабатываютсяraise_for_status или явная проверка status_code. Не молча проглатывать 401/403/429/500

Эти 6 проверок занимают 5 минут на PR и закрывают 80% security-инцидентов в production.


Что почитать дальше

  • OWASP API Security Top 10: owasp.org/API-Security
  • OAuth 2.0 Security Best Current Practice (BCP): RFC 9700
  • Designing Data-Intensive Applications Martin Kleppmann — глава о security in distributed systems
  • PortSwigger Web Security Academy — бесплатные интерактивные labs по веб-безопасности

Хорошие источники для DE-инженера, который хочет растить security-grokking без становления pen-tester’ом.


Итоги модуля 8

Аутентификация — фундамент любой работы с API. Через четыре урока модуля прошли:

  1. Basic Auth и API keys — простейшие механизмы. Базовая гигиена: HTTPS, headers (не query string), ротация
  2. Bearer и JWT — самоносимые подписанные токены. PyJWT, защита от alg=none и alg confusion
  3. OAuth 2.0/2.1 — протокол делегированного доступа. Authorization Code + PKCE / Client Credentials / Device Code
  4. Security checklist + OWASP — двенадцать правил гигиены, которые держат вас в стороне от инцидентов

Для Junior DE главное — выработать рефлексы. HTTPS — рефлекс. Timeout — рефлекс. Не логировать секреты — рефлекс. Каждый код-ревью — пройти короткий чек-лист.

В следующем модуле перейдём к pagination, rate limits, retry и circuit breaker — теме «что делать, когда сервер начинает капризничать». Это другой блок security-сознания: не «защититься от взлома», а «не положить сервис партнёра и не положить себя».


Проверка знанийKnowledge check
Junior закоммитил .env с production API key в публичный репо неделю назад. Сейчас обнаружил. Какие три действия в правильном порядке нужно сделать?
ОтветAnswer
Действия по приоритету: (1) НЕМЕДЛЕННО ротировать ключ через UI провайдера. Не пытаться 'удалить из git history' -- копии репо уже у других людей, история кэшируется в GitHub, ключ уже считается скомпрометированным. Главный приоритет -- отозвать доступ. (2) Проверить audit logs провайдера за неделю с момента коммита: были ли подозрительные запросы (новые IP, аномальные объёмы, доступ к данным, к которым обычно не обращались)? Если были -- это уже инцидент, эскалация в security team. (3) Только после ротации -- git filter-branch / BFG Repo-Cleaner для очистки истории и принудительный force-push, чтобы хотя бы будущие клоны не содержали ключа. Также включить GitHub Secret Scanning, добавить .env в .gitignore, поставить git-secrets/gitleaks в pre-commit, чтобы такое не повторилось. Важный психологический момент: подобная ошибка случается со всеми хоть раз. Главное -- реакция (быстрая ротация), а не самобичевание.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. Какие из этих практик допустимы для хранения API key в production?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4