脚本只负责启动 VPN,不负责置备
启动命令需要一条已经存在且已获批准的连接。把置备和会话控制分开,否则脚本只在你自己的 Mac 上能跑。
你写了一个脚本,用连接名执行 scutil --nc start,它在你自己的 Mac 上能用,但在办公室里每一台刚装好的机器上都失败。脚本并没有坏。它被要求做一件它从来做不到的事:启动一条已存在的连接,与新建一条连接并不是同一个操作。
置备和会话控制是两项工作,各有各的负责人;假设两者相同的流程,只会在写下它的那台机器上通过全部测试。
启动命令究竟在和谁对话
会话控制命令针对的是一份已经存在、并且已经获批准的配置。它请求系统把那条具名连接拉起来,用的是此前保存的服务器设置、身份引用和策略。如果这些从未被安装过,就没有任何东西可以启动。
这就是为什么故障出现在新硬件上,而不是管理员的笔记本上。那台笔记本是几个月前由某个人置备的,而那个人后来忘了这件事。把启动命令想成上季度有人布好线的门铃。按下按钮并不等于布好线路。
VPN 配置的三个负责人
组织管理的配置文件来自设备管理。配置文件带着服务器地址、认证安排和组织要求的策略,而 VPN 管理员就是提供这些东西的人。脚本不应该从列表里找到的显示名称反推出这些设置。
原生的个人 VPN 配置通过 Apple 的 Network Extension API 创建,它覆盖内置的 IPsec 和 IKEv2 协议。那是一个带保存步骤和用户批准的应用程序接口,而不是一条悄悄创建厂商所提供任意隧道类型的 shell 命令。
第三方客户端是第三种情况,有自己的导入格式、自己的管理工具,往往还有自己的特权助手。它的会话归那个厂商的进程所有。找到一个名字相似的系统服务,并不能证明某条系统命令控制着这条隧道;这样假设,会写出在厂商更新客户端之前看起来一直能用的脚本。
原生置备包含一次保存和一次批准
Apple 的模型是载入、修改、保存。应用程序在启动后第一次保存之前先载入既有偏好设置,修改配置,然后再保存。保存无法持久化时会报错,而忽略这个结果,会让一次置备失败在三个步骤之后变成一次连接失败。
第一次保存还要求用户批准这个应用程序。这是一道刻意的安全边界,它意味着新 Mac 上的首次运行是交互式的,尽管之后每一次连接和断开都可以自动化。请把首次运行规划成有人在场的人工任务。第二次运行起再自动化,并在运行手册里写明这一点,免得有人承诺做不到的事。承诺首次运行完全无人值守的部署计划,承诺的是平台并不提供的东西。
Apple 还建议在依赖 API 之前,用配置描述文件验证服务器兼容性。先做这项检查,就能把「服务器不会接受这个客户端」与「客户端从未保存过设置」分开,而这是不同团队的不同工单。
可重复的启动命令可以假设什么
置备之后,脚本可以安全地通过其所有者的接口 定位到那条已存在的连接。只把连接的稳定标识和你想执行的动作交给它,别的都不要给。它不应该去读猜测出来的偏好设置路径,也不应该假设菜单里显示的名字在整个系统里是唯一的。
对于原生连接,像 scutil --nc start 这样具名的会话请求是一项控制操作。它不安装服务器设置,不导入凭据,也不执行首次保存的授权。把它当作链条的最后一步,而不要当作整条链。
把机密留在命令行参数之外,也留在常规诊断输出之外。部署选择了哪种身份机制,启动命令就该依赖哪种;把密码粘进 shell 历史里,在任何评审中都会成为一条发现。
两项验收检查
第一项检查是置备:拿一台刚装好的 Mac,证明配置存在、归正确的管理者所有,并且已完整批准。先做这件事再测连通性,因为在未置备的机器上做连接测试,无法说明脚本的任何问题。
第二项检查是会话:连接、抵达获授权的资源,然后干净断开。现在每个失败都有主了,这正是把两项工作拆开的实际好处。配置缺失是下发问题,保存报错是批准问题,登录被拒是认证问题,启动成功之后超时则是会话层。它们没有一个是「脚本坏了」。