Node.js 程序以 Keyless 模式从 1Password 读取密钥

October 28, 2024

最近经常用 Node.js 写一些本地运行的脚本,需要安全的读取一些密钥密码之类的敏感信息,业界通常的做法是这样:

  • 通过环境变量读取
  • 通过在线的 KMS 系统读取

对于环境变量模式,一般情况需要在本地文件系统明文保存一些关键的密钥信息。本质上还是不太安全,例如你的钱包私钥明文放在本地文件系统,容易在未来的某一天爆雷,这种模式本质上不是无密码(Keyless)的。
对于 KMS 模式,需要依赖各种在线 KMS 服务,整体方案比较重,不太适合在本地电脑上轻量化使用。

本文使用 1Password 和官方提供的 CLI,在本地以无密码方式读取敏感信息。

具体方案

  1. 首先你需要是 1Password 用户(需要付费,家庭版比较实惠)
  2. 安装 CLI,我这里用 macOS 上的 brew 进行安装 brew install 1password-cli。具体可参考:https://developer.1password.com/docs/cli/get-started/
  3. 登录 1Password CLI:op account add
  4. 从 Node.js 读取指定的密钥
import { exec } from 'child_process'
import util from 'util'

const execPromise = util.promisify(exec)

// 使用内存缓存存储密钥
const secretCache = new Map<string, string>()

async function getSecretFromOnePassword(itemName: string, label: string): Promise<string> {
  const cacheKey = `${itemName}:${label}`

  // 检查缓存中是否已存在
  const cachedSecret = secretCache.get(cacheKey)
  if (cachedSecret) {
    console.log('从缓存获取密钥')
    return cachedSecret
  }

  console.log('从 1Password 获取密钥...')
  try {
    // 使用 op cli 获取 JSON 格式的 item 数据
    /*
        返回值示例:
        {
            "id": "xxx",
            "section": {
                "id": "add more"
            },
            "type": "CONCEALED",
            "label": "secret-key",
            "value": "xxx-value",
            "reference": "op://xxx/secret-key"
        }
        */
    const command = `op item get "${itemName}" --fields label="${label}" --format json`
    const { stdout } = await execPromise(command)

    // 解析返回的 JSON 并返回值
    const item = JSON.parse(stdout)
    if (item && item.value) {
      console.log('密钥获取成功')
      // 存入缓存
      secretCache.set(cacheKey, item.value)
      return item.value
    } else {
      throw new Error(`label "${label}" not found in item "${itemName}"`)
    }
  } catch (error) {
    console.error('从 1Password 获取密钥失败:', error)
    throw error
  }
}

1Password 字段说明

在执行脚本时,系统会弹出 1Password 确认框,待你确认后,程序就可以获取到对应的密钥。

1Password 授权确认框

结束语

本文用 Node.js 示范了如何在本地以 Keyless 模式读取 1Password 里的密钥,实现原理本质上是包装调用了 1Password 官方的 CLI 程序,其他语言例如 Python、Java、Golang 之类的也可以很轻松的实现本文的逻辑。

如果你喜欢我的内容,请考虑请我喝杯咖啡☕吧,非常感谢🥰 。

If you like my contents, please support me via BuyMeCoffee, Thanks a lot.