安全处理机密信息与保持环境整洁
使用标准输入、文件和清理后的环境,避免凭据出现在进程列表和日志中。
安全处理机密信息与保持环境整洁 是 CoddyKit 上的免费 Linux Command Line & Bash Scripting Mastery 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Linux Command Line & Bash Scripting Mastery 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。
为什么密钥管理卫生很重要
密钥、密码和令牌是任何系统中最敏感的数据。在 Bash 脚本中不当处理它们,是最常见且危害最大的安全错误之一。
- 进程列表:传递给命令的参数会出现在
ps aux、/proc/<pid>/cmdline和系统审计日志中,主机上的所有用户都可以看到。 - Shell 历史记录:交互式输入的命令(有时也包括脚本中的命令)会记录在
~/.bash_history中。 - 日志文件:
set -x的跟踪信息、应用程序日志以及持续集成/持续交付输出都可能捕获变量值。 - 环境泄露:子进程会继承父进程的完整环境,其中包括已导出的密钥。
加固后的脚本会像对待放射性物质一样对待密钥——缩短暴露时间、限制暴露面,并在输出前对所有内容进行清理。
进程列表攻击面
当您将密钥作为命令行参数传递时,系统上的每个用户都可以立即通过 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看到它。 - 使用完毕后立即取消设置该变量,以缩短暴露窗口。
- 避免使用
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 系统上是由 RAM 支持的 tmpfs,并且只会持续到重启)。 - 除非拥有非常可靠的
.gitignore,否则绝不要将机密文件放在由 git 跟踪的目录中。
#!/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 支持或由容器注入的机密。 - 通过在
EXIT上设置trap来清理临时 netrc 文件。
#!/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-factor 应用、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)对于调试非常有价值,但它会将展开的每个变量的值打印到标准错误中,其中也包括机密。这些跟踪信息经常会进入 CI 日志或系统日志。
在保留跟踪功能的同时保护机密的策略:
- 使用
{ 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_TOKENtmpfs 上的临时机密文件
tmpfs 是由 RAM 支持的文件系统。写入其中的文件永远不会刷新到磁盘,从而消除了机密残留在交换空间、磁盘缓存或快照中的风险。
- 在 Linux 上,
/dev/shm和/run/user/<uid>通常是 tmpfs 挂载点。 - 使用 tmpfs 时,始终配合
trap EXIT,以便在脚本结束时删除文件。 - 在容器(Docker、Kubernetes)中,可以将机密直接作为 tmpfs 卷挂载到
/run/secrets。
#!/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交互式获取机密,避免终端回显或 shell 历史记录暴露。 - 文件权限:机密文件必须使用
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中,使其永不接触磁盘;退出时彻底删除。
纵深防御是关键思维方式:单项措施都不足以确保安全,但将它们分层结合起来,会让机密泄露极其困难。
常见问题解答
「安全处理机密信息与保持环境整洁」课时是免费的吗?
是的 — 「安全处理机密信息与保持环境整洁」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Linux Command Line & Bash Scripting Mastery 课程的其余内容,请升级到 CoddyKit PRO。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。
「安全处理机密信息与保持环境整洁」这节课中我会学到什么?
使用标准输入、文件和清理后的环境,避免凭据出现在进程列表和日志中。 你通过在浏览器中直接运行的动手代码来练习 Linux Command Line & Bash Scripting Mastery,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Linux Command Line & Bash Scripting Mastery 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Linux Command Line & Bash Scripting Mastery 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「安全处理机密信息与保持环境整洁」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Linux Command Line & Bash Scripting Mastery 课中编写并运行代码吗?
能。每节 Linux Command Line & Bash Scripting Mastery 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 防止命令与参数注入
- 安全处理机密信息与保持环境整洁
- 最小权限执行与 sudo 规范
- 使用 ShellCheck 进行静态分析与审计