Docs/ 安全/ 密钥管理

密钥管理

使用 AES-256-GCM 加密、作用域访问和零停机轮换来存储和管理敏感配置值。

sh0 如何加密密钥

所有密钥(标记为敏感的环境变量)在 sh0 的 SQLite 数据库中使用 AES-256-GCM 认证加密进行静态加密,由 ring 加密库提供支持。

加密工作流程:

  1. 在 sh0 安装期间生成唯一的 256 位加密密钥,存储在服务器的配置目录中。
  2. 每个密钥值使用唯一的随机数(96 位随机数)加密,确保相同的值产生不同的密文。
  3. 认证标签(GCM)确保密钥不能在未被检测到的情况下被篡改。
  4. 密钥仅在启动时注入容器时在内存中解密。
Note
变量值在静态存储时加密,且绝不会写入日志。列表中显示为掩码;拥有 Developer 及以上角色的成员可以在控制台中显示明文,或通过 API 使用 ?reveal=true 获取。只读 API 密钥同样可以:请只把密钥和角色交给您愿意托付机密的人员和工具。

通过控制台添加密钥

管理密钥最常见的方式是通过控制台的环境变量面板。

  1. 导航到应用并打开 设置 → 环境变量。
  2. 点击 添加变量。
  3. 输入变量名称(例如 DATABASE_URL)。
  4. 输入值。
  5. 每个值在存储前都会加密,无需任何开关。
  6. 点击 保存。
环境变量面板,包含添加变量表单

通过 API:

Terminal
curl -X POST https://panel.example.com/api/v1/apps/APP_ID/env-vars \
  -H "Authorization: Bearer $SH0_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "DATABASE_URL",
    "value": "postgres://user:pass@db:5432/mydb"
  }'
Tip
环境变量的修改会在下一次部署时生效。保存后请重新部署应用。

从 .env 文件批量导入

如果你有现有的 .env 文件,可以一次性导入所有变量。

  1. 在环境变量面板中,点击 批量导入。
  2. 粘贴 .env 文件的内容或直接上传文件。
  3. sh0 解析文件并显示所有变量的预览。
  4. 审阅,将敏感值标记为密钥,然后点击 导入。
批量导入对话框,显示解析后的 .env 文件内容

解析器支持标准 .env 语法:

.env
# Database
DATABASE_URL=postgres://user:pass@db:5432/mydb
REDIS_URL=redis://redis:6379

# API Keys
STRIPE_SECRET_KEY=sk_live_abc123
SENDGRID_API_KEY=SG.xyz789

# App Config
NODE_ENV=production
PORT=3000
Warning
批量导入会覆盖同名的现有变量。导入文件中不存在的变量保持不变。

密钥作用域

密钥可以在两个级别设置作用域,让你控制哪些容器可以访问哪些值。

堆栈级密钥

堆栈级密钥在堆栈中的所有服务间共享。这对于多个服务需要的共享配置(如数据库凭据)非常理想。

堆栈级环境变量,在所有服务间共享

从 堆栈设置 → 环境变量 或通过 API 设置堆栈级密钥:

Terminal
curl -X POST https://panel.example.com/api/v1/projects/PROJECT_ID/env-vars \
  -H "Authorization: Bearer $SH0_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "DATABASE_URL",
    "value": "postgres://user:pass@db:5432/mydb"
  }'

应用级密钥

应用级密钥针对单个服务。它们优先于同名的堆栈级密钥,允许你为个别服务覆盖共享值。

Note
优先级顺序:应用级变量覆盖堆栈级变量。如果两者都定义了 DATABASE_URL,则该特定服务使用应用级的值。

密钥轮换

轮换机密意味着更新其值,并重新部署使用它的应用:

  1. 在控制台中或通过 API 更新该值。
  2. 重新部署应用:新容器会以新值启动。
  3. 旧容器会继续处理流量,直到新容器通过健康检查。
  4. 随后流量切换到新容器。
Terminal
curl -X PATCH https://panel.example.com/api/v1/apps/APP_ID/env-vars/ENV_ID \
  -H "Authorization: Bearer $SH0_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "value": "sk_live_new_key_456"
  }'

curl -X POST https://panel.example.com/api/v1/apps/APP_ID/deployments \
  -H "Authorization: Bearer $SH0_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
密钥轮换,蓝绿部署进行中
Tip
对于数据库密码轮换,先在数据库中更新密码,然后在 sh0 中更新密钥并重新部署。这个顺序可以防止过渡期间的连接失败。

在容器中访问密钥

密钥作为标准环境变量注入容器。你的应用访问它们的方式与访问任何环境变量相同:

Node.js
const dbUrl = process.env.DATABASE_URL;
const stripeKey = process.env.STRIPE_SECRET_KEY;
Python
import os
db_url = os.environ["DATABASE_URL"]
stripe_key = os.environ["STRIPE_SECRET_KEY"]
Rust
let db_url = std::env::var("DATABASE_URL")?;
let stripe_key = std::env::var("STRIPE_SECRET_KEY")?;

密钥在容器启动时设置,并在容器的整个生命周期内可用。它们不会写入容器内的磁盘。

Danger
永远不要记录环境变量或将其包含在错误报告中。虽然 sh0 在静态和传输中保护密钥,但你的应用代码也必须负责任地处理它们。