适用对象:平台运维、企业网络、安全、数据库和验收人员。本手册描述可在本地验证的工程基线;当前最高状态为
ENGINEERING_COMPLETE_ENTERPRISE_EDGE_UAT_BLOCKED。enterprise_source_1、
enterprise_source_2、network、security_legal 均为 TBD_EXTERNAL,不得把本手册的本地验证解释为真实企业验收。
0600;根目录、queue、artifacts、releases、secrets 目录权限 0700。raw、recent_detail、restricted 仅边缘保存;控制面只接受 desensitized_metadata、statistics、lineage、evidence、health_summary、diagnostic_summary。原始数据保留周期由企业制度批准;近期明细工程上限 365 天;控制面事件按已批准最小保留期执行。20260809_479。应用角色不能创建/轮换/撤销 signing key;仅 migration/security-owner 路径可写受控 signing state。零主机依赖基线(低于该版本必须先由平台运维完成兼容性评估):Docker Engine 26、 Docker Compose 2.27、Python 3.11、OpenSSL 3、SQLite 3.40、curl 8 和 cosign 2.2。 主机不安装数据库驱动、企业来源 SDK 或编译器;这些只能进入经签名的 Edge 镜像。
init 参数、edge.env 和 Compose user 必须使用同一组 UID/GID;不一致时工具拒绝继续,只有 root 可显式使用 --allow-chown 安全调整既有空目录所有权。以下示例固定使用绝对根目录 /opt/dataops-edge;如企业批准其他目录,逐条替换并重新留证,不得使用相对路径。
发布包必须来自受控制品库。下载到空的 /opt/dataops-release,先核对制品库登记的
tar SHA-256 和离线签名,再展开并验证包内每一个文件。下面的公钥、签名和摘要均是
企业批准后注入的占位路径,不能用仓库示例代替:
sudo install -d -m 0700 /opt/dataops-release
cd /opt/dataops-release
sha256sum dataops-platform-release-<VERSION>.tar.gz
cosign verify-blob --key /etc/dataops/trust/release-cosign.pub \
--signature dataops-platform-release-<VERSION>.tar.gz.sig \
dataops-platform-release-<VERSION>.tar.gz
sudo tar -xzf dataops-platform-release-<VERSION>.tar.gz -C /opt
cd /opt/dataops-platform
sha256sum -c SHA256SUMS
任一步失败:隔离下载文件、记录制品 digest 和失败码、从受控制品库重新取件;禁止 跳过验签、手工改包或继续启动。以上只证明交付完整性,不证明企业来源/网络 UAT。
sudo python3 /opt/dataops-platform/scripts/edge_gateway_admin.py init \
--root /opt/dataops-edge \
--runtime-uid 65532 \
--runtime-gid 65532
sudo python3 /opt/dataops-platform/scripts/edge_gateway_admin.py csr --root /opt/dataops-edge --common-name edge-prod-zone-a
验收:JSON 输出仅含路径、指纹和状态;/opt/dataops-edge/secrets/client.key 为 0600,CSR 签名有效,stdout/stderr/审计没有私钥内容。失败时停止注册,隔离并销毁不完整材料后重新初始化;不得降低权限或上传 key。
由企业 CA 对 /opt/dataops-edge/certificates/client.csr 签发客户端证书。将证书、CA bundle、CRL 写入批准证书目录并设为只读挂载源。验证证书用途、有效期、链、CRL 和 CSR/key 公钥一致:
python3 /opt/dataops-platform/scripts/edge_gateway_admin.py register-manifest \
--root /opt/dataops-edge \
--certificate /opt/dataops-edge/certificates/client.pem \
--ca-bundle /opt/dataops-edge/certificates/ca.pem \
--crl /opt/dataops-edge/certificates/control-plane.crl.pem \
--gateway-id 11111111-1111-4111-8111-111111111111 \
--environment production \
--network-zone zone-a \
--policy-digest aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
--control-origin https://control.example.invalid
工具只写固定目标 /opt/dataops-edge/registration/registration.json,不会覆盖 secrets、certificates、CSR、queue 或 releases。它校验证书有效期、clientAuth EKU、key/CSR/certificate 公钥一致、CA 签名、CRL 签名/有效期/撤销状态;相同内容重放幂等,已有不同内容则冲突。注册材料只含 digest、公钥绑定和 CA/CRL 证据字段,不含本地绝对路径、私钥或 credential。通过受批准的人工/平台通道提交它和一次性注册令牌;令牌只能使用一次且失败绑定不得消耗。控制面返回的 edge credential 立即写入 /opt/dataops-edge/secrets/control-credential(0600),不得进入 shell history、环境文件或工单正文。
下列命令均使用占位符,执行前由平台运维把 origin、ID 和 digest 替换为批准值。
Bearer、enrollment token 和 edge credential 只放在 0600 header 文件;不得把值
直接写进命令行。human-auth.headers 由企业统一认证流程生成,本手册不生成弱口令。
先以 umask 077 创建以下闭合请求文件;字段外内容一律拒绝。示例 UUID、digest、
deadline 和 lease 只是占位,必须替换为控制面返回或批准台账中的精确值:
// /run/operator/enrollment-request.json
{"gateway_name":"edge-prod-zone-a","environment":"production","network_zone":"zone-a","policy_digest":"<64-hex>","allowed_control_hosts":["control.example.invalid"],"allowed_proxy_hosts":[],"expected_certificate_sha256":"<64-hex>","ttl_seconds":3600}
// /run/operator/rotate-request.json
{"certificate_sha256":"<new-64-hex>","request_id":"rotate-2026-08-zone-a"}
// /run/edge/outcome-request.json
{"environment":"production","network_zone":"zone-a","generation":1,"outcome":"completed","lease_token":"<control-lease>","safe_summary":{"status":"completed"}}
// /run/edge/reconcile-request.json
{"environment":"production","network_zone":"zone-a","generation":1,"limit":50,"cancel_cursor":null,"release_cursor":null}
// /run/edge/release-installed-ack.json
{"environment":"production","network_zone":"zone-a","generation":1,"release_id":"<uuid>","outcome":"installed","safe_summary":{"release_status":"installed","version":"2.0.0"}}
注释行仅用于说明,真正保存时每个文件只保留对应的一行 JSON;运行前用 OpenAPI
EdgeEnrollmentRequest、EdgeRotateRequest、EdgeTaskOutcomeRequest、
EdgeReconcileRequest、EdgeReleaseAckRequest 校验。lease_token 只由 Agent 持久化加密
并自动发送,人工 curl 仅限事故演练,演练文件用后立即销毁。
平台人员创建 enrollment(HTTP 201;验收字段为 enrollment_id、gateway_id、 expires_in、returned_once):
umask 077
curl --fail-with-body --silent --show-error \
--header @/run/operator/human-auth.headers --header 'Content-Type: application/json' \
--output /run/operator/enrollment-response.json \
--data @/run/operator/enrollment-request.json \
https://control.example.invalid/api/datasource/edge/enrollments
enrollment-request.json 必须含 gateway_name、environment、network_zone、policy_digest、
allowed_control_hosts、allowed_proxy_hosts、expected_certificate_sha256。把一次性 token
从响应安全写入 header 文件,既不打印也不进入 shell history:
python3 - /run/operator/enrollment-response.json /run/operator/enrollment.headers <<'PY'
import json, os, sys
value = json.load(open(sys.argv[1], encoding="utf-8"))["data"]["enrollment_token"]
fd = os.open(sys.argv[2], os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(fd, "w", encoding="utf-8") as target:
target.write("X-Edge-Enrollment: " + value + "\n")
PY
边缘以 mTLS 注册(HTTP 201;验收 generation=1、certificate_sha256 精确匹配、 credential_returned_once=true):
curl --fail-with-body --silent --show-error \
--cert /opt/dataops-edge/certificates/client.pem \
--key /opt/dataops-edge/secrets/client.key \
--cacert /opt/dataops-edge/certificates/ca.pem \
--header @/run/operator/enrollment.headers --header 'Content-Type: application/json' \
--output /run/operator/registration-response.json \
--data @/opt/dataops-edge/registration/registration.json \
https://control.example.invalid/api/datasource/edge/register
安全落盘 credential,同时生成 curl 使用的 X-Edge-Credential header;两个文件均
0600,脚本不打印值:
python3 - /run/operator/registration-response.json \
/opt/dataops-edge/secrets/control-credential /opt/dataops-edge/secrets/edge-auth.headers <<'PY'
import json, os, sys
value = json.load(open(sys.argv[1], encoding="utf-8"))["data"]["credential"]
for path, prefix in ((sys.argv[2], ""), (sys.argv[3], "X-Edge-Credential: ")):
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(fd, "w", encoding="utf-8") as target:
target.write(prefix + value + "\n")
PY
shred -u /run/operator/registration-response.json /run/operator/enrollment-response.json
平台人员轮换/撤销(均 HTTP 200;轮换验收 generation 增加且旧 credential 失效; 撤销验收 revoked=true、旧身份随后返回 401/403):
curl --fail-with-body --header @/run/operator/human-auth.headers \
--header 'Content-Type: application/json' --data @/run/operator/rotate-request.json \
https://control.example.invalid/api/datasource/edge/gateways/<GATEWAY_ID>/rotate
curl --fail-with-body --request POST --header @/run/operator/human-auth.headers \
https://control.example.invalid/api/datasource/edge/gateways/<GATEWAY_ID>/revoke
机器 outcome、reconcile 和 installed ACK 必须同时使用 mTLS 与 credential header。
/tasks/{task_id}/outcome 的 {task_id} 是文档占位符,实际替换为已租赁任务 ID:
curl --fail-with-body --cert /opt/dataops-edge/certificates/client.pem \
--key /opt/dataops-edge/secrets/client.key --cacert /opt/dataops-edge/certificates/ca.pem \
--header @/opt/dataops-edge/secrets/edge-auth.headers --header 'Content-Type: application/json' \
--data @/run/edge/outcome-request.json \
https://control.example.invalid/api/datasource/edge/gateways/<GATEWAY_ID>/tasks/<TASK_ID>/outcome
curl --fail-with-body --cert /opt/dataops-edge/certificates/client.pem \
--key /opt/dataops-edge/secrets/client.key --cacert /opt/dataops-edge/certificates/ca.pem \
--header @/opt/dataops-edge/secrets/edge-auth.headers --header 'Content-Type: application/json' \
--data @/run/edge/reconcile-request.json \
https://control.example.invalid/api/datasource/edge/gateways/<GATEWAY_ID>/reconcile
curl --fail-with-body --cert /opt/dataops-edge/certificates/client.pem \
--key /opt/dataops-edge/secrets/client.key --cacert /opt/dataops-edge/certificates/ca.pem \
--header @/opt/dataops-edge/secrets/edge-auth.headers --header 'Content-Type: application/json' \
--data @/run/edge/release-installed-ack.json \
https://control.example.invalid/api/datasource/edge/gateways/<GATEWAY_ID>/releases/ack
outcome 验收为 completed/failed/cancelled 的幂等确认;reconcile 验收 cancel/release cursor 单调且数组不超过 50;installed ACK 验收 release ID、artifact digest 和当前 verified pointer 一致。4xx 先检查绑定/重放,不重建 credential;5xx/断网保留本地 outbox 并按阈值退避,不手工伪造 ACK。
/opt/dataops-platform/deploy/edge/edge.env.example 到 /opt/dataops-edge/edge.env,设为 0600,将 EDGE_RUNTIME_UID/GID 填成初始化时批准的同一数值,只填写目录、不可变镜像 digest 和精确 origin;credential 不写入 env。仓库默认的 dataops-edge:local 只允许本地工程验证,生产必须把 EDGE_IMAGE 和 EDGE_PULL_POLICY=never 固定到已验签 digest。docker build --pull=false -f deploy/edge/Dockerfile.edge -t dataops-edge:<VERSION> .
docker image inspect --format '{{.Id}}' dataops-edge:<VERSION>
# 企业制品库路径:先由安全方验证签名,再拉 immutable digest
cosign verify --key /etc/dataops/trust/image-cosign.pub \
registry.example.invalid/dataops-edge@sha256:<IMAGE_DIGEST>
docker pull registry.example.invalid/dataops-edge@sha256:<IMAGE_DIGEST>
deploy/edge/edge.config.example.json 复制为 /opt/dataops-edge/config/edge.json。
deploy/edge/edge.config.schema.json 是该文件的完整闭合 schema:除 schema 所列字段外均拒绝。
credential、task/release 公钥和 server CRL 路径不在 edge.json;它们分别从
secrets/control-credential、signing/*.json 和 Compose 固定只读挂载注入。校验:python3 -c 'import json,jsonschema; s=json.load(open("deploy/edge/edge.config.schema.json")); d=json.load(open("/opt/dataops-edge/config/edge.json")); jsonschema.Draft202012Validator(s,format_checker=jsonschema.FormatChecker()).validate(d)'
sudo install -d -m 0700 -o 65532 -g 65532 /opt/dataops-edge/{config,queue,artifacts,releases,secrets,certificates,signing}
chmod 0600 /opt/dataops-edge/config/edge.json /opt/dataops-edge/secrets/client.key
find /opt/dataops-edge -xdev -type d -exec stat -f '%Su:%Sg %Lp %N' {} \;
find /opt/dataops-edge -xdev -type f -exec stat -f '%Su:%Sg %Lp %N' {} \;
Linux 上将最后两条 stat -f 替换为 stat -c '%U:%G %a %n'。验收要求运行 UID/GID
可读 config/cert/key/signing,只能写 queue/artifacts/releases;其他账号不可读 key/credential。
docker compose --env-file /opt/dataops-edge/edge.env -f /opt/dataops-platform/deploy/edge/docker-compose.edge.yml config
docker compose --env-file /opt/dataops-edge/edge.env -f /opt/dataops-platform/deploy/edge/docker-compose.edge.yml up -d
启动后最多等待 120 秒。首次 health 必须是 healthy;degraded 只允许在批准的
断网演练中出现,stopped 表示身份/CRL fail-stop,不能强行复活:
healthy=0
for n in $(seq 1 24); do
docker compose --env-file /opt/dataops-edge/edge.env \
-f /opt/dataops-platform/deploy/edge/docker-compose.edge.yml ps
docker compose --env-file /opt/dataops-edge/edge.env \
-f /opt/dataops-platform/deploy/edge/docker-compose.edge.yml exec -T edge-gateway \
python -m app.edge_gateway.healthcheck && { healthy=1; break; }
sleep 5
done
if [ "$healthy" -ne 1 ]; then
docker compose --env-file /opt/dataops-edge/edge.env \
-f /opt/dataops-platform/deploy/edge/docker-compose.edge.yml stop edge-gateway
exit 1
fi
docker compose --env-file /opt/dataops-edge/edge.env \
-f /opt/dataops-platform/deploy/edge/docker-compose.edge.yml exec -T edge-gateway \
python -c 'import json; print(json.load(open("/run/edge/tmp/health.json"))["status"])'
验收:无 ports/expose;进程 UID 非 0;rootfs 只读;capabilities 全部移除;证书、secret、signing 公钥只读;仅 queue/artifact/release 可写;health.json 输出只含有界状态,不含 credential、key、原始行、SQL 或路径。120 秒未健康则停止容器、保留 health/计数/digest,检查 CRL、证书、exact origin 和文件 owner;禁止扩大 host allowlist。
只读诊断统一使用下列命令;输出只有 queue/release/lease/cursor/outcome/artifact cleanup 计数和 digest,不含本地路径或秘密:
python3 /opt/dataops-platform/scripts/edge_gateway_admin.py status --root /opt/dataops-edge
人工介入阈值由企业可观测负责人确认,未批准更宽阈值前使用以下上限:
| 信号 | 告警 | 人工介入阈值 | 安全动作/恢复验收 |
|---|---|---|---|
| 控制面连续失败 | 3 次或 60 秒 | 10 次或 15 分钟 | 保持 pull-only,禁止换 host;恢复后先 reconcile |
| queue 待发事件 | 1,000 | 10,000 | 冻结新任务;确认 ACK 后再降水位 |
| queue 运行/lease | 最老 5 分钟 | 最老 15 分钟 | 停止新 lease,验证 fencing,不手工删行 |
| queue/artifact 分区 | 剩余 20% | 剩余 10% | 冻结采集;先 legal hold/retention 决策再清理 |
| server CRL | 距 nextUpdate 24 小时 | 距 nextUpdate 1 小时或已过期 | fail-stop;更新并验签后重启 |
| release rollback intent | 任意存在 | 5 分钟未清理 | 禁止新发布;按 intent/history 恢复 |
每次告警记录 gateway digest、时间、计数、版本和批准工单号;不得附带源记录、SQL、 credential 或证书私钥。阈值恢复验收要求连续 3 个心跳 healthy、queue 水位下降、 reconcile cursor 单调和无新签名/身份错误。
当前工程基线没有可绕过签名/租约的“强制执行”入口。冻结新 pull 的唯一受控方式是 停止 Agent;控制面可继续登记 cancel-requested。恢复时启动 Agent,启动循环会先完整 reconcile,再拉取新任务:
COMPOSE=/opt/dataops-platform/deploy/edge/docker-compose.edge.yml
ENV=/opt/dataops-edge/edge.env
docker compose --env-file "$ENV" -f "$COMPOSE" stop edge-gateway
curl --fail-with-body --request POST --header @/run/operator/human-auth.headers \
https://control.example.invalid/api/datasource/edge/tasks/<TASK_ID>/cancel
sqlite3 -readonly /opt/dataops-edge/queue/edge-queue-v2.sqlite3 \
"SELECT status,count(*) FROM edge_tasks GROUP BY status; SELECT status,count(*) FROM edge_outbound_events GROUP BY status;"
docker compose --env-file "$ENV" -f "$COMPOSE" up -d edge-gateway
drain 完成条件:task 不含 pending/leased,event/outcome 不含 pending/sending,
artifact cleanup 不含 deleting,status 显示 active lease=0。若不能 drain,保持容器
停止,保留 queue 副本和 digest;不得直接改状态或删行。恢复后必须观察 reconcile
cursor 前进、cancelled outcome ACK 和待发事件下降,再解除变更冻结。
轮换先生成 pending key/CSR,不覆盖当前有效材料。相同 request-id 和相同 digest 幂等返回;相同 request-id 的不同输入失败。
python3 /opt/dataops-platform/scripts/edge_gateway_admin.py rotate \
--root /opt/dataops-edge \
--common-name edge-prod-zone-a \
--request-id rotate-2026-08-zone-a \
--expected-current-certificate-digest bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
CA 签发 pending CSR 后,先在控制面调用 rotate 并取得新 generation/credential 的服务端批准证据,再原子切换证书/credential;旧材料只保留最多 3 份加密备份并在观察期后销毁。失败时继续使用尚未过期且未撤销的当前 generation;不得半切换。
切换前先把新证书、新私钥和新 credential 分别写入同一文件系统的 .next 文件并设
0600,验证证书/私钥、公钥、generation 和服务端响应完全匹配。然后停止容器,逐个
原子 rename;容器停止期间任何一步失败都从 .previous 恢复,不能以半套身份启动:
COMPOSE=/opt/dataops-platform/deploy/edge/docker-compose.edge.yml
ENV=/opt/dataops-edge/edge.env
docker compose --env-file "$ENV" -f "$COMPOSE" stop edge-gateway
install -m 0600 /run/operator/client.pem.next /opt/dataops-edge/certificates/client.pem.next
install -m 0600 /run/operator/client.key.next /opt/dataops-edge/secrets/client.key.next
install -m 0600 /run/operator/control-credential.next /opt/dataops-edge/secrets/control-credential.next
mv /opt/dataops-edge/certificates/client.pem /opt/dataops-edge/certificates/client.pem.previous
mv /opt/dataops-edge/secrets/client.key /opt/dataops-edge/secrets/client.key.previous
mv /opt/dataops-edge/secrets/control-credential /opt/dataops-edge/secrets/control-credential.previous
mv /opt/dataops-edge/certificates/client.pem.next /opt/dataops-edge/certificates/client.pem
mv /opt/dataops-edge/secrets/client.key.next /opt/dataops-edge/secrets/client.key
mv /opt/dataops-edge/secrets/control-credential.next /opt/dataops-edge/secrets/control-credential
docker compose --env-file "$ENV" -f "$COMPOSE" up -d edge-gateway
120 秒内未 healthy 或新 generation 绑定失败:停止容器、把三个 .previous 原子改回
固定文件名并恢复旧 generation 配置;只有旧证书尚未过期且未撤销时才允许回退。成功
连续 3 个心跳后再按企业加密介质制度销毁 response、.previous 和临时 header;普通
文件系统上的 shred 不能替代加密磁盘/密钥销毁证据。
撤销状态查询:
python3 /opt/dataops-platform/scripts/edge_gateway_admin.py revoke-status --root /opt/dataops-edge
python3 /opt/dataops-platform/scripts/edge_gateway_admin.py revoke-status \
--root /opt/dataops-edge \
--server-status /opt/dataops-edge/state/server-revocation.json \
--trusted-key status-key-2026=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \
--expected-gateway-id 11111111-1111-4111-8111-111111111111 \
--expected-generation 1 \
--expected-certificate-digest bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
服务端文件必须是 canonical Ed25519 签名 envelope,并精确绑定 gateway、generation、证书 SHA-256、status、issued/expires 和 key-id;伪签名、过期、跨网关或绑定不符均拒绝。若可信证据为 revoked,工具写 REVOKED_STOP;本地 revoked 是终态,即使后续收到 active 也不会自行解除。停止容器、隔离本机、保留安全日志并通知控制面。恢复必须新建 enrollment、证书和 credential;禁止清除标记继续复用旧身份。
控制面签名任务创建只接受完整 EdgeTaskContract,签名由服务端产生,操作者不提交
signature。当前镜像内置的唯一可执行任务是工程自检 quality/quality-evaluation/evidence;
企业来源 collect/profile/lineage/controlled_query handler 仍为 TBD_EXTERNAL,在验收接入
前不得下发。工程自检请求示例:
umask 077
cat >/run/operator/edge-self-check-task.json <<'JSON'
{"task_id":"runtime-self-check-20260809","gateway_id":"11111111-1111-4111-8111-111111111111","environment":"production","network_zone":"zone-a","purpose":"quality-evaluation","classification":"evidence","task_type":"quality","contract_version":1,"deadline_at":"2026-08-09T16:00:00Z","attempt":1,"idempotency_key":"runtime-self-check-20260809","policy_digest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
JSON
curl --fail-with-body --silent --show-error \
--header @/run/operator/human-auth.headers --header 'Content-Type: application/json' \
--data @/run/operator/edge-self-check-task.json \
https://control.example.invalid/api/datasource/edge/tasks
把 deadline、gateway/policy 绑定替换为当前批准值。验收为控制面返回 task_id/digest,
Agent 取得签名任务后本地事件为 evidence、payload 仅含 check_count/passed_count/scope/
status,event exact ACK 后 queue 待发数归零。任何其他操作在企业 handler 接入前应返回
execution_boundary_rejected,不能为通过测试而改成空成功。
先下载到 /opt/dataops-edge/releases,再验证完整 Ed25519 manifest、artifact SHA-256、deadline、artifact basename、当前/rollback version 和 key-id:
python3 /opt/dataops-platform/scripts/edge_gateway_admin.py release-check \
--root /opt/dataops-edge \
--manifest /opt/dataops-edge/releases/release-manifest.json \
--artifact /opt/dataops-edge/releases/edge-agent-2.0.0.bin \
--trusted-key release-key-2026=cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc \
--current-version 1.0.0
只有 status verified 的候选可进入安装。工具会把已验签 manifest、artifact 和 bundle 记录分别 fsync 后原子写入 /opt/dataops-edge/releases/verified/<manifest_digest>/;同 digest 仅在整个受控 bundle 再验证一致时幂等返回。该目录外的“verified JSON”、验签后被篡改 artifact、manifest replay 改 digest、降级、过期、路径穿越、符号链接和任意命令均失败。安装前备份 queue 数据库、执行健康检查,再由控制面签收 installed。
正常容器升级由运维在 Agent 已停止且 queue 已 drain 后,把 EDGE_IMAGE 改成已验签
不可变 digest,再启动候选。current.json 只记录已验证 bundle,不替代镜像 digest:
docker compose --env-file /opt/dataops-edge/edge.env \
-f /opt/dataops-platform/deploy/edge/docker-compose.edge.yml stop edge-gateway
sed -i.bak 's#^EDGE_IMAGE=.*#EDGE_IMAGE=registry.example.invalid/dataops-edge@sha256:<IMAGE_DIGEST>#' \
/opt/dataops-edge/edge.env
docker compose --env-file /opt/dataops-edge/edge.env \
-f /opt/dataops-platform/deploy/edge/docker-compose.edge.yml up -d edge-gateway
python3 /opt/dataops-platform/scripts/edge_gateway_admin.py status --root /opt/dataops-edge
连续 3 次 healthy、queue schema v2、签名自检 event ACK 和 current.json/候选 manifest
一致后才发送 installed ACK;失败立即恢复 edge.env.bak 并按下述回滚程序处理。不要用
可变 tag,也不要把 release-check 的 verified 状态当成已经安装。
发布状态机固定为 downloaded -> verified -> candidate -> installed -> ACK;任一步失败
转入 failed。只有新进程无法启动、120 秒无 healthy、queue schema 不是 v2 或控制面
身份绑定失败,且上个 verified bundle 仍在有效 deadline 内,才允许自动回滚。数据
语义/来源质量异常不得自动回滚,必须由数据负责人和事故指挥批准人工回滚。
安装前记录 status、备份 queue,停止容器,原子更新 current 指针后以相同 Compose
启动;启动成功后才发送 installed ACK。固定恢复文件为
/opt/dataops-edge/state/rollback-intent.json、
/opt/dataops-edge/state/rollback-history/<request_digest>.json 和
/opt/dataops-edge/releases/current.json。恢复检查命令:
python3 /opt/dataops-platform/scripts/edge_gateway_admin.py status --root /opt/dataops-edge
sqlite3 /opt/dataops-edge/queue/edge-queue-v2.sqlite3 'PRAGMA quick_check; PRAGMA user_version;'
docker compose --env-file /opt/dataops-edge/edge.env \
-f /opt/dataops-platform/deploy/edge/docker-compose.edge.yml up -d
若 status 显示 rollback intent:禁止发布新版本;按 intent 中 expected current/target 重新执行完全相同的 rollback 请求。相同请求会完成/清理可恢复 intent;任何字段不同 都会冲突。不可手删 intent/history/current。恢复后必须验证 current 指针、bundle manifest/artifact SHA-256、queue quick_check/user_version=2 和连续 3 个 healthy,再 ACK。
回滚需要交互输入 ROLLBACK,或自动化同时提供 --yes、expected-current digest 和 target verified digest:
python3 /opt/dataops-platform/scripts/edge_gateway_admin.py rollback \
--root /opt/dataops-edge --yes \
--expected-current-digest dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd \
--target-digest eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee \
--trusted-key release-key-2026=cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
工具重新验证 current/target 两个受控 bundle 的完整 canonical Ed25519 manifest、可信 key-id、deadline、版本/rollback 绑定和 artifact SHA-256,随后写 rollback intent 并原子提交 /opt/dataops-edge/releases/current.json 指针,fsync 父目录后才返回成功;history 和 intent 使指针已提交但进程崩溃的场景可判定并幂等恢复。已有 history 回放仍须先确认 current pointer,再将遗留 intent 与本次 request digest、expected current、target 做严格等值校验;只清理完全匹配的 intent 并 fsync state 目录,异内容一律冲突。unlink/fsync 中断可由下次同请求继续恢复,且不会阻塞清理后的下一次合法回滚;备份有界为 3 份。回滚后执行健康、queue schema、control binding 和事件重放验证,再上报 outcome=rollback。若任一 bundle 被篡改、expected-current/target 不符或 fsync 失败,停止服务并按 intent/history 判定,不得强行覆盖。
当前本地 queue schema 是 v2;以下仅用于遗留 v1 主机。迁移器会拒绝仍有 pending/leased task 或未 ACK event 的 v1 数据库,因此先冻结拉取并 drain:
python3 /opt/dataops-platform/scripts/edge_gateway_admin.py status --root /opt/dataops-edge
sqlite3 /opt/dataops-edge/queue/edge-queue-v1.sqlite3 \
"SELECT status,count(*) FROM edge_tasks GROUP BY status; SELECT status,count(*) FROM edge_outbound_events GROUP BY status;"
sqlite3 /opt/dataops-edge/queue/edge-queue-v1.sqlite3 'PRAGMA wal_checkpoint(FULL); PRAGMA quick_check;'
sqlite3 /opt/dataops-edge/queue/edge-queue-v1.sqlite3 \
"VACUUM INTO '/opt/dataops-edge/queue-backup/edge-queue-v1-drained.sqlite3';"
sha256sum /opt/dataops-edge/queue-backup/edge-queue-v1-drained.sqlite3
cp -p /opt/dataops-edge/queue-backup/edge-queue-v1-drained.sqlite3 \
/opt/dataops-edge/queue-migration/edge-queue-v2-candidate.sqlite3
EDGE_QUEUE_CANDIDATE=/opt/dataops-edge/queue-migration/edge-queue-v2-candidate.sqlite3 \
/opt/dataops-platform/.venv/bin/python -c \
'import os; from app.core.edge_gateway.queue import SqliteEdgeQueue; SqliteEdgeQueue(os.environ["EDGE_QUEUE_CANDIDATE"])'
sqlite3 /opt/dataops-edge/queue-migration/edge-queue-v2-candidate.sqlite3 \
'PRAGMA quick_check; PRAGMA foreign_key_check; PRAGMA user_version;'
验收必须依次返回 ok、无 foreign-key 行、2;随后在候选副本执行幂等 replay、
cancel、outcome/event ACK 用例,停旧容器后才原子改名为
edge-queue-v2.sqlite3 并启动新 verified release。复制迁移期间禁止写 v1;不得直接
在唯一副本原地迁移。失败时停止候选进程、保留失败摘要,删除候选文件并从 SHA-256
已核对的 v1 drained 备份恢复;旧 verified release 只读该恢复文件。
TBD_EXTERNAL。清理前由数据负责人提供 retention policy digest;存在 legal hold 的 task/artifact 不得
进入 deleting。先用只读查询导出候选 digest(不导出 artifact_ref),审批后由 agent
正常 cleanup worker 执行;禁止用 rm 绕过状态机:
sqlite3 -readonly /opt/dataops-edge/queue/edge-queue-v2.sqlite3 \
"SELECT artifact_digest,classification,retention_until,cleanup_status FROM edge_local_artifacts WHERE cleanup_status IN ('pending','failed') AND julianday(retention_until)<=julianday('now') ORDER BY retention_until LIMIT 100;"
python3 /opt/dataops-platform/scripts/edge_gateway_admin.py status --root /opt/dataops-edge
注意:queue schema v2 没有可由操作者直接修改的 legal_hold 列,且不提供人工强制删除
命令。企业 legal-hold 服务接入、批准记录签名和 cleanup worker 的 hold 消费仍属于
security_legal=TBD_EXTERNAL;接入前一旦出现 hold,必须停止 Agent 以阻止自动 cleanup,
由数据/法务负责人决定延长 retention 后重新生成批准任务,不能直接改 SQLite 或运行
rm。因此本节是候选只读核对,不是“已完成 legal hold 产品能力”的声明。
每个删除必须形成 canonical delete receipt,至少含 artifact_digest、policy_digest、
approved_by、deleted_at、result 和 receipt_digest,并由 outbox 发送 ACK;本地表只有
receipt ACK 后才能保持 deleted。cleanup 中断时,deleting lease 到期后由同一
artifact digest 重试:文件已不存在则生成幂等 delete receipt,不得改写 digest;
失败转 failed 并保留分类化错误码。恢复验收为磁盘水位下降、receipt 已 ACK、没有
legal hold 对象被删。legal hold 解除必须有新批准记录,不能直接改 SQLite。
在停止新 pull、active lease=0 后执行 SQLite 在线一致副本和完整性校验;verified release、config、public signing keys、CA/CRL 分别归档并计算摘要:
sqlite3 /opt/dataops-edge/queue/edge-queue-v2.sqlite3 \
"VACUUM INTO '/opt/dataops-edge/backup/edge-queue-v2.sqlite3';"
sqlite3 /opt/dataops-edge/backup/edge-queue-v2.sqlite3 \
'PRAGMA quick_check; PRAGMA foreign_key_check; PRAGMA user_version;'
sha256sum /opt/dataops-edge/backup/edge-queue-v2.sqlite3
tar -czf /opt/dataops-edge/backup/public-state.tar.gz \
-C /opt/dataops-edge config signing certificates releases state/rollback-history
sha256sum /opt/dataops-edge/backup/public-state.tar.gz
恢复到新建的空专用 leaf,先验证两份 SHA-256,再执行 quick_check/foreign_key_check/
user_version=2、签名公钥和 CRL freshness;恢复 current 指针前重新执行 bundle 验签。
启动时先 reconcile 后 pull。任一校验失败则保持停机、回退到上一份已验证备份;不得
在损坏原件上修表。credential/private key 由企业密钥恢复流程单独注入,绝不包含在
public-state.tar.gz。
职责固定:role-init 仅一次性创建/授予角色,需要 DBA/CREATEROLE;migrator 是
dataops_edge_evidence_owner 成员、负责 Alembic/DDL、无 CREATEROLE;runtime 只持
dataops_app_runtime DML/EXECUTE,不是对象 owner。三个连接串写入 root-only 0600
环境文件,禁止命令行明文。按顺序执行:
set -a
. /etc/dataops-platform/dataops.env
set +a
/opt/dataops-platform/.venv/bin/python -m app.core.edge_gateway.runtime_roles
unset DB_ROLE_INIT_DATABASE_URL
MIGRATION_DATABASE_URL="$MIGRATION_DATABASE_URL" \
/opt/dataops-platform/.venv/bin/alembic -c /opt/dataops-platform/alembic.ini upgrade head
MIGRATION_DATABASE_URL="$MIGRATION_DATABASE_URL" \
/opt/dataops-platform/.venv/bin/alembic -c /opt/dataops-platform/alembic.ini current
DB_ROLE_INIT_DATABASE_URL 只交给一次性 role-init,MIGRATION_DATABASE_URL 只交给
Alembic 子进程,常驻 backend/runner 只获得 DATABASE_URL。用于验收的
/etc/postgresql/pg_service.conf 仅写 host/port/dbname/user,密码放 0600 passfile;
分别执行 PGSERVICE=dataops_migrator psql -X 和
PGSERVICE=dataops_runtime psql -X,不能共用 owner 登录。
current 必须为完整 revision 20260809_479 (head)。随后分别用 migrator/runtime
会话核验职责(以下 SQL 在各自 psql 会话执行,连接由 0600 service/passfile 提供):
SELECT current_user,
has_database_privilege(current_user,current_database(),'CREATE') AS db_create,
has_schema_privilege(current_user,'public','CREATE') AS schema_create,
pg_has_role(current_user,'dataops_edge_evidence_owner','member') AS evidence_owner,
pg_has_role(current_user,'dataops_app_runtime','member') AS app_runtime;
SELECT rolcreaterole, rolsuper FROM pg_roles WHERE rolname=current_user;
验收:role-init 完成后 credential 回收;migrator evidence_owner=true、schema_create=true、 rolcreaterole=false、rolsuper=false;runtime app_runtime=true、schema_create=false、 rolcreaterole=false、rolsuper=false,且不能写 signing authority。任一步失败停止应用, 由 DBA 修 GRANT 后从 role-init/migration gate 重跑;禁止给 runtime 临时 owner/superuser。
控制面 Nginx 用 client CRL 撤销 Edge 客户端;Edge Agent 用 server CRL 验证控制面
服务端,二者不能互换。安全人员先验证 issuer、签名、thisUpdate/nextUpdate,再在同一
文件系统写 .next、fsync 并原子 rename 只读源。Agent 每次请求前核对 CRL digest,
变化时关闭旧连接池并用新 CRL 重建 SSLContext;无需重启即可拒绝新撤销,CRL 过期即
fail-stop,且 SSLContext 必须保持 CRL_CHECK_LEAF:
openssl crl -in /opt/dataops-edge/certificates/control-plane-server.crl.pem -noout -issuer -lastupdate -nextupdate
openssl verify -CAfile /opt/dataops-edge/certificates/ca.pem -crl_check \
-CRLfile /opt/dataops-edge/certificates/control-plane-server.crl.pem \
/opt/dataops-edge/certificates/control-plane-server.pem
openssl crl -in /etc/nginx/edge-client.crl.pem -noout -issuer -lastupdate -nextupdate
install -m 0644 /run/operator/control-plane-server.crl.pem.next \
/opt/dataops-edge/certificates/control-plane-server.crl.pem.next
sync /opt/dataops-edge/certificates/control-plane-server.crl.pem.next
mv /opt/dataops-edge/certificates/control-plane-server.crl.pem.next \
/opt/dataops-edge/certificates/control-plane-server.crl.pem
install -m 0644 /run/operator/edge-client.crl.pem.next /etc/nginx/edge-client.crl.pem.next
sync /etc/nginx/edge-client.crl.pem.next
mv /etc/nginx/edge-client.crl.pem.next /etc/nginx/edge-client.crl.pem
nginx -t
nginx -s reload
验收:新 client CRL 中撤销证书无法握手;有效证书仍通过;Edge health 中 CRL
nextUpdate 新鲜且控制面握手成功;工程测试已证明撤销当前服务端证书后下一次请求立即
失败,不依赖容器重启。更新前保留一份同样已验签且未过期的 .previous;更新失败时
原子恢复该文件,Nginx 执行 nginx -t && nginx -s reload,Edge 在下一次请求自动重建
连接池;
若上一份已过期则保持 fail-stop,不能关闭 CRL 检查。企业 CA/CRL 实际轮换、Nginx/
Agent reload 和抓包证据仍是 TBD_EXTERNAL。