0Pricing
DevOps Bootcamp · 课时

安全处理机密信息与保持环境整洁

使用标准输入、文件和清理后的环境,避免凭据出现在进程列表和日志中。

安全处理机密信息与保持环境整洁 是 CoddyKit 上的免费 DevOps Bootcamp 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 DevOps Bootcamp 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 DevOps Bootcamp 课程共包含 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_TOKEN

tmpfs 上的临时机密文件

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 导师)并解锁 DevOps Bootcamp 课程的其余内容,请升级到 CoddyKit PRO。 DevOps Bootcamp 课程共包含 4 节课。

「安全处理机密信息与保持环境整洁」这节课中我会学到什么?

使用标准输入、文件和清理后的环境,避免凭据出现在进程列表和日志中。 你通过在浏览器中直接运行的动手代码来练习 DevOps Bootcamp,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 DevOps Bootcamp 需要有经验吗?

无需任何先前经验。CoddyKit 上的 DevOps Bootcamp 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「安全处理机密信息与保持环境整洁」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 DevOps Bootcamp 课中编写并运行代码吗?

能。每节 DevOps Bootcamp 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 防止命令与参数注入
  2. 安全处理机密信息与保持环境整洁
  3. 最小权限执行与 sudo 规范
  4. 使用 ShellCheck 进行静态分析与审计
← 返回 DevOps Bootcamp