Суть Чтение реальных конфигов и кода — закоммиченный .env, выборка динамической креды из Vault, хелпер envelope encryption на KMS и утекающий лог — предскажите провал и выберите фикс с наибольшим рычагом.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Провалы с секретами диагностируются в диффах, конфигах и логах. Прочтите каждый сниппет, предскажите, что утекает или ломается, затем выберите фикс, который senior-инженер сделал бы первым, — до того как тянуться к флагу настройки.
Цель
Отработайте цикл, который вы запускаете в каждом инциденте с секретами: прочитать артефакт, найти, где значение покидает свою границу доверия или где неверно время жизни, и взять фикс с наибольшим рычагом.
Сниппет 1 — закоммиченный .env
# config/.env — отслеживается в git, в прошлый вторник запушен в публичное зеркалоDATABASE_URL=postgres://app:Sup3rSecret@db.prod.internal:5432/mainSTRIPE_SECRET_KEY=sk_live_51HxQ...redacted...9aZJWT_SIGNING_KEY=hs256-7f3e9c1b2a8d4e6f
Викторина
Completed
Этот .env закоммитили и запушили в публичное зеркало. Вы добавляете его в .gitignore и удаляете. Какое утверждение верно?
Heads-up .gitignore лишь останавливает отслеживание будущих изменений неотслеживаемого файла; он ничего не делает с уже закоммиченным. Три секрета остаются в истории и на зеркале — их надо ротировать.
Heads-up Внутренний хостнейм — не защита: креда валидна, и атакующий, добравшийся до сети (или через другую утёкшую точку входа), её использует. Каждый закоммиченный секрет ротируется, а не сортируется по предполагаемой чувствительности.
Heads-up История append-only; коммит-удаление добавляет удаление, но прежние коммиты (и публичное зеркало) всё ещё содержат значения. Нужно переписать историю, и даже тогда экспозицию завершает ротация.
Сниппет 2 — выборка динамической креды из Vault
// Получить короткоживущую креду БД из движка database в Vault.secret, err := client.Logical().Read("database/creds/reporting-ro")if err != nil { return err}user := secret.Data["username"].(string)pass := secret.Data["password"].(string)// leaseDuration — секунды до авто-отзыва этой креды в Vaultlease := time.Duration(secret.LeaseDuration) * time.Seconddb := openDB(user, pass)defer db.Close()// ... долгоживущий воркер использует db часами ...
Викторина
Completed
Роль выпускает креду с часовым lease, но этот воркер работает часами. В чём дефект и фикс?
Heads-up Кеширование динамической креды навсегда сводит на нет её смысл: вся суть в коротком TTL, ограничивающем радиус поражения. Креду нужно продлевать или перезапрашивать, а не закреплять на время жизни процесса.
Heads-up Права задаются нуждой, а не длительностью — воркер отчётности должен оставаться read-only (least privilege). Реальный дефект — управление временем жизни: lease никогда не продлевается и истекает под воркером.
Heads-up Vault отзывает по TTL lease независимо от открытых соединений; уже установленное соединение может выжить, но новая аутентификация падает. Надо продлевать lease или перезапрашивать — открытые сокеты lease не продлевают.
Сниппет 3 — хелпер envelope encryption на KMS
def encrypt_blob(kms, key_id: str, plaintext: bytes) -> dict: # просим KMS data key: открытый для использования сейчас + обёрнутый для хранения resp = kms.generate_data_key(KeyId=key_id, KeySpec="AES_256") data_key = resp["Plaintext"] wrapped_key = resp["CiphertextBlob"] ciphertext = aes_gcm_encrypt(data_key, plaintext) # шифруем ЛОКАЛЬНО return { "ciphertext": ciphertext, "data_key": data_key, # хранится рядом с шифротекстом "wrapped_key": wrapped_key, }
Викторина
Completed
Этот хелпер делает локальное AES-GCM шифрование data key, выданным KMS, — envelope encryption. Что разрушает всю схему?
Heads-up Локальное массовое шифрование data key, выданным KMS, — это и есть смысл envelope encryption: оно масштабируется и держит plaintext вне провода. Дефект — хранение открытого data key, а не локальное шифрование.
Heads-up Переиспользование одного data key для всего расширяет радиус поражения при компрометации одного ключа; предпочтительны data keys на контекст. В любом случае фатальный баг здесь — хранение открытого data key, а не частота его выпуска.
Heads-up wrapped_key — это data key, зашифрованный под ключ KMS, который никогда не покидает KMS; его безопасно хранить, и именно его вы оставляете. Утечка — открытый data_key, лежащий рядом с шифротекстом.
Сканер помечает этот хендлер. Где секрет покидает границу доверия и в чём фикс?
Heads-up Чтение из окружения нормально и стандартно; хардкод был бы хуже (снова в git). Утечка — логирование объекта, содержащего ключ, — чините лог, а не чтение.
Heads-up Логи текут в агрегаторы, экспортируются и читаются множеством людей и инструментов; секрет в логах — известный путь утечки. Считайте любой секрет, попавший в лог, скомпрометированным.
Heads-up Уровень лога не сдерживает секрет — debug-логи всё равно пишутся, отправляются и читаются, а конфиг часто включает их в проде во время инцидентов. Уберите секрет из лога полностью.
Итог
Каждый инцидент с секретами читается в артефактах: закоммиченный .env означает, что все три значения утекли и их надо ротировать, а не gitignore-ить; динамическая креда, чей lease никогда не продлевается, истекает посреди работы — продлевайте или перезапрашивайте; envelope encryption разрушается в момент, когда вы сохраняете открытый data key рядом с шифротекстом — храните только обёрнутый ключ и распаковывайте через KMS; а объект с секретом в строке лога отправляет ваш живой ключ в каждый агрегатор, поэтому редактируйте и ротируйте. Прочтите артефакт, найдите, где значение покидает границу или где неверно время жизни, и почините границу до того, как тронуть флаг.