# 使用 macOS MDM 部署本地代理网关

本地代理网关应像安全敏感的桌面应用一样部署，而不是像共享秘密分发系统一样部署。MDM 负责签名应用、应用版本、启动状态和移除操作。开发者负责将凭据放入本地保险库，并负责允许代理使用这些凭据的审批。

这不仅是管理上的整洁划分。当 IT 通过 MDM 推送 API 密钥时，管理平面、清单导出、部署脚本或配置描述文件一旦遭到入侵，都可能导致秘密泄露。当代理直接收到密钥时，每条日志、工具转录、Shell 历史记录和提示边界都会成为秘密的攻击面。网关的作用就是让凭据远离代理进程。部署时不要破坏这一设计。

对于一批受管理的 Mac，我会采用这样的运行模式：分发一个可验证的应用包，在正确的用户上下文中启动应用，让版本通过不同更新环逐步发布，让每位开发者在本地注册自己的凭据，并通过先移除访问权限、再擦除硬件的顺序退役设备。

## MDM 应分发网关，而不是开发者的权限

MDM 应知道哪台 Mac 安装了获批应用，以及运行的是哪个版本。它不应成为个人 API 令牌、SSH 私钥或粘贴进来的凭据文件的保险库。

人们常常把两个不同的东西混在一起，因为它们都被称为“配置”。安装应用是设备群配置。授予某个人或某个代理调用生产 API 的权限，则是委派权限。前者属于 MDM，后者属于本地的、由用户控制并具有明确审批边界的保险库。

这一点会直接影响入职流程。MDM 工作流可以在开发者打开编辑器前安装网关。开发者首次运行时，仍必须创建或解锁自己的保险库，并只添加获授权使用的凭据。IT 可以记录获批的凭据类型和所需的服务账户，但不应收集秘密内容，之后再把它们推送出去。

这也让离职流程更可靠。移除受管理应用，并不会撤销几个月前被复制到配置文件中的云令牌。本地保险库允许用户直接移除凭据，服务所有者也可以在令牌来源处撤销它。这样就有两种相互独立的终止访问方式，而不是依赖一个脆弱的清理脚本。

对于 Sallyport，这种划分是有意设计的：应用会把 API 密钥和 SSH 密钥保存在加密的本地保险库中，并自行执行 HTTP 或 SSH 操作，而不是把凭据传给连接的代理。部署设计应保留这一特性。

合理的网关 MDM 记录只包含运行信息：

- 设备序列号或管理标识符
- 分配的部署环和应用版本
- 软件包收据和安装结果
- 主要支持负责人和例外期限
- 应用是必需、可选还是等待移除

不要把秘密名称、令牌值、私钥路径或审批历史加入清单字段。这些细节要么会暴露敏感上下文，要么会造成一种误解，让人以为 MDM 能够重建用户的工作访问权限。

## 软件包就是部署契约

签名的安装包是让设备群部署可重复的构件。在聊天中传来传去的磁盘映像、解压到 Downloads 的 zip 文件，或把应用包复制到 /Applications 的脚本，都不是部署契约。

Apple 的部署文档指出，通过设备管理安装的软件包必须带有设备能够验证的签名。文档还建议使用自包含应用，这样普通应用部署就不需要自定义安装脚本。这条建议很适合这里。脚本会增加失败路径，以令人困惑的权限运行，而且应用布局改变后往往还会长期存在。

每个发布版本都构建一个发布构件。为它设置带版本号的文件名，在发布记录中保留校验和，并要求所有更新环使用同一个软件包。更新环应该测试同一个构建版本的暴露范围，而不是为每个群组组装一个无法追溯的变体。

将软件包上传到 MDM 前，应在一台干净的测试 Mac 上同时验证软件包和已安装应用。以下命令可以作为最低限度的检查：

```sh
pkgutil --check-signature Sallyport.pkg
spctl -a -vv -t install Sallyport.pkg
```

第一条命令应识别出已签名的安装程序和有效的签名链。第二条命令应评估该软件包是否适合安装。部署后还要检查应用包：

```sh
codesign --verify --deep --strict --verbose=2 /Applications/Sallyport.app
spctl -a -vv /Applications/Sallyport.app
```

不要把 MDM 显示为绿色状态当作正确二进制文件已经可用的证明。MDM 可能会报告软件包已安装，但后续的登录项、后台助手、权限配置或用户会话要求仍可能阻止应用正常工作。软件包验证和功能测试回答的是不同问题。

让软件包保持简单。它应将应用安装到预期位置，避免在 postinstall 阶段下载第二个可执行文件，也不要写入凭据或每个用户的设置。如果软件包需要复杂的 postinstall 脚本才能让产品正常工作，就应该停下来检查应用设计或打包边界是否有问题。每次 macOS 升级和每次事件响应复盘，你都会为这段脚本付出代价。

已安装的应用应当能够像应用一样被移除，而不是需要一次取证式清理。Apple 指出，通过软件包放入 /Applications 的应用包可以由设备管理服务管理并单独移除。这也是避免把核心文件散落到任意目录中的原因之一。

## 在开发者会话中启动应用

需要本地审批或访问用户受保护保险库的网关，必须在已登录用户的上下文中运行。root 守护进程无法替代这个上下文。

团队在这里经常犯一个可预见的错误。他们看到“始终运行”，便选择 LaunchDaemon，因为它可以在登录前运行，并且用户退出登录后仍然存活。随后他们发现，守护进程无法真实地显示用户审批提示，无法使用预期的用户钥匙串或生物识别授权路径，而且获得了超出任务需要的权限。

Apple 清楚地划分了边界。登录项在用户登录时启动，并在该用户的会话中运行。LaunchAgent 也为已登录用户运行。LaunchDaemon 在系统级别运行，可以在登录前启动，并以 root 身份运行。Apple 还说明，对于应在用户会话期间保持活动状态的面向用户应用，登录项是合适的选择。

对于菜单栏网关，通常应选择受管理的登录项，或应用自身支持的登录项机制。它应在用户进入桌面后启动，并保持足够的可见性，让开发者知道它正在运行，用户退出登录时则停止。不要把安全边界隐藏在用户无法检查或退出的进程中。

如果 MDM 需要强制执行启动行为，应使用 Apple 的受管理登录项负载，而不是编写一个由脚本复制的 plist。`com.apple.loginitems.managed` 负载可以指定应用路径以及应用是否隐藏。Apple 的设备管理参考文档提供了负载结构，并支持在 macOS 上使用它。

下面是一个代表性负载。将标识符和路径替换为你自己发布的值，然后通过常用的描述文件工具生成 UUID。

```xml
<dict>
  <key>PayloadType</key>
  <string>com.apple.loginitems.managed</string>
  <key>PayloadIdentifier</key>
  <string>dev.example.agent-gateway.login-item</string>
  <key>PayloadUUID</key>
  <string>REPLACE-WITH-UUID</string>
  <key>PayloadVersion</key>
  <integer>1</integer>
  <key>AutoLaunchedApplicationDictionary-managed</key>
  <array>
    <dict>
      <key>Path</key>
      <string>/Applications/Sallyport.app</string>
      <key>Hide</key>
      <false/>
    </dict>
  </array>
</dict>
```

它能避免一种平常却代价高昂的故障：软件包安装了，应用出现在 /Applications 中，开发者以为网关正在保护代理。实际上，应用登录后从未启动，因此 MCP shim 无法完成请求，或者用户根本看不到审批卡片。入职检查必须测试真实的用户登录，而不能只测试 MDM 代理下的安装。

LaunchDaemon 仍有合理用途，但不要让它进入保险库路径。只有当你能说明任务为什么必须在没有图形界面的会话中运行，以及为什么需要 root 权限时，才使用它。“我们想让它更可靠”不是答案。用户会话中的应用同样可以可靠运行，不必假装自己是机器服务。

## 更新环应测试真实的开发工作

当每个更新环都回答不同的运行问题时，更新环才有用。当它们变成一种永远礼貌地推迟更新的方式时，就失效了。

先建立一个小型内部环。让负责打包应用、维护 MDM 配置和排查开发者工作站问题的人员加入其中。他们的任务是发现安装失败、启动行为异常、意外的权限提示，以及从上一生产版本升级时的问题。

下一个环应包括使用不同工具和访问方式的开发者。加入通过代理使用 HTTP API 的人、使用 SSH 的人、经常切换网络的人，以及使用普通受管理账户而非本地管理员权限的人。你要寻找的是实际使用中的差异，而不是一群会原谅所有粗糙体验的爱好者。

生产环是版本成为默认版本的阶段。只针对明确原因保留一个小型紧急保留组，并指定负责人和到期日期。“这个人很忙”不是部署政策。它只会让不受支持的版本永久存在。

把发布当作状态机，而不是日历仪式：

1. 在内部环安装签名软件包，并确认软件包和应用都通过评估。
2. 让每位测试用户退出登录再重新登录，然后确认网关在其会话中启动。
3. 通过代理路径执行无害的真实操作，如果这些通道在范围内，则分别执行一次 HTTP 请求和一次 SSH 命令。
4. 在审查失败、支持工单和回滚需求后，将同一个软件包推进到试点环。
5. 只有在上一版本的升级路径正常后，才推广到生产，而不是只验证全新安装。

不要把成功定义为“MDM 控制台显示已安装”。应将成功定义为一系列可观察事件：软件包收据存在，签名应用通过评估，应用在登录后启动，开发者可以解锁自己的保险库，代理能够连接本地 shim，并且获准的操作可以返回结果。

在发布前明确回滚方式。对于普通应用更新，回滚应意味着重新部署之前的签名软件包，恢复之前的必需版本分配，并检查旧应用能否安全读取本地状态。它不应意味着让开发者在文件夹之间拖动应用，同时由支持人员猜测他们安装了哪个构建版本。

Apple 的声明式应用和软件包管理可以在受支持的受监管 Mac 上将软件包定义为必需或可选，声明式应用管理会优先于重叠的安装命令。不要向同一个目标发送两种管理机制，然后希望设备选择你想要的那一种。在每个更新环中选择控制方法，并记录下来。

## 独立保险库会改变入职和恢复流程

每位开发者使用独立保险库，会让首次运行成为一次安全流程，而不是自动化缺陷。用户必须决定哪些凭据属于这台 Mac，设备也必须证明用户能够解锁这些凭据。

这正是重点。本地代理网关可以接收 Claude Code 或其他支持 MCP 的代理发来的请求，但代理不应收到明文凭据，也不应收到日后可以滥用的伪占位符。网关应执行 HTTP 或 SSH 操作，然后返回结果。

在 Sallyport 中，固定的决策阶梯很有用，因为它避免了让每个团队都必须学习和审计一套策略语言。锁定的保险库会拒绝所有操作。新的代理进程默认获得会话授权，标记为每次调用审批的凭据则会在每次使用时请求批准。这些控制解决的是不同问题，应训练开发者在不同情境下分别使用它们。

当笔记本无人看管或开发者完成工作后，使用保险库门控。使用会话授权来识别新启动的代理运行，然后再给予它较大的操作空间。对于每次使用都值得人工关注的凭据，例如生产管理令牌或敏感 SSH 身份，使用每次调用审批。

不要因为听起来更安全，就要求开发者把所有凭据都设置为每次调用审批。反复出现的提示会让人习惯于不阅读就批准。应把阻力放在错误请求会造成实际影响的地方，让普通的低风险开发凭据处于会话级边界之下。审批疲劳是设计失败，不是团队重视安全的证据。

恢复流程同样需要清晰的边界。IT 可以重新安装网关应用，并修复其受管理的启动状态。IT 不能也不应从 MDM 记录中恢复开发者保险库的内容。如果 Mac 被替换，开发者应通过源系统获取新凭据，或遵循组织批准的凭据迁移流程。这可能不方便，但仍比把设备群管理数据库当成生产令牌的备份保险库更安全。

部署前记录所有权表：

| 事件 | 开发者 | IT 或终端团队 | 服务所有者 |
| --- | --- | --- | --- |
| 新 Mac 设置 | 解锁保险库并添加获授权凭据 | 安装应用和登录配置 | 授予初始凭据 |
| 代理行为异常 | 撤销会话并锁定保险库 | 确认设备和应用状态 | 必要时撤销凭据 |
| 应用更新失败 | 报告可见行为和版本 | 修复软件包分配或回滚 | 通常无需操作 |
| 设备更换 | 获取新凭据或执行获批迁移 | 退役旧 Mac 并部署新设备 | 轮换或重新签发访问权限 |

这张表能避免发布过程中最糟糕的支持电话：开发者说“我的代理失去访问了”，终端支持说“应用已经安装”，服务所有者则以为别人已经恢复了某个秘密，而实际上从来没有人应该拥有它。

## 清单和操作证据回答的是不同问题

MDM 清单告诉你受管理应用是否到达设备。它无法说明安装后 AI 代理请求了什么、哪项权限获得了批准，或哪个 API 调用实际运行。

保留两类记录，并在需要时将它们关联起来。MDM 记录应提供硬件身份、管理状态、分配的应用版本、安装时间戳和移除状态。网关的审计轨迹应提供会话、单个操作和验证结果。如果把它们合并到一张表格中，你要么会向终端工作人员暴露过多运行细节，要么会让事件响应人员缺少所需证据。

Sallyport 会在 Sessions 日志中记录代理运行，在 Activity 日志中记录单个操作，两者都从同一份加密哈希链审计日志中生成。它的 `sp audit verify` 命令可以对密文离线检查哈希链，不需要保险库密钥。这意味着调查人员可以检查记录是否被篡改，而无需打开用户的秘密。

发生事件或进行发布复盘时，可以采用简单的收集流程：

```sh
sp audit verify
```

有用的结果应当先清楚显示通过或失败状态，然后显示检查范围，例如：

```text
Audit chain: valid
Records checked: 184
First record: 2026-07-01T14:22:09Z
Last record: 2026-07-22T09:15:44Z
```

具体字段取决于已安装的命令版本，因此应将原始输出与应用版本和设备标识符一起保存。不要因为正在收集审计结果，就复制开发者的保险库内容、代理提示转录或无关的终端历史记录。

这种分离也能改善日常支持。如果 MDM 控制台显示预期版本，但开发者说某个操作被拒绝，应检查网关的本地状态和审批路径。如果审计记录显示某个操作已经执行，但 MDM 没有当前安装记录，应调查过期的设备记录、最近退役的 Mac 或未受管理的安装。每个系统都有明确的工作范围，让它把自己的工作做好。

## 设备退役应先处理访问，再擦除设备

当 Mac 易主、丢失或离开公司时，应在擦除设备或解除组织所有权之前结束它的访问关系。擦除可以清理磁盘，却不能证明云凭据、活动代理会话或设备分配已经得到正确处理。

对于正常的公司设备返还，应按以下顺序操作：

1. 确认设备、最后一位分配用户、MDM 记录，以及设备是否仍在回连。
2. 撤销活动网关会话，并让服务所有者轮换或撤销可能在其他地方仍然有效的凭据。
3. 移除受管理应用；如果 Mac 仍在线并能接收命令，也可以将其标记为待移除。
4. 按照保留规则保存最低限度的管理和审计证据。
5. 通过组织批准的返还或重新分配流程擦除 Mac。

不要仅仅因为员工离职，就将 Mac 从 Apple Business Manager 中释放。释放是硬件所有权的决定，适用于已经出售、无法找回，或不再归组织控制的设备。Apple 警告说，释放操作无法通过通常的分配路径撤销，会阻止设备今后再次分配给 MDM，并且释放后必须擦除并恢复设备。Apple 还警告，不要释放送去维修的设备，因为替换设备可能无法回到 Apple Business Manager。

对于重新分配的公司 Mac，应保留组织注册状态，擦除设备，然后通过标准流程为下一位用户注册。对于采用用户注册模式的个人设备，应按照约定的政策移除管理，确认哪些受管理应用和设置会消失，并让开发者在交接前移除自己的凭据。Apple 指出，对于用户注册的设备，取消注册可能会移除受管理应用和内容，而个人应用和设置仍会保留。

丢失的 Mac 需要更快的处理分支。先撤销凭据和会话，因为设备可能永远不会重新上线。然后使用设备管理控制以及服务所有者的撤销流程。等待应用移除命令送达并不等于完成遏制。

## 成功的发布应该平淡无奇

最好的本地网关发布不会引发太多波澜，因为每条边界都很清楚。MDM 团队交付签名应用并控制其生命周期。开发者将凭据保存在本地保险库中，并能看到代理何时请求权限。服务所有者在源头授予和撤销访问。审计轨迹记录操作，但不会变成秘密倾倒区。

从最能暴露错误假设的测试开始：注册一台干净的受管理 Mac，以普通开发者身份登录，通过 MDM 安装生产软件包，退出登录再重新登录，在本地添加一个非关键凭据，执行一次无害的代理操作，验证其审计记录，然后移除应用，并从全新注册状态重复整个流程。如果这套流程需要未记录的人工操作、隐藏的 root 脚本或复制的凭据文件，就应在增加一百台设备之前修好发布流程。
