乐于分享
好东西不私藏

【养虾记12】OpenClaw密钥安全加固实战

【养虾记12】OpenClaw密钥安全加固实战

大家好,我是质量架构师东哥,专注软件研发过程中的质量效率改进。

讲清原理,打通实战,拒绝空谈。欢迎关注。

背景

在上一讲中我们讲解了如何制定安全规范【养虾记11】六步搞定OpenClaw安全规范,但是发现小龙虾经过我们的PUA之后还是可能存在不遵守安全规范暴露api key 的情况,本篇是上一篇的进阶,如何做到更加安全的保护API key的安全性。

原理

OpenClaw 的 openclaw.json 和 agents/*/agent/models.json 默认以明文存储 API Key,存在泄漏风险。

OpenClaw 原生支持 SecretRef 机制——所有 apiKeyappSecretclientSecrettoken 字段可用引用对象替代明文:

// 明文(不安全)
"apiKey": "your-plaintext-key"

// SecretRef(推荐)
"apiKey": {
  "source": "env",        // 来源类型:env | file | exec
  "provider": "default",  // 对应 secrets.providers 中注册的 provider 别名
  "id": "ENV_VAR_NAME"    // 环境变量名(必须匹配 ^[A-Z][A-Z0-9_]{0,127}$)
}

运行时 OpenClaw 通过 process.env[ref.id] 读取实际值。


步骤 1:创建环境变量文件

需要创建两个文件,内容相同但格式不同——这是因为交互式 shell 和 systemd 服务读取环境变量的方式不同。

1.1 交互式 shell 用(带 export

手动执行 openclaw CLI 命令时需要环境变量,通过 /etc/profile.d/*.sh 在登录时自动加载。

cat > /etc/profile.d/openclaw-secrets.sh << 'EOF'
#!/bin/bash
# OpenClaw secrets — loaded at login. Keep chmod 600.

# Model providers
export OPENCLAW_STREAMLAKE_API_KEY="xxx"
export OPENCLAW_VOLCENGINE_API_KEY="xxx"

# Gateway
export OPENCLAW_GATEWAY_TOKEN="xxx"

# Feishu accounts
export OPENCLAW_FEISHU_DEFAULT_SECRET="xxx"
export OPENCLAW_FEISHU_IP_SECRET="xxx"
export OPENCLAW_FEISHU_LOBSTER_SECRET="xxx"
export OPENCLAW_FEISHU_DEVELOPER_SECRET="xxx"
export OPENCLAW_FEISHU_DEVOPS_SECRET="xxx"
export OPENCLAW_FEISHU_IELTS_SECRET="xxx"
export OPENCLAW_FEISHU_QA_SECRET="xxx"

# DingTalk accounts
export OPENCLAW_DINGTALK_CHEF_SECRET="xxx"
export OPENCLAW_DINGTALK_WORK_SECRET="xxx"

# Tavily web search
export TAVILY_API_KEY="xxx"
EOF

chmod 600 /etc/profile.d/openclaw-secrets.sh

1.2 systemd 服务用(不带 export

踩坑警告:systemd 服务进程有独立的运行环境,不会读取 /etc/profile.d/*.sh。 如果只创建 .sh 文件而不创建 .env 文件,gateway 启动时将无法解析 SecretRef,导致服务启动失败。

systemd 的 EnvironmentFile 要求 KEY=VALUE 格式(不带 export 前缀):

cat > /etc/profile.d/openclaw-secrets.env << 'EOF'
# OpenClaw secrets for systemd service. Keep chmod 600.
OPENCLAW_STREAMLAKE_API_KEY=xxx
OPENCLAW_VOLCENGINE_API_KEY=xxx
OPENCLAW_GATEWAY_TOKEN=xxx
OPENCLAW_FEISHU_DEFAULT_SECRET=xxx
OPENCLAW_FEISHU_IP_SECRET=xxx
OPENCLAW_FEISHU_LOBSTER_SECRET=xxx
OPENCLAW_FEISHU_DEVELOPER_SECRET=xxx
OPENCLAW_FEISHU_DEVOPS_SECRET=xxx
OPENCLAW_FEISHU_IELTS_SECRET=xxx
OPENCLAW_FEISHU_QA_SECRET=xxx
OPENCLAW_DINGTALK_CHEF_SECRET=xxx
OPENCLAW_DINGTALK_WORK_SECRET=xxx
TAVILY_API_KEY=xxx
EOF

chmod 600 /etc/profile.d/openclaw-secrets.env

1.3 立即加载到当前 shell

source /etc/profile.d/openclaw-secrets.sh

步骤 2:修改 systemd 服务配置

在 ~/.config/systemd/user/openclaw-gateway.service 的 [Service] 段中,在所有 Environment= 行之前添加:

EnvironmentFile=/etc/profile.d/openclaw-secrets.env

修改后的 [Service] 段开头应形如:

[Service]
ExecStart=/usr/bin/node ...
Restart=always
...
EnvironmentFile=/etc/profile.d/openclaw-secrets.env
Environment=HOME=/root
Environment=TMPDIR=/tmp
...

添加后重新加载 systemd 配置:

systemctl --user daemon-reload

步骤 3:注册 secrets provider

在 openclaw.json 中声明一个 env 类型的 secrets provider,并指定允许读取的环境变量白名单:

openclaw config set secrets.providers.default \
  --provider-source env \
  --provider-allowlist OPENCLAW_STREAMLAKE_API_KEY \
  --provider-allowlist OPENCLAW_VOLCENGINE_API_KEY \
  --provider-allowlist OPENCLAW_GATEWAY_TOKEN \
  --provider-allowlist OPENCLAW_FEISHU_DEFAULT_SECRET \
  --provider-allowlist OPENCLAW_FEISHU_IP_SECRET \
  --provider-allowlist OPENCLAW_FEISHU_LOBSTER_SECRET \
  --provider-allowlist OPENCLAW_FEISHU_DEVELOPER_SECRET \
  --provider-allowlist OPENCLAW_FEISHU_DEVOPS_SECRET \
  --provider-allowlist OPENCLAW_FEISHU_IELTS_SECRET \
  --provider-allowlist OPENCLAW_FEISHU_QA_SECRET \
  --provider-allowlist OPENCLAW_DINGTALK_CHEF_SECRET \
  --provider-allowlist OPENCLAW_DINGTALK_WORK_SECRET \
  --provider-allowlist TAVILY_API_KEY

步骤 4:替换 openclaw.json 中的明文密钥

使用 openclaw config set 将每个密钥字段替换为 SecretRef 引用:

# 大模型 API Key
openclaw config set models.providers.custom-xxx-streamlakeapi-com.apiKey \
  --ref-source env --ref-provider default --ref-id OPENCLAW_STREAMLAKE_API_KEY

openclaw config set models.providers.custom-ark-cn-beijing-volces-com.apiKey \
  --ref-source env --ref-provider default --ref-id OPENCLAW_VOLCENGINE_API_KEY

# Gateway 认证 Token
openclaw config set gateway.auth.token \
  --ref-source env --ref-provider default --ref-id OPENCLAW_GATEWAY_TOKEN

# 飞书 appSecret(7 个账号)
openclaw config set channels.feishu.accounts.default.appSecret \
  --ref-source env --ref-provider default --ref-id OPENCLAW_FEISHU_DEFAULT_SECRET

openclaw config set channels.feishu.accounts.feishu-ip.appSecret \
  --ref-source env --ref-provider default --ref-id OPENCLAW_FEISHU_IP_SECRET

openclaw config set channels.feishu.accounts.feishu-lobster.appSecret \
  --ref-source env --ref-provider default --ref-id OPENCLAW_FEISHU_LOBSTER_SECRET

openclaw config set channels.feishu.accounts.feishu-developer.appSecret \
  --ref-source env --ref-provider default --ref-id OPENCLAW_FEISHU_DEVELOPER_SECRET

openclaw config set channels.feishu.accounts.feishu-devops.appSecret \
  --ref-source env --ref-provider default --ref-id OPENCLAW_FEISHU_DEVOPS_SECRET

openclaw config set channels.feishu.accounts.feishu-ielts.appSecret \
  --ref-source env --ref-provider default --ref-id OPENCLAW_FEISHU_IELTS_SECRET

openclaw config set channels.feishu.accounts.feishu-qa.appSecret \
  --ref-source env --ref-provider default --ref-id OPENCLAW_FEISHU_QA_SECRET

# 钉钉 clientSecret(2 个账号)
openclaw config set channels.dingtalk-connector.accounts.dingtalk-chef.clientSecret \
  --ref-source env --ref-provider default --ref-id OPENCLAW_DINGTALK_CHEF_SECRET

openclaw config set channels.dingtalk-connector.accounts.dingtalk-work.clientSecret \
  --ref-source env --ref-provider default --ref-id OPENCLAW_DINGTALK_WORK_SECRET

替换后配置文件中的字段变为:

"apiKey": {
  "source": "env",
  "provider": "default",
  "id": "OPENCLAW_STREAMLAKE_API_KEY"
}

步骤 5:替换 agents/*/agent/models.json 中的明文密钥

9 个 agent 目录下的 models.json 文件包含重复的 API Key,通过脚本批量替换:

python3 << 'PYEOF'
import json, glob

secret_ref_streamlake = {"source": "env", "provider": "default", "id": "OPENCLAW_STREAMLAKE_API_KEY"}
secret_ref_volcengine = {"source": "env", "provider": "default", "id": "OPENCLAW_VOLCENGINE_API_KEY"}

for f in sorted(glob.glob("/root/.openclaw/agents/*/agent/models.json")):
    with open(f) as fp:
        data = json.load(fp)
    changed = False
    for name, provider in data.get("providers", {}).items():
        if "apiKey" not in provider or not isinstance(provider["apiKey"], str):
            continue
        # 跳过本地占位 key(如 "ollama-local")
        if provider["apiKey"] in ("ollama-local",):
            continue
        # 根据 provider 名称匹配对应的 SecretRef
        if "streamlake" in name:
            provider["apiKey"] = secret_ref_streamlake
            changed = True
        elif "ark" in name or "volces" in name:
            provider["apiKey"] = secret_ref_volcengine
            changed = True
    if changed:
        with open(f, "w") as fp:
            json.dump(data, fp, indent=2, ensure_ascii=False)
            fp.write("\n")
        print(f"Updated: {f}")
    else:
        print(f"Skipped: {f}")
PYEOF

涉及的文件(共 9 个):

agents/main/agent/models.json
agents/ip/agent/models.json
agents/lobster/agent/models.json
agents/developer/agent/models.json
agents/devops/agent/models.json
agents/ielts/agent/models.json
agents/qa/agent/models.json
agents/chef/agent/models.json
agents/work/agent/models.json

步骤 6:清理 .env 文件

从 ~/.openclaw/.env 中移除已转移到系统环境变量的 TAVILY_API_KEY

保留 DINGTALK_MCP_* URL(MCP 网关签发的 URL,key 嵌入在 URL 中无法拆分,且 .env 已被 .gitignore 排除)。

# 编辑 .env,删除 TAVILY_API_KEY 行
sed -i '/^TAVILY_API_KEY=/d' ~/.openclaw/.env

步骤 7:删除备份文件

openclaw config set 会自动创建 openclaw.json.bak(含旧明文),需手动删除:

rm -f ~/.openclaw/openclaw.json.bak

.gitignore 中的 *.bak 规则可防止备份文件被意外提交。


步骤 8:重建 git 仓库

如果旧 git 历史中包含明文密钥,且不需要保留历史记录,直接重新初始化:

cd ~/.openclaw
rm -rf .git
git init
git add .
git commit -m "feat: 初始化(密钥已外部化到环境变量)"

步骤 9:验证

# 1. 验证配置文件合法性
openclaw config validate
# 预期输出: Config valid: ~/.openclaw/openclaw.json

# 2. 确认配置文件和 git 历史中无明文密钥残留
grep -rn "your-actual-key-value" ~/.openclaw/ --exclude-dir=.git
git grep "your-actual-key-value"
# 预期: 两条命令均无输出

# 3. 重启 gateway 并检查状态
systemctl --user restart openclaw-gateway.service
systemctl --user status openclaw-gateway.service
# 预期: Active: active (running)

# 4. 健康检查
curl -s http://127.0.0.1:18789/health
# 预期输出: {"ok":true,"status":"live"}

最终文件布局

/etc/profile.d/
├── openclaw-secrets.sh    # 交互式 shell 环境变量(chmod 600)
└── openclaw-secrets.env   # systemd EnvironmentFile 格式(chmod 600)

~/.config/systemd/user/
└── openclaw-gateway.service   # 添加了 EnvironmentFile 指令

~/.openclaw/
├── openclaw.json              # 所有密钥字段改为 SecretRef 对象
├── .env                       # 仅保留 DINGTALK_MCP_* URL(gitignored)
└── agents/*/agent/models.json # apiKey 改为 SecretRef 对象

环境变量与用途对照表


日常维护

新增密钥

  1. 在 /etc/profile.d/openclaw-secrets.sh 和 .env 中添加新变量

  2. openclaw config set secrets.providers.default --provider-allowlist NEW_VAR ...(需带上所有已有的 allowlist 项)

  3. openclaw config set <config.path> --ref-source env --ref-provider default --ref-id NEW_VAR

  4. systemctl --user daemon-reload && systemctl --user restart openclaw-gateway.service

轮换密钥

  1. 在对应平台生成新密钥

  2. 更新 /etc/profile.d/openclaw-secrets.sh 和 .env 中的值

  3. source /etc/profile.d/openclaw-secrets.sh

  4. systemctl --user restart openclaw-gateway.service

  5. 配置文件无需任何改动

如果你觉得这篇文章写的还不错,麻烦双击屏幕点个👍、点个❤️、点个转发,你的支持是我继续分享的动力!

欢迎志同道合的朋友添加我的微信 Miller_Shan 一起交流,互相学习,共同进步。