Безопасная работа с секретами и чистота окружения
Не допускайте попадания учётных данных в списки процессов и журналы, используя stdin, файлы и очищенное окружение
«Безопасная работа с секретами и чистота окружения» — бесплатный урок DevOps Bootcamp на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения DevOps Bootcamp, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс DevOps Bootcamp содержит 4 уроков всего.
Почему важна гигиена секретов
Секреты — ключи API, пароли и токены — самые чувствительные данные в любой системе. Неправильное обращение с ними в скриптах Bash — одна из наиболее распространённых и опасных ошибок безопасности.
- Списки процессов: аргументы, переданные командам, отображаются в
ps aux,/proc/<pid>/cmdlineи системных журналах аудита — их видят все пользователи узла. - История оболочки: команды, введённые интерактивно (а иногда и скрипты), записываются в
~/.bash_history. - Файлы журналов: трассировки
set -x, журналы приложений и вывод CI/CD могут содержать значения переменных. - Утечка через окружение: дочерние процессы наследуют всё окружение родительского процесса, включая экспортированные секреты.
В усиленном скрипте с секретами обращаются как с радиоактивным материалом: сводят время раскрытия к минимуму, ограничивают область воздействия и очищают всё перед выводом.
Поверхность атаки через список процессов
Когда Вы передаёте секрет как аргумент командной строки, любой пользователь системы может немедленно прочитать его с помощью ps. Это не теоретическая угроза: ею регулярно пользуются в средах общего хостинга и контейнерах.
Приведённый ниже фрагмент демонстрирует проблему и её исправление рядом.
#!/usr/bin/env bash
# DANGEROUS: password visible in 'ps aux' output
# curl -u "admin:SuperSecret123" https://api.example.com/data
# SAFE: pass credentials via stdin or a flag that reads from a file
# Many tools support reading secrets from stdin with '-' or dedicated flags:
# Option 1 — pipe the secret so it never appears in argv
echo 'SuperSecret123' | curl -u 'admin' --password-stdin \
https://api.example.com/data 2>/dev/null || true
# Option 2 — write a temporary netrc and point curl at it
# (covered in a later scene)
echo 'Secret never touches the command line this way'Чтение секретов из стандартного ввода
Самый безопасный интерактивный способ — считывать секрет во время выполнения с помощью read -rs. Флаг -s отключает отображение вводимых символов, а -r запрещает интерпретацию обратной косой черты.
Основные моменты:
- Переменная никогда не экспортируется, поэтому дочерние процессы не могут увидеть её через
/proc/<pid>/environ. - После использования немедленно выполните unset для переменной, чтобы сократить время её раскрытия.
- Избегайте
echo "$SECRET"— используйтеprintf '%s', чтобы завершающий перевод строки не испортил значение и чтобы оно оставалось невидимым в трассировках.
#!/usr/bin/env bash
set -euo pipefail
# Prompt on stderr so stdout stays clean for piping
read -rsp 'Enter API token: ' API_TOKEN <&2
printf '\n' >&2
# Use the secret — printf keeps it out of argv
response=$(printf '%s' "$API_TOKEN" | curl -sS -X POST \
-H 'Content-Type: application/json' \
--data-binary @- \
https://httpbin.org/post 2>/dev/null) || true
echo "Request sent."
# Scrub immediately — unset removes it from shell memory
unset API_TOKENСекреты в файлах: права доступа и владение
Если секрет должен сохраняться на диске (например, ключ служебной учётной записи), права доступа к файлу — ваша главная защита.
- Режим 0600 — только владелец может читать и изменять файл. Доступ для группы и остальных пользователей отсутствует.
- Режим 0400 — владелец может только читать файл. Предпочтительно использовать этот режим для ключей, которые ни при каких обстоятельствах нельзя случайно перезаписать.
- Храните файлы с секретами в специально предназначенном каталоге, например
~/.secrets/или/run/secrets/(последний во многих системах Linux представляет собой tmpfs, размещённый в RAM, и существует только до перезагрузки). - Никогда не помещайте файлы с секретами в каталог, отслеживаемый git, если у вас нет абсолютно надёжного файла
.gitignore.
#!/usr/bin/env bash
set -euo pipefail
SECRETS_DIR="${HOME}/.secrets"
mkdir -p "$SECRETS_DIR"
chmod 700 "$SECRETS_DIR" # directory: only owner can list contents
KEY_FILE="${SECRETS_DIR}/api_token"
# Write secret — atomically restrict permissions before writing content
install -m 0600 /dev/null "$KEY_FILE"
printf '%s' 'my-super-secret-token' > "$KEY_FILE"
echo "Permissions:"
ls -la "$KEY_FILE"
# Read back safely — no subshell, no echo
API_TOKEN=$(< "$KEY_FILE")
echo "Token length: ${#API_TOKEN} chars (value not printed)"
unset API_TOKENИспользование файла .netrc с curl
curl поддерживает файл ~/.netrc (или произвольный путь, указанный через --netrc-file), в котором имена узлов сопоставляются с учётными данными. Благодаря этому данные аутентификации полностью исключаются из командной строки и тела скрипта.
Формат файла прост:
machine api.example.com
login admin
password s3cr3tРекомендации:
- Всегда устанавливайте права
chmod 0600 ~/.netrc— в некоторых системах curl отказывается использовать файл, доступный для чтения всем пользователям. - Используйте
--netrc-file /run/secrets/netrc, чтобы указать на секрет в tmpfs или секрет, переданный контейнеру. - Удаляйте временные файлы netrc с помощью
trapпри наступлении EXIT.
#!/usr/bin/env bash
set -euo pipefail
TMP_NETRC=$(mktemp)
chmod 0600 "$TMP_NETRC"
# Trap ensures cleanup even on error or signal
trap 'rm -f "$TMP_NETRC"' EXIT
# Write credentials to the temp netrc
cat > "$TMP_NETRC" <<'EOF'
machine httpbin.org
login myuser
password mypassword
EOF
curl -fsS --netrc-file "$TMP_NETRC" \
https://httpbin.org/basic-auth/myuser/mypassword \
-o /dev/null -w 'HTTP %{http_code}\n' || true
# trap fires here: $TMP_NETRC is deleted
echo 'Temp netrc cleaned up by trap.'Безопасная работа с переменными окружения
Переменные окружения — популярный способ передавать секреты в скрипты (приложения по методологии 12 факторов, конвейеры CI/CD). Однако они передаются каждому дочернему процессу и отображаются в /proc/<pid>/environ на протяжении всего времени жизни процесса.
Защитные приёмы:
- Сразу сохраните секрет в локальной переменной и снимите переменную окружения, чтобы дочерние процессы не могли её унаследовать.
- Передавайте секреты только нужным командам с помощью
env -iили встроенного присваивания, а не через полностью унаследованное окружение. - Никогда не используйте
exportдля переменной с секретом — по возможности применяйте только присваивание (безexport).
#!/usr/bin/env bash
set -euo pipefail
# Simulate a secret arriving via environment (e.g., from CI system)
export DB_PASSWORD='hunter2' # set by CI — we did not choose this
# Capture locally, then strip from environment immediately
db_password="$DB_PASSWORD"
unset DB_PASSWORD
# Verify the env var is gone before spawning any child process
if printenv DB_PASSWORD 2>/dev/null; then
echo 'ERROR: DB_PASSWORD still in environment!' >&2
exit 1
fi
echo 'Secret captured and env var scrubbed.'
echo "Password length: ${#db_password}"
unset db_passwordКак не допустить появления секретов в трассировке set -x
set -x (трассировка xtrace) незаменима при отладке, но выводит в stderr значение каждой раскрываемой переменной — в том числе секретов. Такие трассировки часто оказываются в журналах CI или syslog.
Стратегии защиты секретов при сохранении полезной трассировки:
- Временно отключайте трассировку вокруг чувствительных операций с помощью
{ set +x; } 2>/dev/null. - После этого включайте её снова с помощью
set -x. - Перенаправляйте вывод xtrace в отдельный файловый дескриптор, связанный с защищённым файлом журнала, а не с общедоступным потоком журналирования.
#!/usr/bin/env bash
set -euo pipefail
set -x # tracing ON — safe for non-sensitive sections
echo 'Building application...'
SRC_DIR='/tmp/build'
mkdir -p "$SRC_DIR"
# Disable xtrace around secret handling (suppress the set +x line itself)
{ set +x; } 2>/dev/null
read -rsp 'Token (hidden from trace): ' SECRET_TOKEN <&2
printf '\n' >&2
token_len=${#SECRET_TOKEN}
unset SECRET_TOKEN
set -x # tracing back ON
echo "Token captured (length=$token_len). Continuing build..."
ls "$SRC_DIR"Удаление секретов из файлов журналов
Даже при осторожной работе секреты иногда попадают в журналы — особенно в подробных или устаревших скриптах. Функция-обёртка для журналирования, которая маскирует известные шаблоны, создаёт дополнительный уровень защиты.
В этом приёме для всех записей журнала используется замена на основе регулярных выражений. Это защита на крайний случай, а не замена описанным ранее правилам безопасной работы.
#!/usr/bin/env bash
set -euo pipefail
# A logging function that scrubs common secret patterns before writing
log() {
local line
# Replace anything that looks like key=VALUE or password=VALUE
line=$(printf '%s\n' "$*" \
| sed -E 's/(password|token|secret|key)=[^[:space:]]*/\1=***REDACTED***/gi')
printf '[%s] %s\n' "$(date -u '+%T')" "$line"
}
# Usage:
log 'Connecting to database with password=hunter2'
log 'Loaded API token=sk-abc123xyz secret'
log 'Build step completed successfully' # unchangedИзолированные окружения с env -i
env -i запускает команду с полностью пустым окружением, не позволяя унаследованным переменным — включая случайно переданные секреты — попасть в дочерний процесс. Затем вы явно передаёте только необходимое.
Это особенно полезно при запуске ненадёжных скриптов, средств сборки или сторонних утилит, которые могут похитить данные окружения.
#!/usr/bin/env bash
set -euo pipefail
# Polluted parent environment (simulating a CI runner)
export AWS_SECRET_ACCESS_KEY='AKIAIOSFODNN7EXAMPLE'
export GITHUB_TOKEN='ghp_faketoken123'
export HOME="$HOME"
export PATH="$PATH"
echo '--- Child sees full environment:'
env | grep -E 'AWS|GITHUB' | head -5
echo '--- Sanitised child (env -i) sees nothing secret:'
env -i HOME="$HOME" PATH="$PATH" TERM="${TERM:-dumb}" \
bash -c 'env | grep -E "AWS|GITHUB" || echo "No secrets visible"'
unset AWS_SECRET_ACCESS_KEY GITHUB_TOKENВременные файлы с секретами в tmpfs
tmpfs — это файловая система, размещённая в RAM. Записанные туда файлы никогда не сбрасываются на диск, благодаря чему исключается риск сохранения секретов в разделе подкачки, дисковом кэше или снимках.
- В Linux
/dev/shmи/run/user/<uid>обычно являются точками монтирования tmpfs. - Всегда сочетайте использование tmpfs с
trap EXIT, чтобы удалять файлы после завершения скрипта. - В контейнерах (Docker, Kubernetes) секреты можно напрямую монтировать в
/run/secretsкак тома tmpfs.
#!/usr/bin/env bash
set -euo pipefail
# Prefer /run/user/$UID (user-owned tmpfs) or /dev/shm (world-readable dir!)
if [[ -d "/run/user/$UID" ]]; then
TMPFS_DIR="/run/user/$UID"
elif [[ -d '/dev/shm' ]]; then
TMPFS_DIR='/dev/shm'
else
# Fallback: warn that disk will be used
echo 'WARNING: No tmpfs available; using /tmp (disk-backed)' >&2
TMPFS_DIR='/tmp'
fi
SECRET_FILE=$(mktemp "${TMPFS_DIR}/secret.XXXXXX")
chmod 0600 "$SECRET_FILE"
trap 'shred -u "$SECRET_FILE" 2>/dev/null || rm -f "$SECRET_FILE"' EXIT
printf '%s' 'my-runtime-token' > "$SECRET_FILE"
echo "Secret stored in: $SECRET_FILE"
df -T "$SECRET_FILE" | awk 'NR==2 {print "Filesystem type:", $2}'
# Use the secret...
token=$(< "$SECRET_FILE")
echo "Token length: ${#token}"
unset token
# trap fires on exit: file shreddedСобираем всё вместе: усиленный скрипт развёртывания
Следующий скрипт объединяет все приёмы из этого урока в реалистичный вспомогательный скрипт развёртывания. Обратите внимание, как каждый уровень защиты усиливает остальные:
- Чтение из stdin с
-s— без отображения ввода в терминале - Файл с секретом в tmpfs с очисткой через
trap - Очистка окружения — секрет удаляется до запуска любого дочернего процесса
- Защита от xtrace — трассировка приостанавливается вокруг чувствительного кода
- Маскирование в журнале — регулярное выражение-защита применяется перед записью в журнал
#!/usr/bin/env bash
set -euo pipefail
### 1. Redacting logger
log() {
local msg
msg=$(printf '%s' "$*" \
| sed -E 's/(password|token|secret|key)=[^[:space:]]*/\1=***/gi')
printf '[%s] %s\n' "$(date -u +%T)" "$msg"
}
### 2. tmpfs secret store
TMPFS_DIR="${XDG_RUNTIME_DIR:-/tmp}"
SECRET_FILE=$(mktemp "${TMPFS_DIR}/deploy_token.XXXXXX")
chmod 0600 "$SECRET_FILE"
trap 'rm -f "$SECRET_FILE"; log "Secret file cleaned up."' EXIT
### 3. Read secret without trace
{ set +x; } 2>/dev/null
read -rsp 'Deploy token: ' _tok <&2; printf '\n' >&2
printf '%s' "$_tok" > "$SECRET_FILE"
unset _tok
set -x
### 4. Scrub inherited env vars before subprocess
unset DEPLOY_TOKEN 2>/dev/null || true
log 'Starting deployment...'
# Simulate deploy using secret from file (token never in argv)
# curl -H "Authorization: Bearer $(< $SECRET_FILE)" https://api.example.com/deploy
log 'Deployment complete. token=hidden_by_redactor'
echo 'Done.'Проверка знаний: раскрытие секретов через список процессов
Проверьте, насколько хорошо вы понимаете, как секреты раскрываются через списки процессов и как этого не допустить.
Итоги урока: безопасная работа с секретами
Вы завершили урок Безопасная работа с секретами и окружением. Ниже приведена краткая памятка обо всём изученном:
- Списки процессов: Никогда не передавайте секреты как аргументы командной строки — они отображаются в
ps auxи/proc/<pid>/cmdline. Вместо этого используйте передачу через stdin или--netrc-file. - Чтение из stdin: Используйте
read -rs, чтобы интерактивно получать секреты без отображения в терминале и без попадания в историю оболочки. - Права доступа к файлам: Для файлов с секретами необходимы права
chmod 0600(или0400). Для атомарного создания используйтеinstall -m 0600. - Файлы netrc: Передавайте учётные данные во временный файл, указанный через
--netrc-file; удаляйте его с помощьюtrap EXIT. - Безопасная работа с окружением: Сразу после локального сохранения секрета используйте
unsetдля переменных окружения с секретами; никогда не применяйтеexportбез необходимости; для изоляции дочерних процессов используйтеenv -i. - Защита от xtrace: Оборачивайте чувствительный код в
{ set +x; } 2>/dev/null ... set -x, чтобы значения не попадали в отладочные трассировки. - Маскирование в журналах: Используйте журналирующую функцию на основе
sedкак защиту на крайний случай. - tmpfs: Храните секреты, необходимые во время работы, в
/run/user/$UIDили/dev/shm, чтобы они никогда не записывались на диск; удаляйте их при завершении.
Главный принцип — эшелонированная защита: ни одной меры недостаточно, но их совместное применение значительно усложняет утечку секретов.
Часто задаваемые вопросы
Урок «Безопасная работа с секретами и чистота окружения» бесплатный?
Да — полный текст урока «Безопасная работа с секретами и чистота окружения» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс DevOps Bootcamp, подпишись на CoddyKit PRO. Курс DevOps Bootcamp содержит 4 уроков всего.
Чему я научусь в уроке «Безопасная работа с секретами и чистота окружения»?
Не допускайте попадания учётных данных в списки процессов и журналы, используя stdin, файлы и очищенное окружение Ты практикуешь DevOps Bootcamp с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать DevOps Bootcamp?
Предыдущий опыт не требуется. DevOps Bootcamp на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Безопасная работа с секретами и чистота окружения»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке DevOps Bootcamp?
Да. Каждый урок DevOps Bootcamp включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Предотвращение внедрения команд и аргументов
- Безопасная работа с секретами и чистота окружения
- Выполнение с минимальными привилегиями и дисциплина sudo
- Статический анализ и аудит с ShellCheck