跳转至

项目接入 vault

消费方项目怎么用 vault 里的凭证和资源。

流程一览

vault.json                                       ←  你写(声明要什么)
    ↓ vault install   (不传 --env · 一气出多份)
.env + .vault/secrets.json                       ←  env-agnostic (顶层 slug 凭证)
.env.<name> + .vault/secrets.<name>.json         ←  每个 envs.<name> 声明对应一份
代码直接读 (按 process.env.APP_ENV 选)

vault install 行为

调用 输出
vault install (不传 --env) env-agnostic .env + .vault/secrets.json + 每个 envs.<name> 声明对应的 .env.<name> + .vault/secrets.<name>.json 一气全出
vault install --env staging 只装这一份: .vault/secrets.staging.json + .env.staging
vault install --env prod 只装这一份: .vault/secrets.prod.json + .env.prod

env-agnostic .env: 只装顶层 slug 的凭证 (env 不区分)。envs.{...} 块声明的多环境凭证在这里跳过 (要 env 上下文才能定 slug)。

1. 初始化

cd ~/myproject
vault init

在当前目录生成空骨架:

{
  "$schema": "https://vault-cli.local/schemas/vault-manifest.schema.json",
  "project": "myproject",
  "credentials": {},
  "resources": []
}

2. 声明依赖

编辑 vault.json,声明你需要的 credentials 和 resources。

{
  "$schema": "https://vault-cli.local/schemas/vault-manifest.schema.json",
  "project": "xiangqin",
  "vault_min_version": ">=0.3",
  "credentials": {
    "aliyun": {
      "slug": "aliyun-main",
      "fields": ["access_key_id", "access_key_secret", "default_region"],
      "env": {
        "access_key_id": "ALIYUN_ACCESS_KEY_ID",
        "access_key_secret": "ALIYUN_ACCESS_KEY_SECRET",
        "default_region": "ALIYUN_REGION"
      },
      "purpose": "OSS 备份 + DNS 记录"
    },
    "alipay": {
      "slug": "alipay-kongxuanpin",
      "fields": ["app_id", "app_private_key", "alipay_public_key"],
      "env": { "app_id": "ALIPAY_APP_ID" },
      "optional": true
    }
  },
  "resources": [
    { "ref": "aliyun/ecs/i-bp1iswr9bmkv3qh89965", "purpose": "生产服务器" },
    { "ref": "aliyun/oss/myproject-backup", "purpose": "每日备份" }
  ]
}

credentials 字段说明

字段 必填 说明
slug vault 里的 credential slug(vault credential list 查)
fields 白名单字段;不填 = 取全部
env vault字段名 → ENV_NAME 映射;只列出的进 .env
optional true 时凭证缺失只报 warning,不报错
purpose 人类可读注释

map 的 key 是本地别名

credentials.aliyun 里的 aliyun 是你代码里用的本地别名。底下挂的 slug: aliyun-main 是 vault 里的真名。别名层解耦了代码和 vault 命名变动。

resources 只声明不注值

Resource 只为 audit + vault who-uses 反查存在,不会把 spec 注入到代码。需要用时 vault resource show <ref>

nested values(1 层 dict)

credential 的 values 支持 1 层 nested。常见场景: - 多环境 db(prod / staging / canary 各一组 user/password) - 多 client(web / mobile / cli 各一对 client_id/secret) - 多 namespace 资源

vault 数据示例:

{
  "slug": "hongniang-rds-mysql",
  "kind": "misc",
  "values": {
    "endpoint": "rm.example.com",
    "prod":    { "db": "hongniang_prod",    "user": "u_prod",    "password": "..." },
    "staging": { "db": "hongniang_staging", "user": "u_staging", "password": "..." }
  }
}

vault.json 取值有两种写法:

{
  "credentials": {
    "db": {
      "slug": "hongniang-rds-mysql",
      "fields": ["endpoint", "prod"],          // 整个 sub-dict
      "env": { "prod.password": "DB_PASSWORD" } // dotted 取叶子(env 必须 scalar)
    }
  }
}
  • fields: ["prod"] → secrets.json 里 db.prod 是完整 sub-dict
  • fields: ["prod.password"] → secrets.json 里 db["prod.password"] 是 scalar
  • env 只接受 dotted 叶子 · sub-dict 会报错(env line 不能塞 JSON)

只支持 1 层 nested · prod.replica.user 不行(YAGNI · 真出现再升级)。

3. 多环境绑定(v0.3 polymorphic slug)

顶层 environments 显式声明项目支持哪些 env · slug 形态自推断作用范围:

  • slug: "shared-slug" (string) → 全 env 共享 · 进 .env + 所有 .env.*
  • slug: { "staging": "...", "prod": "..." } (object) → keys 即作用范围 · .env 跳过 · 只进 keys 列的 env 文件
{
  "project": "akong",
  "environments": ["staging", "prod"],
  "credentials": {
    "mysql": {
      "slug": {
        "staging": "akong-mysql-staging-pw",
        "prod":    "akong-mysql-prod-pw"
      },
      "fields": ["host", "port", "user", "password", "db"],
      "env": {
        "host": "DB_HOST",
        "port": "DB_PORT",
        "user": "DB_USER",
        "password": "DB_PASSWORD",
        "db": "DB_NAME"
      },
      "purpose": "MySQL 真值跟环境走 · env var name 不变"
    },
    "jwt_secret": {
      "slug": {
        "staging": "akong-jwt-staging",
        "prod":    "akong-jwt-prod"
      },
      "fields": ["secret"],
      "env": { "secret": "JWT_SECRET" },
      "purpose": "JWT 环境独立 · 不能共享 prod secret"
    },
    "sms": {
      "slug": {
        "staging": "matchmaker-sms-main",
        "prod":    "matchmaker-sms-main"
      },
      "fields": ["access_key_id", "access_key_secret"],
      "env": {
        "access_key_id": "SMS_AK",
        "access_key_secret": "SMS_SK"
      },
      "optional": true,
      "purpose": "staging/prod 走阿里云 dysmsapi · 跨 env 用同 slug 时显式重复"
    },
    "aliyun_shared": {
      "slug": "aliyun-main",
      "fields": ["access_key_id", "access_key_secret"],
      "env": {
        "access_key_id": "ALIYUN_AK",
        "access_key_secret": "ALIYUN_SK"
      }
    }
  }
}

4. 安装

vault install                    # 一气出: .env + .vault/secrets.json + 每个 envs.<name> 对应文件
vault install --env staging      # 只装这一份: .vault/secrets.staging.json + .env.staging
vault install --env prod         # 只装这一份: .vault/secrets.prod.json + .env.prod

输出:

✓ vault.json → 3 credentials (env=staging)
  .vault/secrets.staging.json
  .env.staging  (6 env lines)
  .gitignore  (added .vault/ + .env + .env.*)

生成的文件:

.vault/secrets.<env>.json(结构化,按别名分组):

{
  "_generated_at": "2026-04-22T13:35:40+00:00",
  "_env": "staging",
  "_note": "Auto-generated by `vault install --env staging` from vault.json. Do not edit.",
  "mysql": {
    "host": "rm-bp1xxx.mysql.rds.aliyuncs.com",
    "port": 3306,
    "user": "akong_staging",
    "password": "...",
    "db": "akong_staging"
  },
  "jwt_secret": { "secret": "..." }
}

.env.<env>(扁平,12-factor 风格):

DB_HOST=rm-bp1xxx.mysql.rds.aliyuncs.com
DB_PORT=3306
DB_USER=akong_staging
DB_PASSWORD=...
DB_NAME=akong_staging
JWT_SECRET=...

5. 代码里使用

代码按当前环境(NODE_ENV / APP_ENV / 部署管道传入)读对应文件即可 — env var name 不变 · 代码无感知。

Python

import json, os

env = os.environ.get("APP_ENV", "local")

# 方式一:结构化
secrets = json.loads(open(f".vault/secrets.{env}.json").read())
host = secrets["mysql"]["host"]

# 方式二:环境变量(先 dotenv.load_dotenv f".env.{env}" 或 systemd EnvironmentFile)
host = os.environ["DB_HOST"]

Node

import fs from "fs";
import "dotenv/config";  // 配合 dotenv-cli: dotenv -e .env.${APP_ENV}

const env = process.env.APP_ENV || "local";
const secrets = JSON.parse(fs.readFileSync(`.vault/secrets.${env}.json`));
const host = secrets.mysql.host;
// 或
const host = process.env.DB_HOST;

Shell

source ".env.${APP_ENV:-local}"
echo $DB_HOST

# 或直接 jq
jq -r '.mysql.host' ".vault/secrets.${APP_ENV:-local}.json"

6. 刷新(AK 轮换后)

vault sync                       # 等价 vault install,default local
vault sync --env staging         # 刷 staging 那一份

7. 反查

哪些项目在用某个 credential?

vault who-uses aliyun-main
# /Users/yarnb/xiangqin/vault.json   (credential (alias=aliyun))
# /Users/yarnb/myproject/vault.json  (credential (alias=aliyun))

gitignore

vault install 自动把 .vault/ + .env + .env.* 加到 .gitignore(如果还没有)。

CI / 生产环境

生产部署时按目标环境跑 vault install --env staging/--env prod 把对应凭证拉到服务器。或者把 .vault/secrets.<env>.json 内容作为 secret manager 的变量(按平台约定)。

GHA 例:

- run: vault install --env staging   # develop 分支 → staging
- run: vault install --env prod      # main 分支 → prod

老格式平滑迁移

v0.2 完全 backward-compat 老格式 vault.json(无 envs 字段):

老格式 新行为
vault install(无 --env .vault/secrets.local.json + .env.local
顶层 slug 任意 --env 都读这个 slug

迁移方式:哪个 alias 真有多 env 用 envs.{local,staging,prod}.slug 重写,剩下保持单 slug 即可。