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.
Что делать сегодня: если используете .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 на клиенте
Хранение токенов:
# Использование 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:
- Включить логирование (часто off by default)
- Настроить alert на аномалии (по объёму запросов, по новым IP, по неожиданным endpoints)
- Раз в месяц просматривать 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:
- Идентифицировать ключ (какой именно скомпрометирован, какие сервисы используют)
- Сгенерировать новый ключ через UI/API провайдера
- Обновить secrets manager с новым ключом
- Деплой приложений с новым ключом
- Проверить, что весь трафик идёт с новым (по логам)
- Отозвать старый ключ
- Аудит логов за период действия скомпрометированного ключа — что было прочитано/изменено
Время от выявления до отзыва старого ключа должно быть минуты, не часы. Если runbook занимает день, переделать.
OWASP API Security Top 10 (2023)
OWASP — некоммерческая организация, публикующая лучшие практики безопасности веб-приложений. API Security Top 10 — это рейтинг самых частых уязвимостей в API. Junior DE стоит знать названия и суть.
Для Junior DE ключевое:
- API1 (BOLA) и API3 (BOPLA) — даже если вы не пишете API, понимайте, как они работают, чтобы не наступать на них с другой стороны
- API4 — реальная угроза для вашего ETL-сервиса. Без rate-limiting на стороне клиента вы случайно DDoS-нете партнёра
- API10 — ваш кейс ежедневно. Валидируйте ответы партнёров, не доверяйте слепо
Чеклист на каждый PR
Перед мерджем кода с интеграциями API:
Эти 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. Через четыре урока модуля прошли:
- Basic Auth и API keys — простейшие механизмы. Базовая гигиена: HTTPS, headers (не query string), ротация
- Bearer и JWT — самоносимые подписанные токены. PyJWT, защита от alg=none и alg confusion
- OAuth 2.0/2.1 — протокол делегированного доступа. Authorization Code + PKCE / Client Credentials / Device Code
- Security checklist + OWASP — двенадцать правил гигиены, которые держат вас в стороне от инцидентов
Для Junior DE главное — выработать рефлексы. HTTPS — рефлекс. Timeout — рефлекс. Не логировать секреты — рефлекс. Каждый код-ревью — пройти короткий чек-лист.
В следующем модуле перейдём к pagination, rate limits, retry и circuit breaker — теме «что делать, когда сервер начинает капризничать». Это другой блок security-сознания: не «защититься от взлома», а «не положить сервис партнёра и не положить себя».