跳到主要内容

设备安装配置——服务器最小化、代理智能化

本文记录了服务器如何描述设备安装任务,以及智能代理如何执行这些任务来部署证书和自签名 CA。

目标

  • 保持服务器简单:描述“做什么”,而不是“如何做”(每个平台由代理负责)。
  • 让代理处理所有平台细节、校验、回滚以及密钥管理。
  • 使用按对象拆分的安装项(copy/exec),统一存放在 JSON 数组中;不需要 config_type
  • 通过 ob_* 字段让安装项直接引用所需资源,确保代理能安全获取。
  • 保证幂等性与安全性(原子写入、备份)并便于审计。

核心原则

  • 服务器与平台无关,无需记录操作系统或发行版信息。
  • 只有代理持有用于解密证书私钥的密钥;服务器永远不会看到明文。
  • 安装项只描述动作:复制文件、执行命令或脚本。检测、验证、回滚等全部由代理实现。
  • 如果用户下发了某个平台无法执行的命令(例如 Windows 上的 bash),代理会明确失败;服务器不会提前阻止。

数据模型影响

  • 表:device_install_configs
    • 仅使用 installs JSON 数组,每个元素就是一个动作项。
    • 通过对 user_device_id 的唯一约束,确保每个设备只有一条配置。
    • 需要分组或标记时,使用安装项自身的 tags 字段;不要依赖额外的表字段。

资源引用模型

  • 每个安装项可以直接引用所需资源:
    • ob_type: "cert""ca"
    • ob_id: 对应 cert_records.idselfca_authorities.id
    • ob_name: 可选的人类可读名称,便于界面和日志展示
  • 代理会使用设备身份和短期令牌从服务器获取所需资源,并在本地完成解密。
  • 证书资源会提供标准虚拟文件名:private.keycertificate.pemchain.pemfullchain.pemcertificate.derbundle.pfxmeta.json
  • 自签名 CA 资源提供:ca.pemca.dermeta.json

代理本地目录结构

代理通过 --config-dir <PATH> 指定可写的工作目录(必填)。所有本地状态、缓存、临时文件均位于此目录。启动时会自动创建缺失的目录并设置安全权限。

<CONFIG_DIR>/
resources/
certs/
<cert_id>/
releases/
<version>/
certificate.pem
private.key
chain.pem
fullchain.pem
certificate.der
bundle.pfx
meta.json
current -> releases/<version>
cas/
<ca_id>/
releases/
<version>/
ca.pem
ca.der
meta.json
current -> releases/<version>
cache/
downloads/
state/
installs_applied.json
resource_index.json
tmp/
logs/

注意:

  • <version> 是代理生成的单调、易读的版本号,例如 2025-09-16T12-00-00Z-<shortSerial>ts-<epoch>-<shortSha>
  • current 指向当前生效版本,更新时在完成验证后原子替换。
  • 若平台不支持符号链接,可使用 current.txt 记录当前版本路径。
  • 目录权限默认为 0750,私钥文件 0600,公开文件 0644。后续如需特殊权限,可通过后续 exec 步骤处理。
  • 默认保留最近 N 个版本(默认 3 个),并在清理时避免删除当前版本。
  • 所有写入均在 <CONFIG_DIR>/tmp 下暂存后原子替换。

虚拟文件名映射

  • copy.from[] 中的虚拟文件名会解析到对应资源当前版本中的具体文件路径。
  • 证书资源:private.keycertificate.pemchain.pemfullchain.pemcertificate.derbundle.pfxmeta.json
  • 自签名 CA:ca.pemca.dermeta.json
  • 代理会逐条执行 (from[i] -> to[i]) 的复制操作,确保原子性与回滚能力。

动作项(JSON 合同 v0)

顶层是数组,每个元素代表一个动作。目前支持 copyexecimport_ca

通用字段

  • id: 字符串,建议填写,用于排序、审计、depends_on
  • type: "copy" | "exec" | "import_ca"
  • enabled?: 默认为 true,为 false 时代理会跳过但仍保留记录
  • continue_on_error?: 默认为 false
  • depends_on?: 字符串数组,引用前置步骤的 id
  • tags?: 字符串数组,用于打标签
  • ob_type?: "cert" | "ca"
  • ob_id?: 数字,资源 ID
  • ob_name?: 字符串,便于阅读

Copy

  • from: 字符串数组,虚拟文件名
  • to: 字符串数组,与 from 一一对应的绝对路径

备注:

  • 协议中没有单独的权限/归属开关,代理会提供安全默认值(原子写入、备份一次、合理权限、幂等)。
  • 如需特殊权限或属主,可在复制后追加 exec 操作。

Exec

  • cmd?: 字符串,通过 shell 执行(POSIX: /bin/sh -c,Windows: cmd.exe /C
  • cmd_argv?: 字符串数组,直接调用(推荐)
  • timeout_ms?: 超时时间,默认 30 秒
  • run_as?: 切换运行用户(POSIX)
  • env?: 环境变量字典
  • verify?: 自定义校验,例如 { "type": "command", "cmd": [...] }

执行行为说明

  • cmd 形式保留 shell 能力,但需注意后台进程等情况。
  • cmd_argv 形式不会经过 shell,更安全,适合生产使用。
  • 代理会捕获 stdout/stderr 写入日志(必要时截断)。
  • timeout_ms 超时会终止子进程。
  • 提供 env 时子进程仅包含这些变量;未提供则继承代理环境。
  • run_as 仅在代理具备权限的 POSIX 平台启用;Windows v0 暂不支持。
  • continue_on_error 为真时失败会记录但继续后续步骤。
  • 若使用 shell 后台执行,代理以 shell 返回码为准,请谨慎使用。

Import CA

  • 负责平台信任库导入,必须包含 ob_type: "ca" 与有效 ob_id
  • 推荐 from: ["ca.pem"] 以确保代理先准备 PEM。
  • enabled: false 时跳过导入和任何复制行为。

示例

[
{
"id": "copy-key",
"type": "copy",
"enabled": true,
"ob_type": "cert",
"ob_id": 12345,
"ob_name": "api.example.com",
"from": ["private.key", "certificate.pem"],
"to": ["/opt/cert/private.key", "/opt/cert/certificate.pem"]
},
{
"id": "trust-store",
"type": "import_ca",
"ob_type": "ca",
"ob_id": 6789,
"ob_name": "corp-root",
"from": ["ca.pem"],
"tags": ["ca-install"],
"enabled": false
},
{
"id": "reload-service",
"type": "exec",
"enabled": true,
"cmd": ["bash", "-lc", "systemctl reload nginx"],
"timeout_ms": 15000,
"depends_on": ["copy-key"],
"verify": { "type": "command", "cmd": ["bash", "-lc", "systemctl is-active nginx"] }
}
]

代理职责

  • 根据 ob_type/ob_id 获取所需资源,使用设备身份和短期令牌。
  • 在本地解密私钥,服务器不会看到明文。
  • 暴露标准虚拟文件名供 copy 使用。
  • depends_on 执行,必要时并行。
  • 保证幂等性、原子写入、权限、备份和回滚。
  • 输出每个动作的状态、耗时、日志摘要。
  • import_ca 需要记录详细的信任库操作日志。

服务器职责

  • 原样存储并下发 installs 数组。
  • 提供认证接口供代理获取资源,并使用短期令牌控制权限。
  • 可做轻量校验,但不强制平台差异。
  • 后续可增加审计表,但不修改该合同。

幂等、校验与回滚

  • 校验类型:
    • file_hash
    • cert_fingerprint
    • command
  • copy 失败会尝试回滚到备份;exec 未来可扩展回滚命令。

安全性

  • 私钥只在设备端解密。
  • 获取资源使用短期令牌,避免长时间存储敏感信息。
  • 推荐通过 cmd_argv 调用可执行文件,减少被 shell 注入的风险。
  • 目标文件的权限默认安全,如需特殊要求可以通过后续命令处理。

迁移注意事项

  • device_install_configs.installs 是唯一真源。
  • 不使用 config_type;需要分组可使用 tags 或未来的绑定模型。
  • 后续可增加运行/状态审计表,不影响该 JSON 合同。

未来扩展

  • variables:简单模板变量。
  • 更丰富的对象选择(按标签、版本)。
  • 计划版本化与设备/设备组绑定。

附录:JSON Schema(节选)

{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DeviceInstallItemsV0",
"type": "array",
"items": { "$ref": "#/definitions/item" },
"definitions": {
"item": {
"type": "object",
"required": ["type"],
"properties": {
"id": { "type": "string" },
"type": { "enum": ["copy", "exec", "import_ca"] },
"enabled": { "type": "boolean" },
"continue_on_error": { "type": "boolean" },
"depends_on": { "type": "array", "items": { "type": "string" } },
"tags": { "type": "array", "items": { "type": "string" } },
"ob_type": { "enum": ["cert", "ca"] },
"ob_id": { "type": "number" },
"ob_name": { "type": "string" },
"from": { "type": "array", "items": { "type": "string" } },
"to": { "type": "array", "items": { "type": "string" } },
"cmd": { "anyOf": [ {"type": "string"}, {"type": "array", "items": {"type": "string"}} ] },
"timeout_ms": { "type": "number", "minimum": 0 },
"run_as": { "type": "string" },
"env": { "type": "object", "additionalProperties": { "type": "string" } },
"verify": { "$ref": "#/definitions/verify" }
},
"allOf": [
{ "if": { "properties": { "type": { "const": "copy" } } },
"then": {
"required": ["from", "to"],
"properties": {
"from": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
"to": { "type": "array", "items": { "type": "string" }, "minItems": 1 }
}
}
},
{ "if": { "properties": { "type": { "const": "exec" } } },
"then": { "required": ["cmd"] } },
{ "if": { "properties": { "type": { "const": "import_ca" } } },
"then": {
"required": ["ob_type", "ob_id"],
"properties": {
"ob_type": { "const": "ca" },
"from": { "type": "array", "items": { "type": "string" } }
}
}
}
]
},
"verify": {
"type": "object",
"oneOf": [
{ "properties": { "type": { "const": "file_hash" }, "expected": { "type": "string" } }, "required": ["type", "expected"] },
{ "properties": { "type": { "const": "cert_fingerprint" }, "expected": { "type": "string" } }, "required": ["type"] },
{ "properties": { "type": { "const": "command" }, "cmd": { "anyOf": [ {"type": "string"}, {"type": "array", "items": {"type": "string"}} ] } }, "required": ["type", "cmd"] }
]
}
}
}

该 v0 合同让服务器保持最小化,只描述 copy/exec 动作,同时赋予平台感知的代理足够能力,安全地在设备上部署证书和自签名 CA。