凭证保险库
Open Connector 如何加密存储 OAuth Token 和 API Key。
凭证保险库是 Open Connector 的核心安全功能。所有用户 OAuth Token、Refresh Token 和 API Key 都经过加密后存储在您的 Postgres 数据库中。没有加密密钥,即使直接访问数据库也无法读取任何凭证。
加密方案
Open Connector 使用 AES-256-GCM 进行对称认证加密。32 字节的加密密钥通过 HKDF-SHA-256 从主密钥派生,使用固定的域分隔标签,主密钥本身从不直接用作加密密钥。
密钥派生
主密钥即 CONNECTOR_ENCRYPTION_KEY 环境变量(至少 32 个字符)。服务器启动时,通过 HKDF-SHA-256 从中派生出 32 字节的 AES 密钥:
master_secret = CONNECTOR_ENCRYPTION_KEY // ≥ 32 个字符
key = HKDF-SHA-256(master_secret, info="open-connector-vault-v1", length=32)派生出的密钥只在服务器进程内存中存在,主密钥不会被直接当作加密密钥使用,派生密钥也不会写入任何地方。
加密
每个凭证以 AES-256-GCM 加密。每次加密都会生成一个全新的随机 12 字节 IV,存储的密文块将 IV、GCM 认证标签和密文打包在一起(各自 base64 编码,以点号分隔):
iv = 随机 12 字节
ciphertext, auth_tag = AES_256_GCM_encrypt(key, iv, plaintext)
stored = base64(iv) + "." + base64(auth_tag) + "." + base64(ciphertext)由于每次加密的 IV 都是随机的,相同的明文会产生不同的密文。密文块是自包含的——IV 和认证标签随密文一起存储,无需单独的 IV 表。具体实现见 packages/connectors/src/vault.ts。
"认证"加密意味着任何对密文的篡改都会在解密时导致认证失败,而不是返回损坏的数据。
密钥管理
加密密钥(CONNECTOR_ENCRYPTION_KEY)是整个保险库安全性的根基:
- 主密钥只存在于服务器进程中,永远不写入数据库
- 主密钥通过环境变量或密钥管理服务(如 AWS Secrets Manager)注入到服务器运行时
- Open Connector 不会在任何 API 响应、日志或错误信息中暴露该密钥
什么数据被加密
一个连接的密钥会被序列化为 JSON,然后作为单个 AES-256-GCM 密文块存储在 connection.credentials 列中。具体字段取决于连接器的认证模式:
| 数据 | 认证模式 | 存储位置 |
|---|---|---|
| OAuth Access + Refresh Token | OAUTH2 | connection.credentials 中的加密 JSON |
| API Key | API_KEY | connection.credentials 中的加密 JSON |
| BASIC 认证的用户名 + 密码 | BASIC | connection.credentials 中的加密 JSON |
| OAuth Client ID + Secret(认证配置) | — | auth_config.client_credentials 中的加密 JSON |
上述列中的内容均经过 AES-256-GCM 加密;非敏感的元数据(状态、连接器 slug、scopes、过期时间)以明文形式并存。
OAuth 应用的 Client Secret 是只写的:在创建/更新认证配置时接收、立即加密存储,任何读取接口都不会返回它。GET /auth_configs/{id} 的响应体里根本不含 client_id 或 client_secret 字段——内部的认证配置视图在投影时就不带凭证字段。
数据传输安全
凭证在传输过程中也受到保护:
- API 服务器与 Postgres 之间的连接推荐使用 TLS(通过
?sslmode=require配置) - Open Connector API 服务器本身应部署在支持 HTTPS/TLS 终止的环境后面(当前 AWS 部署由 Cloudflare Tunnel 处理;其他环境使用合适的反向代理)
- Agent 使用的 API Key 通过 HTTPS 传输,在日志中会进行部分脱敏(仅显示前缀)
密钥轮换
轮换 CONNECTOR_ENCRYPTION_KEY 需要用新密钥重新加密所有已存储的凭证——仅替换这个变量会让所有现有凭证无法解密。
目前没有内置的密钥轮换命令。在轮换工具发布之前,请将初始的 CONNECTOR_ENCRYPTION_KEY 视为持久的基础设施级密钥:妥善备份,避免更改。手动轮换意味着读取每条凭证、用旧密钥解密、用新密钥重新加密后写回——加密原语见 packages/connectors/src/vault.ts。
威胁模型
| 威胁 | 缓解措施 |
|---|---|
| 数据库访问被盗 | AES-256-GCM 加密 — 无密钥则无法读取 |
| 内存转储 | Token 仅在请求处理期间短暂驻留内存,不缓存 |
| 日志泄露 | Token 不会出现在日志中;API Key 在日志中仅显示前缀 |
| 中间人攻击 | 强制使用 HTTPS;数据库连接使用 TLS |
| Agent Key 泄露 | Key 按项目范围化隔离;可立即停用,不影响其他连接 |
与外部密钥管理服务集成
如需进一步提高安全级别,可以将 CONNECTOR_ENCRYPTION_KEY 存储在外部密钥管理服务(KMS)中:
# AWS Secrets Manager 示例
CONNECTOR_ENCRYPTION_KEY=$(aws secretsmanager get-secret-value \
--secret-id open-connector/encryption-key \
--query SecretString \
--output text)
# HashiCorp Vault 示例
CONNECTOR_ENCRYPTION_KEY=$(vault kv get \
-field=value \
secret/open-connector/encryption-key)在 Kubernetes 环境中,推荐使用 External Secrets Operator 将密钥管理服务中的密钥同步为 Kubernetes Secret,然后通过环境变量注入到 Pod 中。