这期周报分享我的 1Password 密钥管理实践,主要内容包括常见的密码管理,SSH/Shell 集成和 CI/CD 应用。
这些实践应当同样适用于 bitwarden 等服务。
密码管理
常见的 SaaS 服务密码管理直接使用 1Password 跨平台客户端创建即可,我个人会遵循如下原则:
每个网站都使用随机生成的唯一密码
1Password 默认使用 Smart Password
来根据网站的要求来生成,不满意的话可以自行微调,比如生成 32 位带大小写字母,数字和符号的密码。
两步验证应开尽开
两步验证是保护账号安全十分有效的手段,如果网站本身支持,应当尽可能开启。
我个人更喜欢 TOTP (Time-based one-time password) 而不是基于短信的两步验证方案:
- TOTP 是纯离线计算,不需要等待接收短信,速度更快
- TOTP 能够更好的与登陆流程集成,比如 1Password 会在登陆成功后自动复制 code, 部分支持的网站中还会直接自动填充,体验更顺畅
- TOTP 不依赖手机,在手机不在身边或者遗失时也能正常工作
1Password 的浏览器插件集成了 Scan QR Code 功能,在开启两步验证时直接扫码即可。
密保问题和答案也是密码
现在还需要设置密保问题和答案的服务已经不多了,但是如果需要设置的话,请将他们视作密码的一部分:不要使用真实的回答,将问题原文记录下来,并使用密码生成器生成随机字符串作为答案。
SSH 集成
1Password 在上一轮融资之后大刀阔斧地开发了一系列开发者工具,SSH 集成就是其中之一。
对我来说,它能解决如下问题:
- 本地无需再存储 SSH Secret Key,避免了密钥泄漏,也避免了被恶意软件读取的可能
- 在机器出现故障或者需要迁移时,不需要考虑密钥同步,避免了密钥丢失
- 所有对 SSH Key 的访问都需要经过 1Password 显式的弹窗请求,从而避免恶意软件在后台静默访问
它的思路非常简单:开发一个 SSH Agent,对外暴露为 SSH_AUTH_SOCK
,这样所有 SSH Key 请求都由本地 1Password 客户端来处理。
配置完成后,所有应用首次访问指定的 SSH Key 都会弹出如下窗口:
该窗口会展示应用 X 尝试访问 SSH Key Y,只有在认证通过后才能正常获取到 key 的内容。根据各自系统的配置不同,支持 Apple Touch ID,Windows Hello 等生物验证,在 Linux 平台上还提供了完整的 polkit
和 PAM
支持,会调用系统内置的用户认证机制提供原生体验。
配置 SSH Agent
使用这个功能首先需要正确配置 1Password SSH Agent。
在 1Password 客户端的配置中勾选并开启 SSH Agent:
在 ssh config 增加如下配置以指定 IdentityAgent
:
Host *
IdentityAgent ~/.1password/agent.sock
根据实际的情况,可以对不同的 Host 增加一些额外配置,比如指定 Host 不使用 1Password:
Host ec2-server
HostName 1.2.3.4
User ec2-user
IdentityFile ~/.ssh/ssh-key-not-on-1password.pem
IdentityAgent none
或者为其指定一个 key:
Host github.com
User git
IdentitiesOnly yes
IdentityFile ~/.ssh/github.pub
最后设置 SSH_AUTH_SOCK
即可:
export SSH_AUTH_SOCK=~/.1password/agent.sock
迁移 SSH Key
完成 SSH 配置之后,我们就可以开始迁移自己的 SSH Key 了。目前 1Password 支持 Ed25519
和 RSA
(2048-bit, 3072-bit, and 4096-bit) 两种 key,点击 New Item
,选择 SSH Key
,然后将 Private Key 添加进去就行。Private Key 添加完毕后 1Password 会自动生成一系列信息以供区分(体验满分!):
配置完毕后可以使用 git fetch
等命令来验证~
Shell 集成
除了 SSH 集成之外,1Password 还基于他们的 CLI 开发了 shell-plugins
。它解决的问题与 SSH 集成是类似的:很多开发者本地电脑上都以静态方式存储着 AWS/GCP/Github 等服务的密钥,万一泄漏的话可能就会导致测试环境甚至生产环境受到影响。思路同样非常简单,1Password 负责存储实际的密钥,而 1Password Plugin 是一层简单的 wrapper,将 GITHUB_TOKEN 等环境变量替换为实际的密钥,然后再调用对应的命令。
以 Github Plugin 为例,本地的 op/plugins.sh
内容如下:
export OP_PLUGIN_ALIASES_SOURCED=1
alias gh="op plugin run -- gh"
而对应的具体实现为:
func GitHubCLI() schema.Executable {
return schema.Executable{
Name: "GitHub CLI",
Runs: []string{"gh"},
DocsURL: sdk.URL("https://cli.github.com"),
NeedsAuth: needsauth.IfAll(
needsauth.NotForHelpOrVersion(),
needsauth.NotWithoutArgs(),
),
Uses: []schema.CredentialUsage{
{
Name: credname.PersonalAccessToken,
},
},
}
}
配置 Shell 集成同样十分简单:
# Signin op to make sure op itself works
op signin
# Setup plugin gh
op plugin init gh
所有的 shell plugin 都开源在 shell-plugins,使用 Golang 开发,如果有想支持的命令行工具欢迎提交 PR!
CI/CD 应用
最后想分享的是 1Password 在 CI/CD 中的应用。Github Actions 提供了内置的 Secrets 功能,但是有如下问题:
- Secrets 要求 Admin 权限,但是有时候项目的维护者可能只有 Committer 权限(比如 ASF 项目增加修改 Secrets 都需要向 Infra 团队提交工单)
- Secrets 只能 per repo 或者 per org 配置,在跨多个 repo 写作时配置复杂且麻烦
- Secrets 只能在 Github 平台中使用,无法支撑本地开发和调试等场景
OpenDAL 项目就遇到了这些问题:
- OpenDAL 中大量服务需要配置 Secrets,其中部分 Secrets 还需要进行周期轮换,通过 ASF Infra 团队来修改不仅等待时间长,而且大幅度增加了对方的工作量
- OpenDAL 的 Committer 调试服务时同样依赖 Secrets,但是很难保证 Secrets 在传递过程中的安全和同步
为此,OpenDAL 团队采用了基于 1Password 的密钥管理方案。
申请 1Password 开源赞助
感谢 1Password 对开源项目的大力支持,只需要向 1password-teams-open-source 提交 PR 即可获取免费的 1Password Teams membership。OpenDAL 提交的 PR 参见:Add Apache OpenDAL (incubating)。OpenDAL 创建了一个独立的 Services
Vault 并将权限赋予给所有的 PPMC 和 Committers,这样确保了团队内部对所有 Secrets 访问的一致性,消除了对 @Xuanwo 的单点依赖。
设置 1Password Services Account
在最近的更新中,1Password 增加了 Service Account 功能,支持授权该账户访问特定的 Vault。我们向 Infra team 提交了开通 1Password/load-secrets-action 并设置 OP_SERVICE_ACCOUNT_TOKEN
的申请。到这里,我们的 Github 环境已经可以正常从 CI 中获取 1Password 存储的密钥了。
配置 Github Actions
接下来我们只需要使用 load-secrets-action
来加载密钥即可,以 OpenDAL 配置 COS 服务为例:
- name: Load secret
id: op-load-secret
uses: 1password/load-secrets-action@v1
with:
export-env: true
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
OPENDAL_COS_TEST: op://services/cos/test
OPENDAL_COS_BUCKET: op://services/cos/bucket
OPENDAL_COS_ENDPOINT: op://services/cos/endpoint
OPENDAL_COS_SECRET_ID: op://services/cos/secret_id
OPENDAL_COS_SECRET_KEY: op://services/cos/secret_key
- name: Test
shell: bash
working-directory: core
run: cargo test cos -- --show-output
env:
RUST_BACKTRACE: full
RUST_LOG: debug
export-env
表示将这些密钥导出为环境变量OP_SERVICE_ACCOUNT_TOKEN
是我们之前配置好的 service account tokenop://services/cos/test
表示使用services
vault 的cos
条目中的test
字段值来替换
需要注意的是,该方案同样依赖静态的 OP_SERVICE_ACCOUNT_TOKEN
密钥,如果泄漏可能导致整个 vault 中的密钥泄漏,所以:
- 如果场景不是像 OpenDAL 这样的复杂,请使用 configure-aws-credentials 和 google-github-actions/auth 等 actions 实现基于 GitHub OIDC 的无密钥认证
- 所有配置进入 Vault 的密钥需遵循最小化原则,能且只能访问指定资源,比如 AWS 资源需配置 IAM 子账户并通过 role 限制权限
总结
本文分享了我在日常工作和生活中使用 1Password 的实践经验,希望能对大家设计自己的密钥管理方案提供一些借鉴!