1Password:从 Vault 共享到离职回收
1Password 中移除成员会阻止其继续从权威 Vault 读取,但不会收回已经到达设备、剪贴板、导出文件和人脑的密码,也不会让目标数据库自动拒绝旧值。成员撤权、设备与会话处置、导出副本清理和底层凭据轮换必须作为四个动作验证。
1Password:vault 是授权边界,item 是使用对象
1Password 的人员链从 account 进入,一个 account 可以访问多个 vault,vault 内保存 item,item 再按 Login、Password、SSH Key 等类别组织字段。团队通常把人加入 group,再把 group 授权给 vault;不要用标签模拟权限,因为标签只是组织方式。管理 vault 不天然包含读取 item 的能力,查看、编辑、导出、复制分享和管理 vault 也是不同权限。官方的 团队 vault 权限说明还指出,允许导出意味着能把内容保存为其他应用可读的未加密文件,因此“可查看”和“可带走全部数据”不应默认绑定。
研发团队可以按环境与职责拆 vault,例如 catalog-dev、catalog-release、shared-emergency,但不能为每条密码建立一个 vault。vault 太粗会让无关人员一起获得高敏项,太碎则使授权关系难以复核。一个实用判断是:同一 vault 中的 item 是否拥有相同的数据 owner、人员集合、客户端访问策略、导出约束和离职轮换批次;任一答案不同,就应考虑拆分。
item 的 title 用于检索,字段才是程序读取的接口。自动化不要依赖易改名的显示标题,优先使用稳定 ID 或受控的 secret reference。op://vault/item/field 引用表达的是定位关系,不含字段明文,可以进入模板仓库;它仍会泄露 vault、item 与字段命名,命名中不要放客户名、事故代号或其他敏感拓扑。字段改名会使引用失效,移动 item、复制 item 和新建同名 item 也可能改变身份,变更时必须同时跑消费端验证。
安装 1Password 桌面端与 CLI,并确认身份链
交互开发机可同时安装官方桌面应用与 op CLI。桌面应用负责本地解锁、系统认证和浏览器集成,CLI 负责脚本化读取;启用桌面集成时,CLI 可以复用已解锁的桌面会话。CLI 也可以使用独立的交互式登录或 Service Account,服务器与 CI 不应为了得到 CLI 而安装桌面会话。官方 1Password CLI 入门列出了各平台入口,Windows 可使用:
winget install --id AgileBits.1Password.CLI --exact
Get-Command op | Format-List Source,Version
op --version使用桌面集成时,按应用的 Developer 设置启用 CLI 集成并保持应用解锁,然后运行:
op vault list
op whoami成功证据不是终端出现一长串 JSON,而是 op whoami 返回预期 account,op vault list 只列出当前人员应访问的 vault。若命令提示未登录,先区分桌面集成未启用、应用已锁定、多个 account 选错和企业策略禁止集成;不要把 account password 或 Secret Key 写进脚本绕过交互认证。
代理网络还要分别验证登录服务、item API 和软件更新入口。能下载 CLI 不代表能访问团队 account;企业 TLS 检查可能让客户端报证书链错误。正确修复是向受控系统信任库分发企业根证书,并确认代理不记录请求体,而不是关闭证书校验。桌面应用、浏览器扩展和 CLI 的版本基线应由软件分发系统记录,安装包与扩展只从官方发布入口取得。
用 1Password 跑通一条合成共享链
在管理页创建实验 vault catalog-demo,只给实验 group 查看权限;在桌面应用中创建 Login item demo-api,用户名填 demo-user,密码使用客户端现场生成的随机值,不把值粘贴进命令、文档或聊天。然后创建只含引用的模板 .env.op:
DEMO_USER=op://catalog-demo/demo-api/username
DEMO_PASSWORD=op://catalog-demo/demo-api/password用一个只判断“值是否存在且用户名是否匹配”的临时脚本消费它,避免把密码打印出来:
// verify-credential.mjs
import { createHash } from "node:crypto";
const user = process.env.DEMO_USER;
const password = process.env.DEMO_PASSWORD;
if (user !== "demo-user" || !password || password.length < 16) {
console.error("credential_contract_failed");
process.exit(2);
}
const fingerprint = createHash("sha256").update(password).digest("hex").slice(0, 12);
console.log(JSON.stringify({ user, loaded: true, fingerprint }));op run --env-file .env.op -- node .\verify-credential.mjs
Remove-Item .\verify-credential.mjs,.\.env.op预期输出只有 loaded: true、合成用户名和不可逆短指纹,没有 secret 明文。op run 把解析后的值放进子进程环境;这比永久 .env 少一个磁盘副本,但环境变量仍可能被子进程、崩溃转储、调试器或错误日志读取。处理高敏凭据时,应优先让目标程序从受限文件描述符或专用 SDK 读取,并关闭 shell trace。
反向实验把实验人员移出 group,锁定并重新解锁客户端,再运行相同命令。可信失败是 op 无法解析该引用或返回无权访问,而不是脚本还能读到旧环境变量。必须启动新终端和新子进程,因为一个已经得到 DEMO_PASSWORD 的进程不会因服务端撤权而自动擦除内存。最后在目标测试服务轮换合成密码,用旧值请求一次并得到确定的认证失败,才能证明离职回收闭环。
这个实验还要分别观察在线撤权与离线副本。暂停或删除成员后,联网且已解锁的 1Password 应移除其团队数据;官方的 成员移除说明同时明确,离线设备上的项目可能一直可读,直到设备联网并再次尝试解锁。因而管理员要把该成员曾有权查看的 vault 作为轮换集合,不能只按审计中出现过的 item 处理。清理实验时先删除合成 item,再移除实验 group 对 vault 的权限;确认留任账号仍能访问需要保留的对象后删除实验 vault,并清除 .env.op、临时脚本、终端子进程和任何共享链接。
1Password 的机器接入不能复用员工会话
交互式 CLI 可以借助桌面应用和系统认证;CI、容器与无头服务器需要非人身份。1Password Service Account 是独立于员工的机器身份,可限制可访问的 vault;其可见操作与报告能力取决于团队方案和管理员权限。官方 Service Account 说明把它用于共享环境、自动访问和无头认证。它的 token 是高敏长期材料:只在 CI secret store 中保存,限制环境与分支,轮换时创建新 token、切换消费者、验证新 token、撤销旧 token,不能把 token 放进镜像层或 Compose 文件。
# CI 平台中 OP_SERVICE_ACCOUNT_TOKEN 来自受保护的 secret,不写入仓库。
steps:
- name: verify synthetic credential
shell: bash
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
run: |
set +x
op read 'op://catalog-demo/demo-api/username' >/dev/null
op run --env-file .env.op -- node verify-credential.mjsService Account 适合 CLI 或 SDK 直接访问托管服务。1Password Connect 则是在团队基础设施中部署 API 服务,让应用通过 Connect token 读取被授权 vault;它需要 Connect 凭据文件和访问 token,并引入镜像升级、TLS、网络分区、容量、备份和自身可用性。官方 Connect 部署入口给出 Docker 与 Kubernetes 形态。Connect 不是“把个人密码库复制到容器”:凭据文件、token、服务日志和缓存都进入新的信任边界,出口网络应只到必要的 1Password 服务,访问 token 只给调用应用,平台团队还要监控同步失败与陈旧数据。
自动化需要大量短期数据库凭据、按工作负载动态签发、租约到期自动撤销时,密码库 item 不是理想的颁发引擎。Service Account 解决“谁可以读取静态记录”,不会让目标数据库里的密码自动变成短期凭据。此时应由 Vault、云 secret manager 或目标系统身份联合签发,1Password 只保留恢复材料、人工审批入口或 bootstrap 凭据。
1Password 的退出链从受控导出开始。1Password 导出说明明确,1PUX 与 CSV 都是未加密文件;CSV 只覆盖标题、网站、用户名、密码、一次性密码等有限字段,不能拿“文件已生成”代替恢复验证。桌面端的文件导出不包含 passkey;当前只有 iOS 与 Android 可以通过 FIDO Credential Exchange Protocol 把 passkey 直接导出到兼容应用,这条移动端迁移链与 1PUX/CSV 是两种不同机制。使用 SSO 解锁的账号不能直接导出,需要团队管理员先关闭该账号的 Unlock with SSO。管理员应先在隔离设备导出一组合成 item,记录文件 hash 与保管人,在候选工具中导入并核对自定义字段、附件、TOTP、SSH key 与 passkey 的迁移限制,再销毁下载目录、回收站和同步盘副本。真实迁移中,凡经过明文导出的凭据都进入轮换队列;关闭账号、Service Account 或 Connect 之前,还要导出所需审计证据并验证新平台的人员权限和机器任务已经接管。
1Password SSH agent 代理签名,不替你撤销服务器公钥
SSH Key item 可以保存密钥,1Password SSH agent 通过 agent 协议向 ssh 提供签名,私钥不必导出为普通文件。安装桌面应用、添加或生成 SSH Key item、在 Developer 设置中启用 SSH agent,再按平台把 SSH_AUTH_SOCK 指向 1Password agent。官方 SSH agent 文档是平台配置的权威入口。
ssh-add -L
ssh -v demo-user@host.examplessh-add -L 只应列出获准使用的公钥;ssh -v 的证据是客户端选择了预期指纹并由 agent 完成签名,不是把私钥打印出来。若出现 Too many authentication failures,要限制 agent 暴露的 key 或在 SSH config 中绑定 IdentityFile 对应公钥;若本机 app 已锁,签名请求应被阻断或要求重新批准。
删除 vault 权限会阻断以后从 1Password 使用 key,但目标服务器的 authorized_keys、Git 平台 deploy key 和代码签名信任不会自动删除。离职时必须从目标端移除公钥并验证旧 key 认证失败。若公钥已复制到多台主机,CMDB 或 IaC 应能按指纹反查全部落点;否则密码库的审计只能证明谁可能拿到 key,不能证明 key 在哪里仍被信任。
用本地合成实验确认撤权不等于旧值失效
没有可用的测试租户或隔离设备时,可以先运行下面的教学模型。它不调用厂商 API,不能代替产品验收;它只演示密码管理工具必须面对的状态不变量:在线读取被阻断后,已下发的离线副本仍可能保留旧值;只有目标端轮换后,旧副本才失去业务效力。脚本只生成随机合成值,只输出 hash 指纹,并在退出前清空临时目录。
// password-vault-lifecycle-lab.mjs
import { createHash, randomBytes } from "node:crypto";
import { mkdtemp, rm, writeFile, readFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
const dir = await mkdtemp(join(tmpdir(), "password-vault-lab-"));
const fp = (value) => createHash("sha256").update(value).digest("hex").slice(0, 12);
const randomSecret = () => `synthetic-${randomBytes(24).toString("base64url")}`;
const authority = {
itemId: "catalog-demo-api",
version: 1,
readers: ["developer", "leaver"],
value: randomSecret(),
};
const target = { acceptedFingerprint: fp(authority.value) };
const readOnline = (actor) => {
if (!authority.readers.includes(actor)) throw new Error("ACCESS_DENIED");
return authority.value;
};
const authenticate = (value) => fp(value) === target.acceptedFingerprint;
try {
const oldValue = readOnline("leaver");
await writeFile(join(dir, "offline-cache.json"), JSON.stringify({ value: oldValue }));
console.log(JSON.stringify({ phase: "positive", online: authenticate(oldValue), fp: fp(oldValue) }));
authority.readers = authority.readers.filter((name) => name !== "leaver");
try {
readOnline("leaver");
throw new Error("revocation_test_did_not_fail");
} catch (error) {
if (error.message !== "ACCESS_DENIED") throw error;
console.log(JSON.stringify({ phase: "revoked", onlineRead: "ACCESS_DENIED" }));
}
const cached = JSON.parse(await readFile(join(dir, "offline-cache.json"), "utf8")).value;
console.log(JSON.stringify({ phase: "offline-copy", oldValueStillWorks: authenticate(cached), fp: fp(cached) }));
authority.value = randomSecret();
authority.version += 1;
target.acceptedFingerprint = fp(authority.value);
console.log(JSON.stringify({
phase: "rotated",
version: authority.version,
oldValueStillWorks: authenticate(cached),
newValueWorks: authenticate(readOnline("developer")),
}));
} finally {
await rm(dir, { recursive: true, force: true });
console.log(JSON.stringify({ phase: "cleanup", removed: true }));
}node .\password-vault-lifecycle-lab.mjs
Remove-Item .\password-vault-lifecycle-lab.mjs预期顺序为:正向读取成功;撤权后在线读取返回 ACCESS_DENIED;离线旧值在轮换前仍可认证;轮换后 oldValueStillWorks 变为 false、newValueWorks 为 true;临时目录清理成功。任何产品的离职流程如果无法给出同等证据,就还没有关闭旧凭据风险。
浏览器、剪贴板与自动填充仍是独立攻击面
1Password 的客户端负责解密与填充,服务端权限正确并不意味着终端没有旁路。浏览器扩展只从官方渠道安装,工作资料与个人 profile 分离,关闭浏览器自带密码保存以避免双写。自动填充前核对协议、完整域名、端口和 iframe;相似域名、HTTP 页面和不受信窗口应拒绝自动填充。复制动作会经过系统剪贴板、远程桌面、跨设备同步和录屏环境,短时清除只是降低暴露窗口,不能撤回已经被其他进程读取的内容。
自动化优先使用该产品的机器身份和进程级注入,交互登录优先使用受控扩展或客户端集成。故障排查不得打印 secret、会话 token 或完整导出内容;只输出稳定对象 ID、版本、权限结果和不可逆短指纹。需要人工共享时,必须设置接收者、最短有效期、owner 和主动删除时点,并把接收者可能已经复制内容纳入轮换判断。
导出、恢复与迁移要验证数据可读性
1Password 的 1PUX 与 CSV 导出都是未加密文件,且不同格式覆盖的字段并不相同;passkey 还有独立的移动端迁移链。使用 SSO 解锁的账号在导出前还可能需要管理员调整账号方式。不能用‘文件已经生成’代替恢复测试,也不能把 Emergency Kit 与已经解锁的工作设备放在同一控制域。
恢复演练必须在隔离环境中实际导入,核对条目数、自定义字段、附件、TOTP、SSH key、passkey 与历史版本,记录导出文件 hash、保管人和销毁时点。导入成功也不代表权限模型已经重建;新平台中的人员组、机器身份和最小权限需要单独验收。凡经过明文或降级格式迁移的真实凭据,都应进入轮换队列。
离职与失窃设备按两阶段回收
移除 1Password 成员需要设备联网并再次解锁,离线设备中的团队数据可能继续可读。管理员应以该成员曾能查看的全部 Vault 为轮换集合,同时撤销 Service Account、Connect token 与共享链接;SSH Key item 被移除后,还要在服务器、Git 平台或签名系统删除对应公钥。
第一阶段立即阻断身份、会话、共享链接和机器 token,并隔离失窃设备;第二阶段按“曾经可读”的最大范围盘点目标凭据,逐项轮换或撤销。结案证据至少包括旧账号不能继续同步、旧会话不能新取值、旧值在目标系统被拒绝、新值由留任身份验证成功,以及导出和离线副本已按最坏情况处置。
长期治理与退出条件
Vault 是 1Password 的主要授权容器,标签不是权限。Service Account、Connect、SSH Agent、浏览器扩展和导出文件分别形成不同信任边界,必须各自登记 owner、权限、版本、轮换和恢复责任。
每个授权容器和机器身份都要有业务 owner、技术 owner、复核周期和退出路径。持续指标应观察无 owner 对象、超期共享、长期 token、离职暴露项、旧值拒绝证据缺口和恢复失败,而不是只统计保存了多少密码。停止使用前先冻结写入、完成受控导出与恢复验证、重建权限和自动化,再撤销旧 token、删除客户端配置和不再需要的副本;无法证明旧值失效,就不能把迁移标记为完成。
