边缘设备统一接入平台部署文档
安全连接边缘设备与云端 —— 基于 AWS 的边缘设备统一接入平台,两大能力:远程开发运维(JumpServer 堡垒机隐藏于 CloudFront 之后,车载/办公室等边缘设备经 frp 隧道反向接入,一个 Web 控制台纳管全部资产)+远程监控运维(边缘设备 node_exporter 指标经 SigV4 直写 Amazon Managed Prometheus,Grafana 统一看板)。 Connect edge devices to the cloud, securely — bastion access and metrics collection for edge fleets on AWS. 覆盖:JumpServer v4.x | frp v0.71.x | CloudFront VPC Origins | IAM Roles Anywhere | AMP / Grafana 两部分均按部署顺序组织,各自独立可用;全流程已在真实环境端到端验证。
仓库结构 —— 目录布局 · 脚本清单(运行位置 / 凭证需求 / 执行频次)· 锁定的组件版本
系统总览 —— 平台定位 · 总体架构 · 两大能力 · 典型应用场景
第一部分:远程开发运维(JumpServer + frp + CloudFront)
- 架构与组件 —— 第一部分全景架构、组件清单与流量路径
- 部署准备 —— 前置条件、变量、交付物脚本清单
- 阶段一:JumpServer Web 入口(CloudFront + VPC Origin)
- 阶段二:frp 设备接入(frps → 设备入口 → 设备端 frpc)
- 阶段三:JumpServer 资产纳管(节点树 / 批量导入 / API)
- 阶段四:安全加固(WAF · 源站保护 · 日志)
- 安全模型说明(供客户安全团队评审)
- 运维手册(故障排查 / token 轮换 / 日常操作)
- 成本估算
- 回滚与清理
第二部分:远程监控运维(node_exporter + AMP + Grafana,零长期凭证)
- 监控架构与认证设计 —— Roles Anywhere 凭证链、方案选型
- 云端部署(手动创建 AMP / Grafana workspace + CloudShell 两步:init 引导、device 设备包)
- 设备端接入(一键安装:裸机全自动 / 已有组件增量)
- 监控链路故障排查
- 成本与日常运维(证书轮换 / 系列数控制)
本仓库 = 一份部署文档(本 README)+ 一套可直接执行的交付脚本。脚本按能力分三个目录,全部幂等可重跑,关键组件版本在脚本头部集中锁定,升级只改一处。
.
├── README.md # 本文档(唯一权威说明,两部分按部署顺序组织)
├── edge-cloud-bastion.html # 单页离线版(可直接发给客户,无需网络与 Markdown 渲染器)
├── jumpserver/
│ ├── setup-cloudfront-jumpserver.sh # 阶段一:CloudFront 入口① → JumpServer:80(VPC Origin)
│ └── AWS VPC - jp-demo项目.drawio # 网络拓扑源图(draw.io / diagrams.net 可编辑)
├── frp/
│ ├── setup-frp-server.sh # 阶段二·1:frps EC2 上装 frps(含 token 生成与轮换)
│ ├── setup-frp-cloudfront.sh # 阶段二·2:CloudFront 入口② → frps:7000(wss 接入)
│ ├── setup-frp-client.sh # 阶段二·3:设备端装 frpc(交互 / 批量刷机两种模式)
│ └── test-frp-link.sh # 阶段二·验证:入口②全链路连通性自检(无需真 token)
└── amp-edge-monitoring/
└── setup-amp-edge.sh # 第二部分全部云端操作:init 引导 + device 设备包
| 脚本 | 运行位置 | 需要 AWS 凭证 | 执行频次 |
|---|---|---|---|
setup-cloudfront-jumpserver.sh |
AWS CloudShell(推荐)或本地 CLI v2 | ✅ | 每套环境一次 |
setup-frp-cloudfront.sh |
AWS CloudShell(推荐)或本地 CLI v2 | ✅ | 每套环境一次 |
setup-frp-server.sh |
frps EC2(root) | ❌ | 每套环境一次 + token 轮换时 |
setup-frp-client.sh |
边缘设备(root) | ❌ | 每台设备一次 |
test-frp-link.sh |
任意联网的 macOS / Linux | ❌ | 按需验证 |
setup-amp-edge.sh init |
AWS CloudShell | ✅ | 每账号一次 |
setup-amp-edge.sh device |
AWS CloudShell | ✅ | 每台设备一次 |
install-edge-collector.sh(由 device 子命令动态生成,在设备包内) |
边缘设备(root) | ❌ | 每台设备一次 |
💡 设备端脚本一律零 AWS 凭证需求:frpc 只用 frp token,采集端只用 X.509 设备证书。所有需要 AWS 权限的动作都被收敛到 CloudShell 里执行。
脚本锁定的组件版本(升级即改脚本头部常量,无需改文档):
| 组件 | 版本 | 锁定位置 |
|---|---|---|
| frp(frps / frpc) | v0.71.0 | 三个 frp 脚本的 FRP_VER |
| aws_signing_helper | 1.8.5(附 sha256 校验) | setup-amp-edge.sh 的 HELPER_VERSION / SHA_X86 / SHA_ARM |
| node_exporter | 1.11.1(附官方 sha256 校验) | setup-amp-edge.sh 内嵌安装器的 NE_VERSION |
| Prometheus(agent 模式) | 3.13.2 LTS(附官方 sha256 校验) | setup-amp-edge.sh 内嵌安装器的 PROM_VERSION |
| JumpServer | v4.x(官方 quick_start 安装,本方案不代管其安装) | — |
两部分的独立性:jumpserver/ + frp/(第一部分)与 amp-edge-monitoring/(第二部分)没有任何依赖关系,可只部署其一。同一批设备同时接入两条通道时,两者互不影响。
Edge-Cloud Bastion 是一套构建在 AWS 上的边缘设备统一接入平台:把分散在办公室 NAT 与车辆蜂窝网络之后、没有固定公网 IP 的边缘设备,与云上服务器一起接入统一管理平面。平台提供两大能力——远程开发运维(浏览器点击即连任意设备,全程审计)与远程监控运维(设备指标持续入云,统一看板与告警),分别对应本文档的第一、第二部分。
| 被管理对象 | 所在网络 | 接入方式 |
|---|---|---|
| 云上服务器(WorkNode EC2) | AWS VPC 私有子网 | VPC 内私网直连 |
| 办公室局域网设备 | 客户办公室 NAT 之后 | 设备 frpc 主动外连反向隧道 |
| 车载设备 | 蜂窝网络(普通 SIM,IP 漫游/CGNAT) | 设备 frpc 主动外连反向隧道 |
同一批设备可同时接入两条通道:访问通道解决"连上去操作",监控通道解决"不连上去也随时知道它好不好"。
flowchart LR
U["运维 / 研发用户<br/>(浏览器)"]
subgraph EDGE["现场边缘设备(办公室 NAT / 车辆蜂窝网络,无固定公网 IP)"]
FRPC["frpc<br/>访问通道客户端"]
NE["node_exporter +<br/>Prometheus agent<br/>监控通道采集端"]
end
subgraph AWSC["AWS(公网暴露面 100% 由托管服务承担,EC2 均无公网 IP)"]
subgraph CFL["公网入口层(共享同一 WAF)"]
CF1["CloudFront 入口①<br/>Web 访问"]
CF2["CloudFront 入口②<br/>设备接入 (wss)"]
end
subgraph P1["第一部分:远程开发运维"]
JMS["JumpServer 堡垒机<br/>纳管 · 授权 · 审计"]
FRPS["frps<br/>设备隧道汇聚"]
end
subgraph P2["第二部分:远程监控运维"]
RA["IAM Roles Anywhere<br/>X.509 证书 → 临时凭证"]
AMP["Amazon Managed<br/>Prometheus(AMP)"]
GRAF["Amazon Managed Grafana<br/>(IAM Identity Center 登录)"]
end
end
U -- "① HTTPS 访问" --> CF1
CF1 -- "私网回源" --> JMS
FRPC -- "② wss:443 主动外连" --> CF2
CF2 -- "私网回源" --> FRPS
JMS -- "③ SSH 私网 :600x" --> FRPS
NE -- "④ remote_write + SigV4" --> AMP
NE -. "X.509 换临时凭证" .-> RA
U -- "⑤ 看板 / 告警" --> GRAF
GRAF --> AMP
style JMS fill:#dbeafe,stroke:#1a56db
style FRPS fill:#dbeafe,stroke:#1a56db
style CF1 fill:#f3e8ff,stroke:#7c3aed
style CF2 fill:#f3e8ff,stroke:#7c3aed
style CFL fill:#faf5ff,stroke:#7c3aed,stroke-dasharray:4
style AMP fill:#dcfce7,stroke:#16a34a
style GRAF fill:#dcfce7,stroke:#16a34a
style RA fill:#fef3c7,stroke:#d97706
| 能力 | 解决的问题 | 技术栈 | 对应章节 |
|---|---|---|---|
| 远程开发运维(访问通道) | 无固定公网 IP 的设备如何被安全地"连上去":浏览器点击即连,命令级审计 + 会话录像 | JumpServer + frp + CloudFront VPC Origin + WAF | 第一部分(1-10 章) |
| 远程监控运维(监控通道) | 不登录设备也能掌握其健康状态:CPU / 磁盘 / 温度 / 在线状态秒级入云,统一看板与告警 | node_exporter + Prometheus agent + IAM Roles Anywhere + AMP + Grafana | 第二部分(11-15 章) |
💡 两条通道共享同一套设计哲学:① 一切连接由设备主动外连(frpc wss 与 remote_write 均为出站 443),现场网络零入站开口;② 零长期凭证——人用 IAM Identity Center 登录,机器用 X.509 证书经 Roles Anywhere 换临时凭证,全账号不创建 IAM User / 不下发 AK;③ 公网暴露面 100% 由 AWS 托管服务(CloudFront / AMP 端点)承担,所有 EC2 均无公网 IP;④ 全程可审计——访问通道命令审计 + 录像,监控与入口层 WAF / CloudFront 日志留存。
本方案的直接业务背景:OEM / Tier1 的自动驾驶研发体系中,测试车辆分散在各地路测,数采 / 标定 / 训练资源分布在办公室集群,两端都没有固定公网地址。研发团队需要一条安全、可审计、随时可达的访问通道,支撑数据采集与研发迭代——这正是本系统要建立的通道。
| # | 场景 | 谁在用 | 通过本系统怎么做 |
|---|---|---|---|
| 1 | 路测车辆远程诊断与数采运维 | 数采 / 自动驾驶测试工程师 | 车载域控(数采主机)经蜂窝网络 frpc 常驻在线,工程师浏览器点击车辆资产即进入车机终端:检查数采软件与传感器状态、启停采集任务、查看磁盘水位与日志——无需追车、无需回场 |
| 2 | 采集数据抽检与小样本回传 | 数据工程团队 | 事件片段、日志、标注样本等小体量数据经隧道 SFTP 直接拉回;TB 级整盘数据仍走硬盘回收 / 专线,隧道承担"先看后传"的远程抽检筛选,减少无效回传 |
| 3 | 办公室标定 / HIL / 训练集群统一纳管 | 算法与标定工程师 | 办公室 GPU 训练机、标定台架、HIL 台架经 frpc(或网域网关)接入,与云上资源在同一资产树;出差、居家均从统一入口访问,权限与审计一致 |
| 4 | OEM ↔ Tier1 跨组织协作 | 双方项目团队 | 按 JumpServer 节点授权:Tier1 工程师仅能看到被授权的车辆与台架,命令级审计 + 会话录像全程留痕,满足数据安全与商密管理要求 |
| 5 | 试验车异常应急取证 | 质量 / 试验车队工程师 | 车辆在外出现异常,远程登入固定日志与现场数据,不必等车回场,缩短问题闭环周期 |
💡 该形态对汽车研发场景的关键适配:① 测试车使用普通互联网 SIM(CGNAT、IP 随基站漂移),无法反向寻址——frpc 主动外连模式天然解决;② 办公室 IT 只需放行 443 出站,零入站开口;③ 全程命令审计与会话录像,为智能网联汽车数据合规(采集范围、操作留痕)提供审计依据;④ 资产连通性状态即车辆 / 台架在线状态,免费获得车队在线监控。
边界说明:本通道定位为运维 / 控制平面(交互式终端、任务启停、小流量传输),不承载大规模数采回传的主数据链路——后者按数据量走硬盘回收、专线或 S3 直传方案。
浏览器统一入口远程访问云上服务器、办公室集群与车载设备 —— JumpServer + frp + CloudFront,全程审计
flowchart LR
U["运维 / 业务用户<br/>(浏览器)"]
subgraph AWS["AWS 东京区域(EC2 均无公网 IP)"]
WAF["AWS WAF 共享 Web ACL<br/>IP信誉 · OWASP · SQLi · 速率限制"]
CF1["CloudFront 入口①<br/>Web 访问"]
CF2["CloudFront 入口②<br/>设备接入 (wss)"]
subgraph PRI["私有子网"]
JMS["JumpServer EC2<br/>纳管 · 授权 · 审计"]
subgraph FRPSEC2["frps EC2(兼 WorkNode #1,同一台机器)"]
FRPS["frps<br/>设备隧道汇聚点"]
WN1["WorkNode #1"]
end
WN2["WorkNode EC2 #2"]
WN3["WorkNode EC2 #N(按需扩展)"]
end
S3LOG["S3 日志桶<br/>CloudFront 访问日志 · 90天"]
CWLOG["CloudWatch Logs<br/>WAF 拦截/放行日志 · 90天"]
end
subgraph DEV["现场设备(客户办公室 / 车辆)"]
OFF["办公室设备 frpc"]
CAR["车载设备 frpc"]
end
U -- "① HTTPS 登录" --> CF1 --> JMS
JMS -- "② SSH :22 私网直连" --> WN1
JMS -- "②" --> WN2
JMS -- "②" --> WN3
JMS -- "③ SSH :600x 私网" --> FRPS
OFF -- "④ wss :443 主动外连" --> CF2
CAR -- "④ wss :443 主动外连" --> CF2
CF2 --> FRPS
FRPS -. "⑤ 反向隧道" .-> OFF
FRPS -. "⑤ 反向隧道" .-> CAR
WAF -. "L7 边缘防护" .- CF1
WAF -. "L7 边缘防护" .- CF2
CF1 -. "访问日志" .-> S3LOG
CF2 -. "访问日志" .-> S3LOG
WAF -. "WAF 日志" .-> CWLOG
style JMS fill:#dbeafe,stroke:#1a56db
style FRPS fill:#dbeafe,stroke:#1a56db
style FRPSEC2 fill:#eff6ff,stroke:#1a56db,stroke-dasharray:4
style CF1 fill:#f3e8ff,stroke:#7c3aed
style CF2 fill:#f3e8ff,stroke:#7c3aed
style WAF fill:#fee2e2,stroke:#dc2626
style S3LOG fill:#f1f5f9,stroke:#64748b
style CWLOG fill:#f1f5f9,stroke:#64748b
| 组件 | 部署位置 | 角色 |
|---|---|---|
| CloudFront 入口① | AWS 托管(全球边缘) | 用户浏览器访问 JumpServer 的唯一公网入口,VPC Origin 私网回源 |
| CloudFront 入口② | AWS 托管(全球边缘) | 设备 frpc 的 wss 接入入口,VPC Origin 回源 frps:7000 |
| JumpServer EC2 | 私有子网,独立一台 | 堡垒机本体:纳管、授权、审计、录像 |
| frps EC2(兼 WorkNode #1) | 私有子网,独立一台 | 设备隧道汇聚点:每台设备映射为本机一个私网端口(6001-6299);同机也作为一个被管节点 |
| WorkNode EC2 #2…#N | 私有子网,按需扩展 | 被管理的云上服务器,22 端口仅对 JumpServer 安全组开放 |
| AWS WAF(阶段四) | us-east-1 全球作用域 | 一个 Web ACL 同时保护两个 CloudFront 入口(含 frp 握手放行规则) |
| 日志(阶段四) | S3 桶(东京)+ CloudWatch Logs(us-east-1) | 两个入口的 CloudFront 访问日志入 S3、WAF 拦截/放行日志入 CloudWatch,均保留 90 天 |
| 通道 | 流量路径 | 对应章节 |
|---|---|---|
| 人访问系统 | 浏览器 → 入口① → JumpServer → 云上/现场资产 | 阶段一 |
| 设备接入系统 | 设备 frpc --wss--> 入口② → frps ← JumpServer 私网 :600x | 阶段二 |
设备接入的关键机制:设备端 frpc 主动向外拨号建立 WebSocket 反向隧道(办公室防火墙只需允许出站 443,车辆蜂窝网络天然支持),frps 把每台设备映射为自己的一个私网端口,JumpServer 把「frps私网IP:端口」当普通 SSH 资产纳管——frp 中间层对用户完全透明。
| # | 条件 | 检查 / 修复命令 |
|---|---|---|
| 1 | 已有 VPC,含私有子网、IGW、NAT 网关 | aws ec2 describe-internet-gateways --filters Name=attachment.vpc-id,Values=<VPC-ID>(VPC Origins 硬性要求挂 IGW,即使实例在私有子网;不需要改私有子网路由) |
| 2 | VPC 启用 DNS support + DNS hostnames | aws ec2 modify-vpc-attribute --vpc-id <VPC-ID> --enable-dns-support / --enable-dns-hostnames |
| 3 | VPC 未开 Block Public Access,或已建豁免 | aws ec2 describe-vpc-block-public-access-exclusions;无豁免则 create-vpc-block-public-access-exclusion(BPA 会拦截 VPC Origin 的创建与回程流量,实际部署中遇到过) |
| 4 | JumpServer EC2 运行中(私有子网、监听 80) | 主机上 ss -ltnp | grep 80 |
| 5 | 执行者 IAM 权限 | cloudfront:CreateVpcOrigin/GetVpcOrigin/CreateDistribution、ec2:Describe*、ec2:AuthorizeSecurityGroupIngress、wafv2:、logs:、sts:GetCallerIdentity |
| 6 | 操作终端 | 推荐 AWS CloudShell(凭据天然就位,省 -p 参数;保持浏览器标签页打开)或本地 CLI v2 + --profile customer。不要用 Git Bash——MSYS 路径转换会破坏 file:// 参数 |
| 脚本 | 位置 | 作用 | 对应章节 |
|---|---|---|---|
setup-cloudfront-jumpserver.sh |
jumpserver/ | 一键创建入口①(VPC Origin + Distribution + 安全组放行) | 阶段一 |
setup-frp-server.sh |
frp/ | frps EC2 上一键安装 frps(下载/token/配置/systemd) | 阶段二·步骤 1 |
setup-frp-cloudfront.sh |
frp/ | 一键创建入口②(VPC Origin 指向 frps:7000 + Distribution + 安全组放行) | 阶段二·步骤 2 |
setup-frp-client.sh |
frp/ | 设备端一键安装 frpc(含 sshd 保活),支持交互与批量两种模式 | 阶段二·步骤 3 |
test-frp-link.sh |
frp/ | 入口②连通性验证工具(macOS / Linux 通用),不需要真 token 即可判定 wss→CloudFront→VPC Origin→frps 全链路是否贯通 | 阶段二·验证 |
💡 所有脚本均幂等可重跑:VPC Origin / Distribution 按名称或关联关系自动复用,轮询超时后用相同参数重跑即可续接。三个 frp 脚本统一锁定 frp v0.71.0,升级版本只需改脚本头部的
FRP_VER。
export AWS_DEFAULT_REGION=ap-northeast-1
VPC_ID=<VPC-ID> # 例: vpc-0123456789abcdef0
PRI_SUBNET=<私有子网ID> # frps 所在子网(与 JumpServer 同子网或可互通)
JMS_SG=<JumpServer安全组ID>
KEY_NAME=<EC2密钥对名称>
AMI_ID=$(aws ssm get-parameters --names /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 --query 'Parameters[0].Value' --output text)目标:浏览器经 https://<CloudFront域名> 登录私有子网中的 JumpServer
用户浏览器 --HTTPS:443--> CloudFront入口① --VPC Origin(ENI)--> JumpServer EC2:80(私有子网)
│ SSH:22(JumpServer托管凭据发起)
▼
WorkNode EC2(22 仅对 JumpServer SG 开放)
| 设计 | 原因 |
|---|---|
| JumpServer 私有子网、无公网 IP | 零直接暴露面,入口只有 CloudFront |
| CloudFront VPC Origins | 无需公网地址/公网 LB,CloudFront 直连私有子网 ENI 回源 |
| WorkNode 22 端口只放行 JumpServer SG | 强制所有 SSH 经堡垒机,无法绕行 |
| CachingDisabled + AllViewerExceptHostHeader | 动态应用 + WebSocket(Web 终端),不能缓存、需透传全部头 |
| redirect-to-https | 用户侧强制加密 |
能力边界:CloudFront 只承载 HTTP/HTTPS(含 WebSocket),覆盖 Web 登录、Web 终端、Web SFTP 全部使用场景;如未来需要原生 SSH 客户端直连 koko(ssh user@bastion -p 2222),需另行评估四层 TCP 入口,不在本方案范围。
bash setup-cloudfront-jumpserver.sh \
[-p customer] \ # aws profile(CloudShell 中省略)
-r ap-northeast-1 \ # 区域
-i i-xxxxxxxxxxxxxxxxx \ # JumpServer 实例 ID
[-P 80] \ # JumpServer HTTP 端口,默认 80
[-g sg-xxxxxxxx] # 实例挂多个安全组时明确指定要放行的那个脚本自动完成:身份确认(防止跑错账号)→ 预检(实例/IGW/DNS 属性)→ 创建 VPC Origin(轮询至 Deployed,实测约 12 分钟)→ 创建 Distribution(策略要点见下表)→ 放行安全组(80 ← CloudFront-VPCOrigins-Service-SG,幂等)→ 输出 CloudFront 域名。
| Distribution 配置项 | 值 |
|---|---|
| Cache Policy | CachingDisabled(托管策略 4135ea2d-6df8-44a3-9df3-4b5a84be39ad) |
| Origin Request Policy | AllViewerExceptHostHeader(b689b0a8-53d0-40ab-baf2-68738e2966ac) |
| AllowedMethods | 全部 7 种(登录 POST、API PUT/DELETE 都需要) |
| ViewerProtocolPolicy | redirect-to-https |
| OriginReadTimeout / KeepaliveTimeout | 60s / 30s |
⚠️ ⚠️ 本方案最容易踩的坑。DOMAINS 没配(或与浏览器实际使用的域名不匹配)有两种症状:① 登录报 CSRF 错误;② 登录一切正常,但所有资产的 Web 终端都连不上(WebSocket Origin 校验被拒)——第二种极具迷惑性,实际部署中曾被误判为资产/账号/密钥/网络问题排查数小时。新建或更换 Distribution 后必须同步追加新域名。
# 登录 JumpServer 主机(SSM Session Manager 或既有方式)
sudo su -
echo 'DOMAINS="dxxxxxxxxxxxx.cloudfront.net"' >> /opt/jumpserver/config/config.txt
cd /opt/jumpserver-installer-* # 官方 quick_start 安装的默认路径
./jmsctl.sh restart非官方脚本安装时 config.txt 位置以 jmsctl.sh 所在目录的 config 为准;已有 DOMAINS 行时用逗号追加域名,不要重复添加整行。
把入口从默认 *.cloudfront.net 换成自有域名(CloudFront 备用域名 + ACM 证书配好之后),JumpServer 侧必须同步改三处:
| # | 配置项 | 位置 | 不改的症状 |
|---|---|---|---|
| 1 | DOMAINS | config.txt(改后需 ./jmsctl.sh restart) |
登录页直接报 "Configuration file has problems" |
| 2 | 站点 URL | Web 控制台 → 系统设置 → 基本设置 | 邮件/通知里的链接仍指旧域名 |
| 3 | Endpoint.host | Web 控制台 → 系统设置 → 终端管理 → 端点管理(改后即时生效) | 登录一切正常,但所有资产 Web 终端"拒绝连接"——koko 的 connect URL 只用 Endpoint.host 生成,不读站点 URL;iframe 指向旧域名后跨站不带 Cookie → 被弹回登录页 → X-Frame-Options 拒绝渲染 |
⚠️ ⚠️ 本架构下 Endpoint.host 必须写死当前域名,不能留空。留空时 JumpServer 用request.get_host()跟随访问域名——但 CloudFront 的 AllViewerExceptHostHeader 源请求策略会剥离 Host 头,源站收到的 Host 永远是 VPC Origin 的内网名(如ip-10-0-1-40...compute.internal),跟随机制在此架构下必坏。同理,依赖request.get_host()的功能(Passkey/WebAuthn origin 校验、CAS/OAuth2 回调)当前均不可用;未来要上 SSO/Passkey 需把源请求策略换成 Managed-AllViewer 并灰度验证。
aws ec2 authorize-security-group-ingress --region ap-northeast-1 \
--group-id <WorkNode的SG> --protocol tcp --port 22 \
--source-group $JMS_SG
# 并确认 22 端口没有对 0.0.0.0/0 或其他来源开放| # | 验证项 | 预期 |
|---|---|---|
| 1 | aws cloudfront get-distribution --id <dist-id> --query 'Distribution.Status' |
Deployed(约 5-10 分钟) |
| 2 | 浏览器打开 https://<CloudFront域名> | JumpServer 登录页;初始 admin/ChangeMe,首次强制改密并立即启用 MFA |
| 3 | 普通用户登录 → Web 终端连 WorkNode | 成功落到目标机,会话有录像审计 |
目标:办公室/车载设备经反向隧道挂进 frps,成为 JumpServer 可管理的私网端口
| 项目 | 规划值 | 说明 |
|---|---|---|
| frps 部署位置 | 复用 WorkNode #1 EC2(t3.small 级即可) | 2000+ 设备内够用;也可独立小实例部署 |
| 公网入口② | CloudFront wss:443 | 回源 frps:7000。用 443 是因为部分蜂窝网络/办公室防火墙只放行 443 出站 |
| frps 控制端口 | 7000 | 仅接受 CloudFront VPC Origin 回源流量 |
| 车载设备映射端口 | 6001 – 6099 | 每车固定一个端口 |
| 办公室设备映射端口 | 6101 – 6199 | 同上 |
| 预留端口段 | 6201 – 6299 | RDP/VNC/其他协议扩展(frp 是 TCP 层转发,可直接穿 VNC) |
| 安全组 frps-sg | 入站:7000 ← CloudFront 服务SG;6001-6299 ← JumpServer SG | 最小权限,无任何 0.0.0.0/0 |
⚠️ ⚠️ 端口分配表必须留档:维护「设备 ↔ remotePort ↔ JumpServer 资产名」对照表(Excel/CMDB),frpc 配置与 JumpServer 资产都以它为准。frps 已配maxPortsPerClient=4防单设备占满端口池。安全组命名注意不能以sg-开头(AWS 资源 ID 前缀保留)。
一键脚本(推荐):将 frp/setup-frp-server.sh 上传到 frps EC2(经 JumpServer SFTP、SSM 或 S3 中转),执行:
sudo bash setup-frp-server.sh
# 自动完成:架构检测 → 下载 v0.71.0 → 生成随机 token → 写配置 → systemd 注册 → 自检
# 结束会打印 TOKEN —— 妥善保存,所有设备端 frpc 用同一个
# 轮换 token:sudo bash setup-frp-server.sh <新token>(可重复执行,已有配置不覆盖)手工等效配置(脚本不可用时),核心文件 /etc/frp/frps.toml:
bindAddr = "0.0.0.0"
bindPort = 7000
auth.method = "token"
auth.token = "<openssl rand -hex 32 生成的64位随机串>"
# ⚠️ 不要开启 transport.tls.force —— frpc→CloudFront 段已是 wss(TLS),
# CloudFront→frps 回源是明文 ws,开 force 会拒掉回源导致全部设备无法接入
allowPorts = [ { start = 6001, end = 6299 } ]
maxPortsPerClient = 4
log.to = "/var/log/frps.log"
log.level = "info"
log.maxDays = 30systemd 单元 /etc/systemd/system/frps.service(ExecStart=/opt/frp/frps -c /etc/frp/frps.toml,Restart=always),然后 systemctl enable --now frps,用 ss -lntp | grep 7000 确认监听。
# CloudShell 中执行
bash setup-frp-cloudfront.sh -i <frps实例ID> -j <JumpServer安全组ID>
# 自动完成:前置检查(IGW/BPA豁免,幂等) → VPC Origin 指向 frps:7000(Failed 旧资源自动清理)
# → Distribution(WebSocket 调优)→ 安全组放行(7000←CloudFront服务SG、6001-6299←JMS SG)
# → 输出 CloudFront 接入域名(即设备端 frpc 的 serverAddr)
# 全程约 10-20 分钟,大部分在等 AWS 部署手工创建要点:VPC Origin 目标 = frps 实例 ARN、HTTPPort=7000、http-only;Distribution 缓存策略 CachingDisabled、源请求策略 AllViewerExceptHostHeader(WebSocket 的 Sec-WebSocket-* 头靠它转发)、Viewer 协议 https-only、Origin 超时 60s。技术依据:AWS 2026 年 5 月发布 VPC Origins 的 WebSocket 支持;CloudFront WebSocket 仅支持 HTTP/1.1(frpc 原生即是)。
一键脚本(推荐):frp/setup-frp-client.sh 自动检测 x86_64 / aarch64 / armv7l 架构:
# 交互模式:按提示输入 CloudFront接入域名 → 映射端口(按分配表) → 设备名 → token(静默输入)
sudo bash setup-frp-client.sh
# 批量部署(车队产线刷机):
sudo FRP_TOKEN=<token> bash setup-frp-client.sh <接入域名> 6001 car-001
# 显示 ✅ 即隧道建立成功,并打印该设备挂进 JumpServer 的资产参数手工等效配置 /etc/frp/frpc.toml(车机-001 示例):
serverAddr = "<CloudFront接入域名>"
serverPort = 443
auth.method = "token"
auth.token = "<与 frps 相同的 token>"
transport.protocol = "wss" # 走 WebSocket over TLS 穿 CloudFront
loginFailExit = false # 蜂窝网络必配:首次连不上也持续重试
transport.heartbeatInterval = 30
transport.heartbeatTimeout = 90
[[proxies]]
name = "car-001-ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6001 # ★ 按端口分配表,每设备唯一(办公室设备用 61xx 段)systemd 单元同 frps 模式(frpc -c /etc/frp/frpc.toml、Restart=always、After=network-online.target)。
CloudFront 对空闲约 10 分钟的 WebSocket 连接会超时断开;frp 心跳只保护控制连接,每条 SSH 会话是独立的 work connection。在设备端 sshd 开启保活即可彻底解决:
echo -e "ClientAliveInterval 60\nClientAliveCountMax 3" | sudo tee /etc/ssh/sshd_config.d/frp-keepalive.conf
sudo systemctl restart ssh # Amazon Linux 等发行版服务名为 sshd💡 补充:① JumpServer 自身的终端空闲超时(系统设置→安全设置)是独立安全策略,建议保留;② 设备上跑长任务用 tmux/nohup,网络波动断开后
tmux attach继续。
# 设备端:日志出现 login to server success / start proxy success
journalctl -u frpc -f
# frps 端:确认端口已映射
sudo ss -lntp | grep 6001
# JumpServer 机器上:应能直接 SSH 到设备
ssh -p 6001 root@<frps私网IP>入口②链路自检(frp/test-frp-link.sh):在任意一台能上网的 macOS / Linux 上跑,用来把"设备连不上"拆成"链路问题"还是"设备/token 问题"——尤其适合在设备到场前先验收入口,或排查阶段四挂 WAF 后的集体掉线:
bash test-frp-link.sh <接入域名> # 无 token:故意用错 token,只要 frps 回"token 校验失败"即证明全链路贯通
bash test-frp-link.sh <接入域名> <token> # 有 token:看到 login success 即认证 + 链路全部通过脚本自动按系统/架构下载 frpc v0.71.0,固定用预留段末位端口 6299 做测试映射(不与真实设备冲突,退出即释放),12 秒采样后直接给出结论与排查顺序。
目标:云上节点与现场设备统一进资产树,按节点授权、点击即连
/Default
├── AWS云资源
│ ├── WorkNode-01(即 frps 所在 EC2)
│ └── WorkNode-02 … N
└── 现场设备
├── 车载设备 (端口段 6001-6099)
└── 办公室设备 (端口段 6101-6199)
按节点授权用户/用户组,可实现「车队运维只能看到车载设备」的最小权限分配。
方式一:手工单个(控制台 → 资产管理 → 资产列表 → 创建 → 主机):
| 字段 | 云上 WorkNode | frp 现场设备 |
|---|---|---|
| 名称 | WorkNode-02 | 车机-001 |
| 地址 | 该 EC2 私网 IP | frps 私网 IP(所有 frp 设备相同) |
| 协议/端口 | SSH 22 | SSH 600x(按端口分配表,每资产独立端口) |
| 节点 | /Default/AWS云资源 | /Default/车载设备 或 /办公室设备 |
| 账号 | 录入 root/密钥或专用账号,用户连接时自动登录 | 同左 |
保存后点「测试可连接性」,绿色即打通。
方式二:CSV 批量导入(资产列表 → 导入 → 下载模板):
name,address,platform,protocols,nodes
车机-001,<frps私网IP>,Linux,ssh/6001,/Default/车载设备
车机-002,<frps私网IP>,Linux,ssh/6002,/Default/车载设备
办公室-NAS,<frps私网IP>,Linux,ssh/6101,/Default/办公室设备方式三:API 自动注册(新车上线自动化):
curl -s -X POST https://<JumpServer地址>/api/v1/assets/hosts/ \
-H "Authorization: Token <JMS-API-Token>" -H "Content-Type: application/json" \
-d '{"name":"车机-003","address":"<frps私网IP>","platform":{"pk":1},
"protocols":[{"name":"ssh","port":6003}],"nodes":[{"pk":"<车载设备节点ID>"}]}'💡 ✅ 附带收益:JumpServer 的资产连通性测试走的就是 frps私网IP:端口,frpc 掉线时资产立即显示不可达——相当于免费获得设备在线状态监控。
⚠️ 首次执行「测试可连接性」若报错「未找到 Ansible Docker 镜像 jumpserver/ansible-executor:latest」:在宿主机docker pull jumpserver/ansible-executor:latest,或在 系统设置→功能设置→作业中心 关闭「Ansible Docker 隔离」。
三项均围绕 CloudFront 入口实施,全部通过 AWS 控制台配置,对已部署系统零改动、零停机
| 顺序 | 加固项 | 作用层 | 作用对象 |
|---|---|---|---|
| 1(先开) | 日志启用 | 可观测层 | 两个 Distribution 访问日志 + WAF 日志——先获得流量基线 |
| 2 | 源站保护核查 | 网络层 | JumpServer / frps 实例的安全组与公网暴露面 |
| 3 | AWS WAF | L7 边缘 | 入口①+②共享一个 Web ACL |
前提说明:
- CloudFront 的 Web ACL 必须创建在 Global (CloudFront) 作用域——进入 WAF 控制台后在 Region 下拉框选择 Global (CloudFront)
- 一个 Web ACL 可同时关联多个 Distribution,本方案两个入口共享一个
- 下表规则合计约 1334 WCU,在默认容量上限 1500 之内
规则清单:
| # | 规则 | 说明 | 动作 | WCU |
|---|---|---|---|---|
| 0 | 自定义放行规则 allow-frp-tunnel(最高优先级,必配) |
frpc 的 WebSocket 握手请求(GET /~!frp)不携带 User-Agent 头,会命中 #4 中的 NoUserAgent_HEADER 子规则被边缘拦截,导致设备无法接入。须最先放行设备入口的该路径 |
Allow:Host == 设备入口域名 且 URI 前缀 == /~!frp |
4 |
| 1 | AWSManagedRulesAmazonIpReputationList |
已知恶意 IP、僵尸网络、扫描源 | Block | 25 |
| 2 | AWSManagedRulesKnownBadInputsRuleSet |
Log4Shell、主机头注入等已知漏洞利用特征 | Block | 200 |
| 3 | AWSManagedRulesSQLiRuleSet |
SQL 注入 | Block | 200 |
| 4 | AWSManagedRulesCommonRuleSet |
XSS、LFI 等 OWASP 通用攻击 | Block,其中 SizeRestrictions_BODY 子规则改为 Count(该子规则拦截 >8KB 请求体,会影响 JumpServer 文件上传与批量导入) |
700 |
| 5 | AWSManagedRulesLinuxRuleSet |
Linux 路径穿越、命令注入 | Block | 200 |
| 6 | 速率规则 rate-limit-all |
CC / 洪水防护 | 单 IP 2000 请求 / 5 分钟,超限 Block(自动解封) | 2 |
| 7 | 速率规则 rate-limit-login |
登录爆破防护 | URI 含 /auth 的请求单 IP 100 次 / 5 分钟,超限 Block |
3 |
⚠️ 灰度建议(生产环境):#0/1/2/3/6/7 直接启用;#4、#5 两组先整组 Count 模式观察 1–2 周,在 WAF 日志中确认无正常运维流量命中后再切 Block。
控制台配置步骤:
- 控制台搜索进入 WAF & Shield → 左侧 Web ACLs → Region 下拉选 Global (CloudFront) → Create web ACL
- Name 填
jumpserver-edge-waf;Resource type 选 Amazon CloudFront distributions;Associated AWS resources → Add AWS resources → 勾选入口①与入口②两个 Distribution → Next - Add rules → Add managed rule groups → AWS managed rule groups → 依次勾选上表 #1–#5 五个规则组(AWS 托管组免费)→ Add rules
- 对 Core rule set 点 Edit → 找到
SizeRestrictions_BODY→ Rule action 选 Override to Count → Save。若采用灰度策略,同时对 Core rule set 与 Linux operating system 勾选 Set all rule actions to count
rate-limit-all:Type 选 Rate-based rule,Rate limit2000,Evaluation window 5 minutes,Request aggregation 选 Source IP address,Action Blockrate-limit-login:Rate limit100,勾选 Only consider requests that match the criteria in a rule statement,Inspect 选 URI path,Match type 选 Contains string,String 填/auth,Action Block
本方案采用 VPC Origins 架构,源站保护为结构性内置:源站无公网 IP、回源走 VPC 内私网 ENI、安全组仅放行 CloudFront 服务安全组、JumpServer DOMAINS 叠加 Host 校验。生产前在控制台核查三项:
- 确认源站无公网 IP:EC2 控制台 → 实例 → 分别选中 JumpServer 与 frps 实例 → 详情页 公有 IPv4 地址 应为空("–")
- 找到 CloudFront 服务安全组:EC2 控制台 → 安全组 → 搜索
CloudFront-VPCOrigins-Service-SG(创建 VPC Origin 时自动生成),记下其安全组 ID - 核查源站安全组入站规则:打开 JumpServer 安全组与 frps 安全组的 入站规则 页签——JumpServer 的 80 端口与 frps 的 7000 端口的来源应仅为上一步的服务安全组;任何端口都不应出现
0.0.0.0/0。发现多余规则(如调试遗留)点 编辑入站规则 逐条删除
可选进阶(仅在客户合规清单明确要求"源站校验请求来源"时实施):CloudFront 控制台 → Distribution → Origins → Edit → Add custom header,注入 X-Origin-Verify 随机串并在源站 Nginx 校验。注意 JumpServer 的 Nginx 在容器内,升级重建会丢失该改动,需固化到部署脚本;VPC Origins 架构下该措施边际收益有限。
第一步:准备 S3 日志桶
- S3 控制台 → Create bucket:名称
jumpserver-cf-logs-<账号ID>(桶名全局唯一,附账号 ID 避免冲突),区域选东京 ap-northeast-1,Block all public access 保持全部勾选 → Create - 进入该桶 → Management 页签 → Create lifecycle rule:规则名
expire-90d,Apply to all objects,动作勾选 Expire current versions of objects,天数填 90 → Create rule
第二步:开启 CloudFront 访问日志(两个 Distribution 各一份)
- CloudFront 控制台 → Distributions → 选中入口① → Logging 页签 → Standard log destinations → Add
- Destination 选 Amazon S3,Bucket 选上面创建的日志桶,Prefix 填
cf-web/;Output format 选 Parquet(便于 Athena 直查,选 Plain text + Gzip 亦可),Partitioning 勾选按日期 → Add(所需桶策略由控制台自动配置) - 对入口②重复上两步,Prefix 填
cf-device/
第三步:开启 WAF 日志
- CloudWatch 控制台(us-east-1)→ Log groups → Create log group:名称
aws-waf-logs-jumpserver(必须以aws-waf-logs-开头),Retention 选 90 天 - WAF 控制台 → Web ACLs →
jumpserver-edge-waf→ Logging and metrics 页签 → Enable logging → Destination 选 CloudWatch Logs log group → 选上一步的日志组 → Save
| 日志 | 回答什么问题 | 保留 |
|---|---|---|
| CloudFront 访问日志 | 谁、何时、从哪个 IP 访问了入口;设备何时建立过隧道(一条 wss 隧道只产生一条握手记录) | S3 90 天 |
| WAF 日志 | 哪些请求被拦截 / 计数、命中哪条规则;也是灰度观察期的判定依据 | CloudWatch 90 天 |
| frps 日志(已有) | 设备隧道级:哪台设备何时上下线、真实源 IP | 本机 30 天 |
| JumpServer 审计(已有) | 人员操作级:谁登录哪台资产、执行什么命令、会话录像 | 平台内,定期归档 |
| # | 验收项 | 预期 |
|---|---|---|
| 1 | 浏览器访问 https://<入口①域名>/?q=<script>alert(1)</script> |
403 拦截页(WAF 生效) |
| 2 | 正常登录 JumpServer + Web 终端连资产 + 文件上传 | 全部正常,无误拦 |
| 3 | 设备 frpc 重启后重建隧道(必测) | frps 上对应端口重新出现映射、frpc 日志 login success。若 frpc 报 bad status:WAF 控制台 → 该 Web ACL → Sampled requests 查 /~!frp 是否被 NoUserAgent_HEADER 拦截 → 核对 #0 放行规则的域名与 URI 是否填写正确、优先级是否在最顶部 |
| 4 | 日志桶 1 小时内出现日志对象;WAF 日志组出现记录 | 非空 |
| 5 | 源站核查三项(6.3) | 无公网 IP;入站仅服务安全组;无 0.0.0.0/0 |
| 项目 | 计费 | 月成本(约) |
|---|---|---|
| WAF Web ACL | $5/个(两入口共享 1 个) | $5 |
| WAF 规则 | $1/条 × 8(5 个托管组 + 1 条放行 + 2 条速率) | $8 |
| WAF 请求处理 | $0.60/百万请求,运维流量极小 | < $1 |
| S3 访问日志 + CloudWatch WAF 日志 | 存储 + 摄入,90 天生命周期 | < $1 |
| 合计 | ≈ $13 – 15 / 月 |
本方案唯一的公网暴露面是两个 CloudFront 入口的 443 端口(AWS 托管域名)。车载设备使用普通互联网 SIM(无专用 APN),源 IP 随基站漫游且位于 CGNAT 之后,无法用源 IP 白名单收敛入口——安全边界依设计下沉到认证层。
| 防护层 | 状态 | 说明 |
|---|---|---|
| 入口防护 | ✅ AWS 托管 | 入口整体由 CloudFront 托管,账号内无自管公网安全组;各实例安全组均最小权限 |
| DDoS 防护 | ✅ 自带 | AWS Shield Standard 免费内置,抵御 L3/L4 洪水;CloudFront 弹性承载海量并发 |
| L7 防护 | ✅ 阶段四 | WAF:IP 信誉/已知漏洞/SQLi/OWASP/速率限制(见 第 6 章) |
| 传输加密与认证 | ✅ 强制 | 设备→CloudFront 全程 wss(TLS) + 64 位随机 token,未认证连接毫秒级断开 |
攻击者扫到 443 后能做的全部事情:建 TCP 连接 → 被要求 TLS + token 校验 → 失败立即断开。拿不到 banner、版本号或服务指纹;64 位随机 token 暴力破解计算上不可行。该模型与 WireGuard / OpenVPN / AWS Client VPN 端点完全同级——"端口可达、边界在认证层"是移动设备远程接入的行业标准做法。
| 风险 | 概率 | 缓解 |
|---|---|---|
| frp 软件 CVE | 低(历史有先例) | 订阅 frp Release 及时升级;frps 与 JumpServer 是两台机器,即使 frps 被攻破,到 JumpServer 仍隔着安全组。注意:frps 与 WorkNode #1 同机,frps 失陷即该节点失陷——对该节点做最小化部署(不放敏感数据),或后续拆分为独立小实例 |
| TLS 握手洪水耗 frps CPU | 低 | WAF 速率规则先拦一层;CloudWatch CPU/连接数告警;frps 日志留设备真实源 IP 可 NACL 封禁 |
| token 泄露(车机物理拆解) | 中——最现实的泄露途径 | 季度轮换;maxPortsPerClient=4 限占用;泄露即换 token 全量下发。即使泄露,攻击者也只能把自己设备映射进端口池,无法访问 JumpServer 或其他设备 |
| 现象 | 排查方向 |
|---|---|
| CloudFront 502/504(入口①) | 安全组未放行 80←服务SG;或 JumpServer 容器没起(./jmsctl.sh status) |
| 登录页正常但登录报 CSRF | DOMAINS 没配或没 restart(3.3 节) |
| 登录正常但所有资产 Web 终端连不上 | ① 首查 DOMAINS 是否含浏览器正在用的域名、改后是否 restart——全资产失败几乎必是它(旁证:宿主机 ssh -p 2222 admin@127.0.0.1 走 koko 直连口能通则坐实 Web 链路问题)② 确认缓存策略 CachingDisabled、AllowedMethods 全开 |
| frpc 连不上(login failed / timeout) | ① 设备端 curl -v telnet://<接入域名>:443 测出站,或直接跑 test-frp-link.sh <接入域名> 一次判定链路 ② token 是否一致 ③ transport.protocol="wss" 是否配置 ④ 浏览器访问接入域名返回 502 属正常(说明已回源到 frps);超时则回源链路有问题 |
| 入口②回源不通(访问超时) | ① frps 进程/7000 监听 ② frps-sg 放行 7000←CloudFront 服务SG ③ VPC Origin 端口是否 7000 |
| JumpServer 资产测试不可达 | ① frps 上 ss -lntp | grep 600x(没有=frpc 离线)② frps-sg 放行 6001-6299←JMS SG ③ 端口对照分配表 |
| 换自定义域名后,登录正常但 Web 终端"拒绝了我们的连接请求"(iframe 指向旧域名) | 终端管理 → 端点管理里的 Endpoint.host 还是旧域名——改为新域名即时生效(见 3.3.1,换域名三件套) |
| 测试可连接性报 ansible-executor 镜像缺失 | docker pull jumpserver/ansible-executor:latest 或关闭 Ansible Docker 隔离(5.2 节提示框) |
| 能连上但立即断开 | frpc localIP 与设备 sshd 监听地址是否一致(通常 127.0.0.1) |
| 车机频繁掉线重连 | 正常(基站切换);确认 heartbeat 已配、systemd Restart=always |
| 端口冲突(proxy already exists) | 两台设备配了相同 remotePort,对照分配表修正 |
| Web 终端闲置断开 | 确认设备端 sshd 保活已配(4.4);长任务用 tmux |
| VPC Origin 创建失败 | VPC 没挂 IGW 或 BPA 未豁免(2.1 节前置条件 1/3) |
挂 WAF 后全部设备掉线,frpc 报 bad status,frps 日志零记录 |
WAF 的 NoUserAgent_HEADER 在边缘拦掉了 frpc 握手(frpc 无 UA 头),请求不回源。查 WAF 抽样日志确认 /~!frp 被 BLOCK → 补 6.2 表 #0 放行规则(实际发生过的故障:WAF 上线后设备例行重连时集体掉线) |
| WAF 误拦正常操作 | WAF 日志过滤该请求命中的规则 → 控制台把对应子规则 Override to Count → 复现确认后评估长期豁免 |
| 操作 | 方法 |
|---|---|
| 新增 WorkNode | 该机 SG 放行 22←JMS SG(3.4)→ 资产列表添加 → 授权 |
| 新增现场设备 | 端口分配表登记 → 设备跑 setup-frp-client.sh(4.3)→ 资产列表添加 ssh/600x(5.2) |
| 更换入口域名 | 三件套:config.txt DOMAINS(restart)+ 基本设置站点URL + 端点管理 Endpoint.host(见 3.3.1);旧域名建议在 DOMAINS 保留作应急入口 |
| token 季度轮换 | frps 执行 sudo bash setup-frp-server.sh <新token> → 设备端灰度下发重启 frpc;过渡期可用 auth.additionalScopes 双 token |
| WAF Count→Block 切换 | 观察期后在 Web ACL 中把 Core/Linux 规则组的 Count 覆盖取消 |
| 设备在线监控 | 资产连通性测试即在线状态;可配定时任务批量测试 + 告警 |
| 资源 | 计费说明 | 月成本(约) |
|---|---|---|
| frps 复用 WorkNode #1 | 无新增实例 | $0(独立部署则 t3.small ≈ $21) |
| CloudFront 入口①+② | 请求数 + 流出流量,SSH 运维流量极小 | $2 – 8 |
| WAF(阶段四) | Web ACL $5 + 规则 $1×8 + 请求 $0.60/百万 | $13 – 14 |
| 日志(阶段四) | S3/CloudWatch 存储,90 天生命周期 | < $1 |
| NAT 流量增量 | frps 出站极少(仅下载安装包) | ≈ $0(复用现有 NAT) |
| 合计 | ≈ $15 – 22 / 月(frps 独立部署则 +$21) |
精确报价以 AWS Pricing Calculator 为准;若后续隧道走大流量(如日志回传)需重估流量费。
按依赖顺序执行(先删引用者)。清理是纯减量操作,不影响 JumpServer 本体与既有环境:
# 0. WAF(如已部署阶段四):先从两个 Distribution 的 WebACLId 置空,再删 Web ACL
aws wafv2 delete-web-acl --scope CLOUDFRONT --region us-east-1 --name jumpserver-edge-waf --id <acl-id> --lock-token <token>
# 1. 禁用并删除 Distribution(先 disable,等 Deployed 后 delete)
aws cloudfront get-distribution-config --id <dist-id> # 取 ETag,改 Enabled=false 后 update-distribution
aws cloudfront delete-distribution --id <dist-id> --if-match <etag>
# 2. 删除 VPC Origin
aws cloudfront get-vpc-origin --id <vo-id> --query 'ETag' --output text
aws cloudfront delete-vpc-origin --id <vo-id> --if-match <etag>
# 3. 移除安全组放行(80/7000 ← CloudFront服务SG、6001-6299 ← JMS SG)
aws ec2 revoke-security-group-ingress --group-id <SG> --protocol tcp --port <端口> --source-group <来源SG>
# 4. JumpServer 中删除对应资产/节点;设备端停用 frpc(sudo systemctl disable --now frpc)边缘设备指标直写 Amazon Managed Prometheus —— node_exporter + Prometheus agent + IAM Roles Anywhere,设备零长期凭证
与第一部分的关系:第一部分解决"人访问设备"(交互式运维通道),本部分解决"设备上报指标"(监控数据通道)。两条链路方向相反、互相独立,可只部署其一;共同点是同一套设计哲学——设备无固定公网 IP 也能接入、设备端零长期 AK/SK、公网面由 AWS 托管服务承担。
flowchart LR
subgraph EDGE["边缘设备(ARM / x86,Linux)"]
NE["node_exporter :9100<br/>仅绑定 127.0.0.1"]
PA["Prometheus agent<br/>WAL only · 30s 抓取"]
HLP["aws_signing_helper<br/>credential_process"]
CRT["X.509 设备证书+私钥<br/>(设备上唯一的秘密)"]
end
subgraph CLOUD["AWS"]
RA["IAM Roles Anywhere<br/>Trust Anchor + Profile"]
ROLE["IAM Role<br/>仅 aps:RemoteWrite 限单 workspace"]
AMP["Amazon Managed Prometheus<br/>工作区"]
GRAF["Amazon Managed Grafana<br/>统一看板"]
end
PA -- "scrape :9100" --> NE
CRT -.-> HLP
HLP -- "① 证书换1小时临时凭证" --> RA --> ROLE
PA -- "② remote_write + SigV4" --> AMP
HLP -. "凭证到期自动重取" .-> PA
AMP --> GRAF
style CRT fill:#fef3c7,stroke:#d97706
style RA fill:#fee2e2,stroke:#dc2626
style AMP fill:#f3e8ff,stroke:#7c3aed
style GRAF fill:#f3e8ff,stroke:#7c3aed
| 组件 | 位置 | 角色 |
|---|---|---|
| node_exporter | 设备 | 采集本机指标,仅暴露给本机(127.0.0.1:9100),不出网 |
| Prometheus(agent 模式) | 设备 | 只留 WAL 无本地 TSDB,抓取后经 remote_write + SigV4 上报 |
| aws_signing_helper | 设备 | 用设备证书向 Roles Anywhere 换 1 小时临时凭证,SDK 到期自动重取 |
| IAM Roles Anywhere | 云端 | Trust Anchor 信任自建 CA;信任策略含 SourceArn 条件防 confused deputy |
| IAM Role | 云端 | 权限收紧为 aps:RemoteWrite 且限定单个 workspace ARN |
| AMP + Grafana | 云端 | 托管 Prometheus 存储查询与统一可视化 |
| 方案 | 额外成本 | 判断 |
|---|---|---|
| IAM User + AK/SK | $0 | ❌ 长期凭证落盘设备、泄露即永久,反模式(多数企业已禁止创建 IAM User) |
| IAM Roles Anywhere | $0(服务免费) | ✅ 首选:设备只持 X.509 证书,临时凭证自动轮换、单设备可吊销——AWS 官方推荐的非云端工作负载凭证方案 |
| IoT Core credentials provider | ≈$0 | 机制等价,但需注册 IoT Thing、走 IoT 域名,仅客户已在用 IoT Core 时考虑 |
| 中心 sigv4 代理 | +1 台 EC2 | 设备零凭证但需自管认证层且引入单点,不如托管方案 |
⚠️ CA 选型:PoC / 中小车队用自建 openssl CA($0,本方案脚本内置,CA 私钥必须离线备份);客户已有企业 PKI 则直接复用;AWS Private CA 通用模式($400/月)仅在需要托管与审计时选用。不要选 Private CA 短周期模式($50/月但证书 ≤7 天)——车辆离线超 7 天后证书过期即无法自行续期(续期本身需要有效凭证),形成死锁只能人工重灌。
密码学上,自建 CA 与托管 CA 签出的证书没有强度差别(本方案:RSA-4096 根证书 + SHA-256 签名 + RSA-2048 设备证书,与商业 CA 同级)。更关键的是该 CA 的爆炸半径被架构刻意收窄:它是纯私有信任,仅被本方案的 Trust Anchor 认可、不进任何公共信任链;其背后的 IAM Role 仅有单 workspace 的 aps:RemoteWrite。极端情况下 CA 私钥泄露,攻击者能做的全部事情是伪造设备证书向 AMP 写入虚假指标(数据污染)——无法访问其他任何 AWS 资源、无法读取任何数据。
与托管 CA 的真实差距在运营三项:
| 维度 | 自建 openssl CA | AWS Private CA($400/月对应的价值) |
|---|---|---|
| CA 私钥保管 | 文件形式(脚本已锁目录 700/私钥 600);须立即离线备份——CloudShell 家目录约 120 天不活动会被清理 | FIPS 140-2 Level 3 HSM,私钥不可导出 |
| 签发审计 | 无内建记录,靠维护设备-证书清单 | 全量 CloudTrail |
| 吊销 | 无现成 CRL 设施;小规模实用做法是换 CA 全量换证,或 openssl 生成 CRL 手动导入 Roles Anywhere | 托管 CRL / OCSP |
证书有效期(脚本默认值,可调):
| 证书 | 有效期 | 到期提醒 | 到期处理 |
|---|---|---|---|
| CA 根证书 | 10 年(行业惯例 10-20 年) | Trust Anchor 提前 90 天通知 | 换根:新 CA → 新 Trust Anchor → 设备分批换证,可提前平滑过渡 |
| 设备证书 | 默认 1 年(device 子命令可指定天数) | Trust Anchor 提前 30 天通知 | 重跑 device 子命令签新证 → 替换设备 crt/key → 重启服务 |
设备证书取 1 年是泄露止损与轮换负担的平衡点:车机被物理拆解是最现实的证书泄露途径,1 年有效期给出自然的止损上限。规模结论:PoC 与几十台规模自建 CA 够用且是业界常见做法(CA 私钥须立即离线备份);上百台或有合规审计要求时优先复用客户企业 PKI,无企业 PKI 再考虑 AWS Private CA。
自动化脚本不代建这两个 workspace——它们是账号级长期资源,创建涉及区域与认证方式选型,应由管理员在控制台显式完成:
| 服务 | 控制台操作 | 要记下的信息 |
|---|---|---|
| Amazon Managed Service for Prometheus(AMP) | 控制台 → Amazon Managed Service for Prometheus → 创建工作区(选定区域后一路默认即可) | workspace ID(ws-xxxx,init 步骤的 -w 参数)与所在 region |
| Amazon Managed Grafana | 控制台 → Amazon Managed Grafana → 创建工作区:身份验证选 IAM Identity Center,权限选 Service managed 并勾选 Prometheus(详见 13.3) | workspace URL(Grafana 登录地址) |
AMP 创建后即可开始下面两步;Grafana 不阻塞采集链路,可在设备数据入库后再建(配置细节与看板导入见 13.3)。其余全部云端操作由 amp-edge-monitoring/setup-amp-edge.sh 在 CloudShell 完成——在 AMP workspace 所在区域打开 CloudShell 并上传脚本。
# AWS 控制台右上角 CloudShell 图标打开(确认区域 = AMP workspace 所在区域),上传脚本后执行:
bash setup-amp-edge.sh init -w <workspace-id> -r <region>自动完成:身份确认(防跑错账号)→ 校验 workspace ACTIVE → 创建收紧版 IAM 策略(仅该 workspace 的 RemoteWrite)→ IAM Role → 自建 CA(RSA-4096 / 10 年 / 目录锁 700)→ Trust Anchor(含证书到期通知)→ 回填信任策略 SourceArn 条件 → Profile(会话 1 小时)→ 三串 ARN 落盘 ~/.amp-edge/amp-edge.env。
# 同一 CloudShell 会话中执行(init 产物已在 ~/.amp-edge/ 持久保存,隔天重开 CloudShell 也可直接跑):
bash setup-amp-edge.sh device <设备名> [有效期天数,默认365]签发设备证书(CN=设备名,clientAuth + CA:FALSE),并动态生成设备端安装脚本——设备专属参数(三串 ARN、remote_write URL、region、设备名)全部烤进脚本内,打包为 ~/.amp-edge/devices/device-<设备名>.tar.gz。CloudShell → Actions → Download file 下载。
💡 母子脚本关系:
setup-amp-edge.sh只在 CloudShell 跑(需 AWS 权限);包内的install-edge-collector.sh只在设备上跑(零参数、零 AWS 凭证需求)。设备包含私钥,传输到设备后请删除中转副本。
scp device-<设备名>.tar.gz user@device:/tmp/
# 设备上:
cd /tmp && tar xzf device-<设备名>.tar.gz && cd <设备名>
sudo bash install-edge-collector.sh
# 设备已有 Prometheus 且用户/服务名非默认时:
sudo PROM_USER=<用户> PROM_SERVICE=<服务名> bash install-edge-collector.sh安装脚本按设备现状自动走两条路径之一:
| 设备状态 | 脚本行为 |
|---|---|
| 裸机 | 全自动到跑通:装 node_exporter(绑 127.0.0.1)与 Prometheus LTS(--agent 模式)→ 自动生成完整配置(scrape + sigv4 + 唯一 instance 标签)→ promtool 校验 → 启动。零人工合并环节 |
| 已有组件 | 跳过安装、完全不动现有配置;只装凭证链,最后打印待合入 prometheus.yml 的 sigv4 片段(profile: amp-edge 行必须保留) |
两条路径共同完成:安装 aws_signing_helper(按架构、sha256 校验、自检)→ 证书私钥落盘(600/750/属主为采集用户)→ 写 AWS profile(credential_process)→ systemd drop-in 注入环境变量 → 扫描 AWS_* 环境残留并警告 → 以采集用户身份实换一次凭证自检。
裸机路径自动生成的配置即此形态;已有组件路径手动合并后应与之等价。<设备名> / <region> / <ws-id> 以设备包打印的真实值为准:
global:
scrape_interval: 30s
external_labels: # 可选:车队业务分组维度(附加到该设备的所有上报序列)
env: prod # 环境 / 用途
site: tokyo-lab # 站点 / 车队
# ⚠️ 不要在这里写 instance——external_labels 无法覆盖抓取自动生成的
# instance 标签,必须用下方 relabel_configs(见第 14 章排查实案)
scrape_configs:
- job_name: node
static_configs:
- targets: ['127.0.0.1:9100']
relabel_configs:
- target_label: instance
replacement: <设备名> # ← 每台设备唯一,与证书 CN 一致
# 这就是 Grafana 里的设备选择器
remote_write:
- url: https://aps-workspaces.<region>.amazonaws.com/workspaces/<ws-id>/api/v1/remote_write
sigv4:
region: <region>
profile: amp-edge # ← 必须保留!第一道凭证保障;缺失则 SDK 走
# 环境变量凭证链,易被设备残留带偏 → 403
queue_config: # 可选:边缘弱网 / 低配设备调优
capacity: 2500 # 本地缓冲队列深度(断网期间暂存)
max_shards: 2 # 并发上传分片上限(控内存与带宽)
max_samples_per_send: 1000 # 单次发送样本数
⚠️ 标签分工:instance(relabel 注入)= 设备唯一标识,全生态看板通用;external_labels只放分组维度(env / site / fleet),不要放设备标识——两处职责不要混。
# 设备上
journalctl -u prometheus -f # 不应出现 401/403
# CloudShell 上确认落库
pip3 install --user awscurl -q
awscurl --service aps --region <region> \
"https://aps-workspaces.<region>.amazonaws.com/workspaces/<ws-id>/api/v1/query?query=up"
# 返回 value [..,"1"] 即数据入库;Grafana 加 AMP 数据源即可出看板
# 部署后 1 小时复查 journal:确认跨过首个凭证刷新周期无 401/403前置:创建 Amazon Managed Grafana workspace 时,身份验证选 IAM Identity Center(其用户并非 IAM User,与企业禁令不冲突——人用 Identity Center、机器用 Roles Anywhere,全账号零长期凭证);权限选 Service managed 并勾选 Prometheus。创建后到 workspace 详情页 → Authentication → Assign new user or group 分配用户并给 Admin 角色。
- 加数据源(推荐):Grafana 菜单 → Apps → AWS Data Sources → Amazon Managed Service for Prometheus → 选 region → 自动发现 workspace → Add data source(自动配好 SigV4 与 endpoint)
- 手动备选:Add data source → Prometheus → URL 填 workspace 查询地址(不带 /api/v1 尾巴)→ 打开 SigV4 auth 开关 → Provider 选 Workspace IAM Role + region → Save & test
- 验证:Explore 查询
up应返回 1 - 首块看板:Dashboards → Import → 输入社区看板 ID 1860(Node Exporter Full)→ 数据源选 AMP → Import。CPU/内存/磁盘/网络/温度全套即出,instance 下拉即车队设备选择器
⚠️ Save & test 报 403:建 workspace 时未勾 Prometheus 数据源权限——到 Managed Grafana 控制台该 workspace 的 IAM permission settings 补勾后重试。
| 现象 | 排查方向 |
|---|---|
| remote_write 403(但安装时凭证自检通过) | 凭证链被环境残留带偏:查 /etc/default/服务名 里的 AWS_PROFILE 与 AWS_SHARED_CREDENTIALS_FILE 残留——systemd 的 EnvironmentFile 会覆盖 drop-in 的 Environment。修法:yml 的 sigv4 里写死 profile: amp-edge(第一道保障)+ 清理残留(实案:设备残留旧静态 key 指向致 403) |
| SigV4 签名被拒 / 时间相关报错 | 设备时钟偏差过大:timedatectl 确认 NTP 同步(node_timex_sync_status 应为 1) |
| 证书过期设备批量掉线 | 查 Trust Anchor 到期通知;用 device 子命令重签并下发新包;建议按到期日历提前 30 天轮换 |
| helper 无法运行 | Amzn2023 构建与极老 glibc 不兼容时从 GitHub 源码编译(Debian 13 实测可直接运行) |
| Grafana 查不到新设备 / 设备都叫 127.0.0.1:9100 | instance 标签必须用抓取 job 的 relabel_configs(target_label: instance + replacement: 设备名)设置——external_labels 无法覆盖抓取自动生成的 instance 标签 |
| 项目 | 计费 | 说明 |
|---|---|---|
| IAM Roles Anywhere | $0 | 服务本身零收费(无月费、无会话费) |
| 设备证书 | 自建 CA / 企业 PKI:$0 | openssl 签发不产生 AWS 费用。仅当改用 AWS Private CA 签发才有证书费:通用模式 $400/月 + $0.75/张(阶梯递减,1 万张后 $0.001);短周期模式 $50/月 + $0.058/张(证书 ≤7 天,本方案已排除) |
| AMP | 按摄入样本 + 存储 + 查询计费 | 主要成本项,正比于时间序列数量——务必用 node_exporter 白名单控系列数;精确估算用 AWS Pricing Calculator |
| Amazon Managed Grafana | 按活跃用户/月 | 编辑者与查看者价格不同;小团队也可自建 Grafana 连 AMP |
| 日常操作 | 方法 |
|---|---|
| 新增设备 | CloudShell 跑 device 子命令 → 包传设备 → 一键安装(裸机全自动) |
| 证书轮换(年度) | 重跑 device 子命令签新证 → 替换设备上的 crt/key → 重启服务;Trust Anchor 到期通知提前 30 天提醒 |
| 吊销单台设备 | Roles Anywhere 控制台导入 CRL,或直接更换 CA 全量换证(小车队更省事) |
| 控制系列数与费用 | node_exporter 用 --collector.systemd.unit-include 白名单;关闭不需要的 collector |
| 监控"监控本身" | up 序列即设备在线状态;Grafana 告警 up==0 超过阈值 |
Edge-Cloud Bastion 边缘设备统一接入平台 | 第一部分:JumpServer v4.x + frp v0.71.x + CloudFront VPC Origins | 第二部分:node_exporter + IAM Roles Anywhere + AMP + Grafana | 全流程经真实环境验证 第一部分脚本位于 jumpserver/ 与 frp/ 子目录 | 第二部分脚本位于 amp-edge-monitoring/ 子目录
单页离线版:edge-cloud-bastion.html(内容与本 README 等价,可直接发给客户离线阅读)