API Key 安全注入范式

是kimi主笔的~ 学自家的项目学到了api key注入的正确方式,觉得很有用啊,于是让kimi写了存档~

API Key 安全注入范式:从「写死代码」到「零信任部署」

基于 GitHub Actions → 云服务器 的实战经验,提炼一套通用、可复现的 API Key 全生命周期管理方案。


一、核心原则:三条不可妥协的底线

原则 含义 反面教材
不进仓库 任何密钥不得出现在 Git 历史、配置文件或构建产物中 api.key=sk-xxx 提交到 application.properties
不进构建产物 密钥不应被打包进 jar/docker image,避免镜像泄露即密钥泄露 在 Dockerfile 里 ENV API_KEY=xxx
只在运行时注入 密钥仅在进程启动时通过环境变量/挂载文件进入内存 把密钥写在 systemd 的 ExecStart 命令行参数里

二、通用架构:四层漏斗模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌─────────────────────────────────────────┐
│ Layer 1: 密钥托管平台 │ ← GitHub Secrets / AWS Secrets Manager / 1Password
│ (COZE_API_TOKEN / OPENAI_API_KEY ...) │
└─────────────────┬───────────────────────┘
│ CI/CD 流水线注入

┌─────────────────────────────────────────┐
│ Layer 2: 服务器密钥文件 │ ← /opt/app/config/api.env (600权限)
│ (仅 root + 服务用户可读) │
└─────────────────┬───────────────────────┘
│ systemd / docker-compose 加载

┌─────────────────────────────────────────┐
│ Layer 3: 进程运行时环境变量 │ ← Spring Boot / Node / Python 读取
│ (内存中,不持久化) │
└─────────────────┬───────────────────────┘
│ 占位符解析

┌─────────────────────────────────────────┐
│ Layer 4: 应用层配置 │ ← ${API_KEY:} 占位符,空值优雅降级
│ (代码中无明文,无默认值) │
└─────────────────────────────────────────┘

三、逐层落地:从代码到服务器的完整配置

Layer 4:代码层 —— 占位符 + 本地覆盖

配置文件resources/api.properties):

1
2
3
4
5
6
7
8
9
# 明文配置可以写,密钥必须占位
ai.provider.url=https://api.example.com/v1
ai.provider.model=gpt-4

# 占位符:从环境变量读取,无默认值
ai.provider.key=${AI_API_KEY:}

# 可选:开关控制,无密钥时自动禁用
ai.enabled=${AI_API_KEY:}

主配置导入application.properties):

1
spring.config.import=optional:classpath:api.properties,optional:classpath:api-local.properties

本地开发覆盖api-local.properties,已加入 .gitignore):

1
ai.provider.key=sk-local-test-key

.gitignore 关键行

1
2
*-local.properties
.env

Layer 3:应用层 —— 优雅降级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class AiService {

@Value("${ai.provider.key:}")
private String apiKey;

@Value("${ai.provider.url}")
private String apiUrl;

public boolean isEnabled() {
return StringUtils.hasText(apiKey);
}

public AnalysisResult analyze(String code) {
if (!isEnabled()) {
// 返回降级提示,而非抛异常导致 500
return AnalysisResult.disabled("AI 分析未启用");
}
// 正常调用 API...
}
}

关键设计:无密钥时服务不崩溃,前端显示「AI 未配置」即可。


Layer 2:服务器层 —— 文件权限最小化

创建密钥文件:

1
2
3
4
5
6
7
8
sudo mkdir -p /opt/myapp/config
sudo tee /opt/myapp/config/api.env << 'EOF'
AI_API_KEY=sk-prod-xxxxxxxx
EOF

# 核心:权限最小化
sudo chmod 600 /opt/myapp/config/api.env
sudo chown root:root /opt/myapp/config/api.env

为什么用文件而非直接 export

  • 环境变量在 ps e/proc/$pid/environ 中可被其他用户读取
  • 文件配合 systemd 的 EnvironmentFile 加载,权限控制更细

Layer 1:CI/CD 层 —— GitHub Actions 注入

GitHub Secrets 设置

1
2
3
Settings → Secrets and variables → Actions → New repository secret
Name: AI_API_KEY
Value: sk-prod-xxxxxxxx

deploy.yml 关键片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- name: Deploy & Inject Secrets
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
# 写入密钥(注意:这里用 HERE DOCUMENT 避免 shell 历史记录)
sudo mkdir -p /opt/myapp/config
sudo tee /opt/myapp/config/api.env << 'EOF'
AI_API_KEY=${{ secrets.AI_API_KEY }}
EOF

sudo chmod 600 /opt/myapp/config/api.env
sudo systemctl daemon-reload
sudo systemctl restart myapp

安全细节

  • 使用 << 'EOF'(单引号)防止变量提前展开
  • GitHub Actions 日志会自动 mask secrets.* 的值,即使 set -x 也不会泄露

Layer 0:systemd 服务配置

/etc/systemd/system/myapp.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[Unit]
Description=MyApp Backend
After=network.target

[Service]
Type=simple
User=appuser
Group=appgroup

# 从文件加载环境变量
EnvironmentFile=/opt/myapp/config/api.env

# 工作目录
WorkingDirectory=/opt/myapp

# 启动命令(命令行中不出现任何密钥)
ExecStart=/usr/bin/java -jar /opt/myapp/myapp.jar

# 安全加固
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true

[Install]
WantedBy=multi-user.target

应用:

1
2
sudo systemctl daemon-reload
sudo systemctl restart myapp

五、扩展场景

Docker / Docker Compose

1
2
3
4
5
6
7
# docker-compose.yml
services:
app:
image: myapp:latest
env_file:
- /opt/myapp/config/api.env # 宿主机密钥文件挂载为环境变量
# 不要写:environment: API_KEY=xxx

多环境管理

环境 密钥来源 文件路径
本地 api-local.properties / export resources/
测试 GitHub Secrets (AI_API_KEY_STAGING) /opt/myapp-staging/config/api.env
生产 GitHub Secrets (AI_API_KEY_PROD) /opt/myapp/config/api.env

Commentarii · 评论