12 KiB
Quickly Configuring SSH Server
在 Git 仓库中独立管理服务器 SSH 公钥,通过 Cloudflare Worker 发布,再由服务器定期同步。
- 优先使用本仓库的
keys/authorized_keys;主清单为空时,可按配置顺序读取备用 HTTPS 公钥源。没有默认 GitHub 账号 SSH Keys 来源。 - 网页、脚本与仓库公钥一起构建发布,不再依赖 GitHub Pages;备用公钥独立标记来源和内容摘要。
- 首页支持浅色 / 深色 / 跟随系统、手机与宽屏,以及 GitHub 风格的代码块复制按钮。
- POSIX Shell 客户端只替换自己的密钥管理区块,保留手工密钥;下载或校验失败保留上次成功的内容。
仓库初始没有服务器公钥,生产构建会拒绝空清单。先填写你独立管理的服务器公钥或显式配置备用源;不要上传私钥,也不要直接拿测试夹具上线。
Quick Usage
以下地址是部署配置中的示例。Fork 后先修改 deployment.json,填写公钥并完成自己的 Worker 部署,再使用自己的域名。执行前建议下载并检查脚本。
安装公钥并设置定时同步:
curl -fsSL 'https://ssh.lamgc.me/script.sh' | sh -s -- -c
新安装默认每 15 分钟同步一次;已有的有效自定义计划会保留。只更新一次:
curl -fsSL 'https://ssh.lamgc.me/script.sh' | sh -s -- -o
预览、帮助与本地状态:
curl -fsSL 'https://ssh.lamgc.me/script.sh' | sh -s -- -o --dry-run
curl -fsSL 'https://ssh.lamgc.me/script.sh' | sh -s -- -h
sh "$HOME/.conf-sshd/conf-sshd.sh" --status
指定计划、取消计划、单独更新已安装脚本:
sh "$HOME/.conf-sshd/conf-sshd.sh" -c '0 */6 * * *'
sh "$HOME/.conf-sshd/conf-sshd.sh" -c false
sh "$HOME/.conf-sshd/conf-sshd.sh" -u
卸载定时同步与脚本,保留密钥及 SSH 服务配置:
sh "$HOME/.conf-sshd/conf-sshd.sh" --uninstall
这些命令用途不同,按需单独执行。网页只提供逐块复制,不把安装、卸载、密码策略合成“执行全部”。
公钥清单与发布
将普通 OpenSSH 公钥逐行放入 keys/authorized_keys,可附注释和空行。构建会规范化换行与编码,逐条调用 ssh-keygen 校验,拒绝重复钥匙、私钥、无效编码和未知行。
支持 ED25519、RSA(至少 2048 位)、ECDSA nistp256/384/521 和 OpenSSH 安全密钥类型。第一版不接受 DSA、证书或 command=、environment= 等 authorized_keys 选项。私钥应始终保留在你的客户端或安全密钥设备上。
配置文件 deployment.json:
baseUrl:Worker 的正式 HTTPS origin,不带路径或末尾斜杠。repositoryUrl:自己的项目仓库地址。defaultCron:新安装的默认同步计划,默认*/15 * * * *。allowEmpty:默认false;明确设为true时,空主清单表示撤销全部,禁止回退到备用源。fallbackSources:默认空数组。可填写最多 5 个公开 HTTPS URL,按顺序尝试;不能带凭据、查询参数或片段,不接受请求端传入的地址。
页面展示的是当前已发布版本,不是每台服务器的实时同步状态。提交合并后还要等待部署成功;服务器会在下一次成功同步时应用。
有序备用源
只有仓库清单为空,且 allowEmpty 不是 true 时,Worker 才依次读取 fallbackSources。取第一个成功且有效的非空公钥集合,不聚合多个源。仓库清单非空始终优先;明确撤销全部时不调用任何备用源。
每个备用源有 3 秒超时(含正文读取)、1 MiB 正文限制、最多 1000 个公钥,并拒绝重定向。非 2xx、空正文、无效 UTF-8、私钥/选项/证书、损坏密钥或重复密钥会跳过并尝试下一个。全部失败时 /ssh.keys 和 /release.json 返回 503,首页说明暂不可用;客户端保留上次成功的密钥。
不设置 Worker 备用公钥缓存,不使用陈旧内容兜底;每个相关请求重新获取,要求上游也正确提供当前数据。首页的一次响应中,公钥、复制内容、来源、数量与内容摘要来自同一个结果。不同时间请求可能看到备用源的不同版本,因此页面分别显示代码修订与公钥摘要,不能只用 Git 提交号代表动态公钥版本。
撤销备用源公钥取决于上游更新、下一次成功请求和服务器同步周期;这不是即时断开连接的机制。主清单为空、未允许明确撤销且未配置备用源时,生产构建仍会拒绝。
明确撤销全部管理密钥
确实需要删除全部受管理密钥时,在同一提交中清空公钥清单,并显式将 allowEmpty 改为 true。发布与新客户端都会检查数量、版本和内容摘要,只有合法的明确空清单才会清空管理区块。
先确认其他恢复登录方式可用。手工放在管理区块之外的密钥不会删除;同一把钥匙若还存在于其他 authorized_keys 来源,仍可能允许登录。删除密钥也不会终止已有 SSH 会话。
网络故障、错误正文、未知协议或校验失败都会保留本地旧密钥并报错。因此断网时不能保证及时撤权。旧版本客户端不支持明确空清单撤权,应先升级。
安全边界
服务器密钥与 GitHub 账号认证密钥相互独立。在仓库里保存公钥不会把它注册为 GitHub 账号登录凭据。
但能修改并发布这个项目的人,以及被配置为备用公钥源的发布者,都能改变使用它的服务器的授权密钥。请保护仓库主分支、CI、Cloudflare 账号和部署权限。HTTPS 保护传输,SHA-256 用于检测截断或版本不一致,不能抵抗发布源本身被攻陷。curl | sh 仍意味着信任发布的可执行脚本;定时任务只拉公钥,不会自动更新脚本。
安装与部署
前提
构建环境需要 Node.js 22+、npm、Git、OpenSSH 客户端工具(ssh-keygen)及 POSIX sh。依赖固定在 package-lock.json。
客户端需要 sh、curl、ssh-keygen、awk、mktemp、stat 和常用 Unix 工具,以及 sha256sum / shasum / openssl 之一;设置定时同步还需 crontab。请自行安装所需软件。脚本不安装包、不启动或重载服务;-n 保留为兼容参数。
目标为 Linux 上的 dash、Bash 的 sh 用法和 BusyBox ash。真实发行版服务管理、SELinux 标签或自定义 AuthorizedKeysFile 不应仅凭单元测试视为已验证。默认目标文件是当前用户的 $HOME/.ssh/authorized_keys;自定义 SSH 授权路径需要人工核对。
一次性配置
- Fork / 克隆仓库,填入自己的服务器公钥和
deployment.json。 - 按需要修改
cf-worker/wrangler.toml中的 Worker 名称,并为baseUrl配置 Worker 自定义域名。无需启用 GitHub Pages。 - 安装依赖、运行检查、构建生产产物:
npm ci
npm test
npm run build
node scripts/verify-release.mjs
- 在 GitHub 的生产环境中配置最小权限的 Cloudflare 部署 token 和账号 ID,对应
CLOUDFLARE_API_TOKEN、CLOUDFLARE_ACCOUNT_ID。凭据只存放在 Secrets,不能提交到代码或公钥清单。 - 准备好正式公钥、域名和权限后,将仓库变量
ENABLE_WORKER_DEPLOY设为true。此后 main 的更新会运行生产发布流程,也可手动触发。
默认关闭自动生产发布,防止合并本次改动时意外上线。PR 验证不会读取部署凭据或发布 Worker。
生产流程先测试和严格构建,再部署,最后核对 /release.json 和 /ssh.keys 的版本与摘要。没有正式密钥、没有备用源且未显式允许空清单时会失败;不会偷偷改用 GitHub Keys 或测试密钥。
发布脚本、网页、仓库公钥和备用源配置属于同一构建产物;动态备用公钥有单独的内容摘要。若要回滚历史版本,先查看公钥差异:回滚可能重新启用已撤销的密钥。部署失败保留旧发布,但不能把这描述为新密钥已生效。
Paths
| 路径 | 内容 |
|---|---|
/ |
浏览器首页;旧 curl/libcurl 请求仍返回脚本(显式请求 HTML 除外) |
/script.sh |
Shell 脚本,推荐所有新命令使用这个明确地址 |
/ssh.keys |
带注释元数据的普通公钥文本 |
/release.json |
当前代码版本、有效公钥来源、数量、摘要和空清单状态 |
动态入口不缓存旧数据;带内容哈希的静态 CSS / JS 可以长期缓存。未知路径返回 404,不能用首页 HTML 代替密钥或脚本错误。
客户端保护措施
- 所有参数先解析;未知选项、缺参数或冲突用途会报错。
- 下载具有 HTTPS 限制、HTTP 失败检测、超时、有限重试和大小限制。
- 响应包含协议版本、Git revision、密钥数量、明确空清单标记和 SHA-256;所有公钥逐条校验。
- 每个用户独占运行锁;临时文件使用不可预测名称、严格权限和异常清理。
- 只识别唯一且配对的原有 BEGIN / END 管理标记,损坏或重复时停止,避免误吞手工密钥。
- 同目录原子替换,写入前保存一个受限备份;重复执行内容相同时不重写文件。
- 拒绝符号链接、不合适的所有权/写权限和异常文件类型。
-c只管理自己的 cron 区块;原版已知命令可迁移,无法可靠判断的记录会停止并提示。- 正常密钥同步不会修改
sshd_config,也不会重载 SSH 服务。
如果运行被不可捕获地终止,锁可能残留。先检查 .conf-sshd/lock/pid 对应的进程及工作状态,再人工处理锁,不能仅凭锁的年龄删除。
Root 密码登录(高级)
保留 -p yes / -p no,必须单独显式使用并以 root 运行。不属于安装/同步默认流程。它只设置 PermitRootLogin,不会替你打开其他认证策略。
脚本会检查原配置、生成临时配置、验证语法与生效值、备份后替换,不会自动 reload/restart。发现 Include / Match 等无法安全判断的组合时,会拒绝修改并请你人工处理。应用服务变更前应保留恢复会话并测试新连接。
从旧版本迁移
- 先建立独立服务器公钥清单并发布新 Worker;或显式配置备用源;不会自动导入 GitHub 账号公钥。
- 检查页面公钥、版本和
/ssh.keys,确认是你的服务器密钥。 - 显式更新已安装脚本,或从新的
/script.sh下载并检查后重新运行-c。 - 验证自定义 cron 仍保留,手工密钥不变,另开 SSH 会话验证登录。
旧客户端对非空的 /ssh.keys 仍有基本格式兼容,但不具备新验证、明确空清单撤权与其他保护。不要因为 Worker 更新了就认为所有客户端已升级。
开发与测试
npm ci
npm test
npm run build:test
npx wrangler deploy --dry-run --config cf-worker/wrangler.toml
build:test 只用于测试/预览,使用公开的 RFC 测试公钥,并在产物中标记 testFixture: true。生产验证脚本拒绝该产物;生产构建只使用仓库公钥和显式配置的备用源,不允许回退到测试夹具。
测试覆盖构建配置与注入边界、逐条密钥验证、Worker 路由/缓存、主题/复制交互、Shell 参数/管理区块/失败保留/cron/自更新等。Shell 测试只在临时 HOME、假命令和测试文件中运行,不操作真实 SSH 配置或服务。
npm run build:test 之后若要部署正式版本,必须重新执行 npm run build 与 node scripts/verify-release.mjs。不得直接部署测试产物。
License
Apache-2.0,见 LICENSE。
客户端会检测写入期间可观察到的手工修改并停止;其他编辑器若不遵守同一锁,仍无法保证操作系统级的原子比较并交换。请避免同时手工编辑授权文件。