关于 robotics-security
robotics-security 是面向 ROS2 机器人系统的安全技能,涵盖 DDS 访问控制、网络与主机加固、容器安全及物理安全设计。适用于审计和加固机器人及机器人车队。
主要功能
- 01配置 SROS2 身份验证、加密与访问控制
- 02隔离控制、数据和管理网络
- 03加固 SSH、主机和容器
- 04通过环境变量管理密钥与证书
- 05设计独立于软件的急停保护
适用场景
- 01为 ROS2 节点配置 DDS 权限
- 02保护机器人网络和车载计算机
- 03管理车队凭据及证书轮换
- 04审查机器人系统的网络安全与安全停机
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ arpitg1304/ robotics-agent-skills --skill robotics-security安装后在 Claude Code 中输入/robotics-security确认已加载。
详细介绍
机器人安全技能
何时使用此技能
- 在 ROS2 主题/服务上启用 SROS2 加密和访问控制
- 为 DDS 生成密钥库、证书和安全策略
- 加固机器人车载计算机(SSH、防火墙、最小化软件包)
- 设置机器人控制、数据和管理平面之间的网络分段
- 管理机器人集群中的机密和凭据
- 保护运行 ROS2 节点的 Docker 容器
- 设计能够抵御网络攻击的急停和安全系统
- 审计机器人系统中的安全漏洞
- 实施安全启动和固件验证
- 满足工业机器人部署的 IEC 62443 要求
机器人的攻击面
机器人有其独特性:网络漏洞会演变成物理威胁。
NETWORK MIDDLEWARE APPLICATION
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ Open DDS ports │───────▶│ Unauthenticated│──────────▶│ Hardcoded │
│ (7400-7500) │ │ /cmd_vel pub │ │ credentials │
│ Unsegmented LAN│ │ No msg signing │ │ Unvalidated cmd│
└────────────────┘ └────────────────┘ └────────────────┘
PHYSICAL FIRMWARE SUPPLY CHAIN
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ USB/debug ports│───────▶│ Unsigned │──────────▶│ Compromised │
│ Serial consoles│ │ firmware OTA │ │ ROS packages │
│ Exposed SBCs │ │ No secure boot │ │ Unverified imgs│
└────────────────┘ └────────────────┘ └────────────────┘
| 向量 | 影响 |
|---|---|
未经身份验证的 /cmd_vel | 机器人意外移动 — 造成人身伤害/财产损失 |
传感器欺骗(/scan、/camera/image) | 机器人发生碰撞、做出错误决策 |
| 开放的 DDS 多播发现 | 被动监听者可枚举完整主题图 |
| USB/串口物理访问 | 获取 root shell、刷写固件、窃取数据 |
| 未签名的固件更新 | 在电机控制器中植入持久后门 |
SROS2:DDS 安全
SROS2 封装 DDS Security,在 DDS 层提供身份验证、加密和访问控制。
生成密钥库和设置证书
export ROS_SECURITY_KEYSTORE=~/sros2_keystore
ros2 security create_keystore ${ROS_SECURITY_KEYSTORE}
# Generate per-node enclaves (use exact fully-qualified node names)
ros2 security create_enclave ${ROS_SECURITY_KEYSTORE} /my_robot/camera_driver
ros2 security create_enclave ${ROS_SECURITY_KEYSTORE} /my_robot/navigation
ros2 security create_enclave ${ROS_SECURITY_KEYSTORE} /my_robot/motor_controller
ros2 security create_enclave ${ROS_SECURITY_KEYSTORE} /my_robot/teleop
# Result:
# sros2_keystore/
# ├── enclaves/my_robot/{camera_driver,navigation,...}/
# │ ├── cert.pem, key.pem # Node identity
# │ ├── governance.p7s # Signed governance
# │ └── permissions.p7s # Signed permissions
# ├── public/ca.cert.pem # CA certificate
# └── private/ca.key.pem # CA private key — PROTECT THIS
安全策略 XML
治理策略 — 域级安全行为:
<?xml version="1.0" encoding="UTF-8"?>
<dds xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="omg_shared_ca_governance.xsd">
<domain_access_rules>
<domain_rule>
<domains><id_range><min>0</min><max>230</max></id_range></domains>
<allow_unauthenticated_participants>false</allow_unauthenticated_participants>
<enable_join_access_control>true</enable_join_access_control>
<discovery_protection_kind>ENCRYPT</discovery_protection_kind>
<liveliness_protection_kind>ENCRYPT</liveliness_protection_kind>
<rtps_protection_kind>ENCRYPT</rtps_protection_kind>
<topic_access_rules>
<topic_rule>
<topic_expression>*</topic_expression>
<enable_discovery_protection>true</enable_discovery_protection>
<enable_read_access_control>true</enable_read_access_control>
<enable_write_access_control>true</enable_write_access_control>
<metadata_protection_kind>ENCRYPT</metadata_protection_kind>
<data_protection_kind>ENCRYPT</data_protection_kind>
</topic_rule>
</topic_access_rules>
</domain_rule>
</domain_access_rules>
</dds>
权限策略 — 每个 enclave 的发布/订阅规则:
<?xml version="1.0" encoding="UTF-8"?>
<dds xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="omg_shared_ca_permissions.xsd">
<permissions>
<grant name="/my_robot/motor_controller">
<subject_name>CN=/my_robot/motor_controller</subject_name>
<validity><not_before>2024-01-01T00:00:00</not_before>
<not_after>2035-01-01T00:00:00</not_after></validity>
<allow_rule>
<domains><id>0</id></domains>
<publish><topics><topic>rt/joint_states</topic></topics></publish>
<subscribe><topics><topic>rt/cmd_vel</topic></topics></subscribe>
</allow_rule>
<default>DENY</default>
</grant>
<grant name="/my_robot/teleop">
<subject_name>CN=/my_robot/teleop</subject_name>
<validity><not_before>2024-01-01T00:00:00</not_before>
<not_after>2035-01-01T00:00:00</not_after></validity>
<allow_rule>
<domains><id>0</id></domains>
<publish><topics><topic>rt/cmd_vel</topic></topics></publish>
<subscribe><topics><topic>rt/joy</topic></topics></subscribe>
</allow_rule>
<default>DENY</default>
</grant>
</permissions>
</dds>
过期会导致无声中断。 not_after 一旦过期,使用该授权的每个参与者都会在发现时被拒绝。在 ROS_SECURITY_STRATEGY=Enforce 下,机器人启动后各节点彼此不可见,唯一线索是 DDS 日志中的发现时拒绝记录——没有主题错误,也不会崩溃。像跟踪 TLS 证书一样跟踪权限过期时间:提前发出警报,并演练重新签发流程。
在启动文件中启用安全
import os
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
security_env = {
'ROS_SECURITY_KEYSTORE': os.path.expanduser('~/sros2_keystore'),
'ROS_SECURITY_ENABLE': 'true',
'ROS_SECURITY_STRATEGY': 'Enforce', # Enforce=reject unauth, Permissive=warn only
}
return LaunchDescription([
Node(package='my_robot_drivers', executable='motor_controller',
name='motor_controller', namespace='my_robot',
additional_env=security_env),
Node(package='my_robot_nav', executable='navigation',
name='navigation', namespace='my_robot',
additional_env=security_env),
])
生产环境中始终使用 Enforce。Permissive 会记录违规,但仍允许访问——仅用于调试。
按主题实施访问控制
遵循最小权限原则进行设计:
| 节点 | 发布 | 订阅 | 原因 |
|---|---|---|---|
motor_controller | /joint_states | /cmd_vel | 驱动器仅执行速度指令 |
navigation | /cmd_vel、/path | /scan、/odom、/map | 导航模块读取传感器数据并写入指令 |
camera_driver | /camera/image_raw | (无) | 纯数据源,不订阅任何内容 |
teleop | /cmd_vel | /joy | 手柄直通,攻击面最小 |
遭到入侵的 camera_driver 无法向 /cmd_vel 发布消息——DDS 层的权限会拒绝此操作。
网络加固
网络分段
┌───────────────────┬──────────────────┬────────────────────────┐
│ CONTROL PLANE │ DATA PLANE │ MANAGEMENT PLANE │
│ VLAN 10 │ VLAN 20 │ VLAN 30 │
│ 10.10.10.0/24 │ 10.10.20.0/24 │ 10.10.30.0/24 │
├───────────────────┼──────────────────┼────────────────────────┤
│ /cmd_vel, /odom │ /camera/image │ SSH, Prometheus │
│ /joint_states │ /pointcloud │ Log collection │
│ /e_stop │ /map, /rosbag │ Fleet mgmt API │
├───────────────────┼──────────────────┼────────────────────────┤
│ LOW LATENCY │ HIGH BANDWIDTH │ RESTRICTED ACCESS │
│ QoS: RELIABLE │ QoS: BEST_EFFORT │ Jump host / VPN + 2FA │
└───────────────────┴──────────────────┴────────────────────────┘
管理平面绝不能从数据平面访问。控制平面流量绝不经过 WiFi。
ROS2/DDS 防火墙规则
#!/bin/bash
# firewall_ros2.sh — adapt interface names to your hardware
iptables -F && iptables -X
# Default: drop inbound, allow outbound
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
iptables -A INPUT -i lo -j ACCEPT # Loopback (intra-process DDS)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # Existing connections
iptables -A INPUT -p udp --dport 7400:7500 -s 10.10.10.0/24 -j ACCEPT # DDS discovery — control VLAN
iptables -A INPUT -p udp --dport 7500:7700 -s 10.10.10.0/24 -j ACCEPT # DDS user traffic
iptables -A INPUT -p tcp --dport 22 -s 10.10.30.0/24 -j ACCEPT # SSH — mgmt VLAN only
iptables -A INPUT -i wlan0 -d 239.255.0.0/16 -j DROP # Block multicast on WiFi
iptables -A INPUT -j LOG --log-prefix "DROPPED: " --log-level 4
iptables -A INPUT -j DROP
iptables-save > /etc/iptables/rules.v4
机器人网络的 VLAN 配置
# /etc/netplan/01-robot-vlans.yaml
network:
version: 2
renderer: networkd
ethernets:
eth0: {dhcp4: false}
vlans:
vlan10:
id: 10
link: eth0
addresses: [10.10.10.5/24]
vlan20:
id: 20
link: eth0
addresses: [10.10.20.5/24]
vlan30:
id: 30
link: eth0
addresses: [10.10.30.5/24]
routes: [{to: default, via: 10.10.30.1}]
在生产环境中禁用 DDS 多播
多播自动发现会暴露完整主题图。请使用单播节点列表。
<CycloneDDS>
<Domain>
<General><AllowMulticast>false</AllowMulticast></General>
<Discovery>
<Peers>
<Peer address="10.10.10.1"/>
<Peer address="10.10.10.2"/>
<Peer address="10.10.10.3"/>
</Peers>
<ParticipantIndex>auto</ParticipantIndex>
</Discovery>
</Domain>
</CycloneDDS>
export CYCLONEDDS_URI=file:///etc/ros2/cyclonedds_secure.xml
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
FastDDS 的等效设置——在参与者配置文件中设置 initialPeersList,指定明确的单播定位器,并省略多播定位器。使用 FASTRTPS_DEFAULT_PROFILES_FILE 环境变量加载配置。
SSH 和主机加固
仅使用 SSH 密钥进行身份验证,禁用 root 登录
# /etc/ssh/sshd_config
Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers robot-admin
X11Forwarding no
AllowTcpForwarding no
PermitTunnel no
sudo systemctl restart sshd
# Per-robot key pair (on management workstation)
ssh-keygen -t ed25519 -f ~/.ssh/robot_$(hostname) -C "admin@$(hostname)"
ssh-copy-id -i ~/.ssh/robot_$(hostname).pub -p 2222 [email protected]
在机器人计算机上使用 fail2ban
# /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
sudo apt install fail2ban -y && sudo systemctl enable --now fail2ban
无人值守安全更新
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgrades
# Key settings in /etc/apt/apt.conf.d/50unattended-upgrades:
# Allowed-Origins: "${distro_id}:${distro_codename}-security"
# Automatic-Reboot: "false" # NEVER auto-reboot a running robot
最小化已安装软件包
# Remove unnecessary packages from robot computers
sudo apt purge -y avahi-daemon cups snapd modemmanager bluetooth bluez
sudo apt autoremove -y
机密管理
不要硬编码凭据
# BAD:
class FleetClient:
def __init__(self):
self.api_key = "sk-live-abc123xyz789"
# GOOD:
import os
class FleetClient:
def __init__(self):
self.api_key = os.environ['FLEET_API_KEY']
# BAD: credentials in params.yaml tracked by git
fleet_manager:
ros__parameters:
aws_secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
# GOOD: reference environment variables
fleet_manager:
ros__parameters:
aws_secret_key: "$(env AWS_SECRET_KEY)"
为 ROS2 节点使用基于环境变量的机密
# /etc/systemd/system/robot-nav.service
[Service]
User=robot
Group=robot
EnvironmentFile=/etc/robot/secrets.env
ExecStart=/opt/ros/humble/bin/ros2 launch my_robot nav.launch.py
Restart=always
# /etc/robot/secrets.env
FLEET_API_KEY=sk-live-actual-key-here
ROS_SECURITY_KEYSTORE=/opt/robot/sros2_keystore
# Lock it down
sudo chown root:robot /etc/robot/secrets.env
sudo chmod 640 /etc/robot/secrets.env
证书轮换模式
#!/bin/bash
# rotate_certs.sh — run via cron monthly
set -euo pipefail
KEYSTORE="/opt/robot/sros2_keystore"
cp -r "${KEYSTORE}" "${KEYSTORE}_backup_$(date +%Y%m%d)"
for enclave in motor_controller navigation camera_driver teleop; do
ros2 security create_enclave "${KEYSTORE}" "/my_robot/${enclave}"
done
sudo systemctl restart robot-*.service
echo "Certificates rotated at $(date)"
# /etc/cron.d/robot-cert-rotation
0 3 1 * * root /opt/robot/scripts/rotate_certs.sh >> /var/log/cert-rotation.log 2>&1
密钥库文件权限
sudo chown -R root:robot /opt/robot/sros2_keystore
sudo find /opt/robot/sros2_keystore -type d -exec chmod 750 {} \;
sudo find /opt/robot/sros2_keystore -type f -exec chmod 640 {} \;
# CA private key — root only
sudo chmod 600 /opt/robot/sros2_keystore/private/ca.key.pem
sudo chown root:root /opt/robot/sros2_keystore/private/ca.key.pem
容器安全
非 root 容器
FROM ros:humble-ros-base AS runtime
RUN apt-get update && apt-get install -y --no-install-recommends \
ros-humble-nav2-bringup && rm -rf /var/lib/apt/lists/*
RUN groupadd -g 1000 robot && useradd -u 1000 -g robot -m -s /bin/false robot
COPY --from=builder /opt/ros2_ws/install /opt/ros2_ws/install
USER robot:robot
ENTRYPOINT ["/ros_entrypoint.sh"]
CMD ["ros2", "launch", "my_robot", "nav.launch.py"]
最小化运行时镜像
FROM ros:humble-desktop AS builder
WORKDIR /opt/ros2_ws
COPY src/ src/
RUN . /opt/ros/humble/setup.sh && \
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release --merge-install
FROM ros:humble-ros-core AS runtime
COPY --from=builder /opt/ros2_ws/install /opt/ros2_ws/install
# Remove shell and package manager — prevents interactive exploitation
RUN rm -f /bin/sh /bin/bash /bin/dash && apt-get purge -y --auto-remove apt
镜像扫描和签名
trivy image --severity HIGH,CRITICAL my-robot/navigation:latest
cosign sign --key cosign.key my-registry.io/my-robot/navigation:v1.2.3
cosign verify --key cosign.pub my-registry.io/my-robot/navigation:v1.2.3 || exit 1
只读根文件系统
# docker-compose.yml
services:
motor_controller:
image: my-robot/motor-controller:v1.0.0
user: "1000:1000"
read_only: true
tmpfs: ["/tmp:size=64M", "/var/log/ros:size=32M"]
volumes:
- type: bind
source: /opt/robot/sros2_keystore/enclaves/my_robot/motor_controller
target: /keystore
read_only: true
security_opt: ["no-new-privileges:true"]
cap_drop: [ALL]
environment:
ROS_SECURITY_KEYSTORE: /keystore
ROS_SECURITY_ENABLE: "true"
ROS_SECURITY_STRATEGY: Enforce
物理安全与网络安全的交汇点
针对机器人的网络攻击会造成人身伤害。标准 IT 安全措施必不可少,但还不够。
急停独立性
无论所有软件、网络和主计算设备是否完全失效,急停都必须正常工作。
┌──────────┐ HARDWIRED ┌─────────────────┐
│ Physical │ ─────────────────▶│ Safety Relay / │──▶ Motor power cut
│ E-Stop │ Direct circuit │ Safety PLC │ via contactor
│ Button │ NO software └─────────────────┘
└──────────┘
┌──────────┐ OPTIONAL
│ Software │ ───(notifies)───▶ Can trigger relay, but NOT sole path
│ E-Stop │
└──────────┘
Main compute crash ──X──▶ Cannot prevent hardware e-stop
Network failure ──X──▶ Cannot prevent hardware e-stop
设计规则:使用硬接线电路断开电机电源;软件可以触发继电器,但绝不能是唯一的控制路径;无线急停使用专用无线电,而不是 WiFi。
安全控制器隔离
┌──────────────────────────────┬───────────────────────────────┐
│ MAIN COMPUTE (Jetson/x86) │ SAFETY CONTROLLER (STM32/MCU) │
│ Ubuntu + ROS2 │ Bare-metal firmware │
│ Nav, Perception, Planning │ │
│ ──── CAN/UART ──▶│ Validates: │
│ cmd_vel │ - Max velocity │
│ │ - Max acceleration │
│ ◀── joint_fb ────│ - Workspace limits │
│ │ - Watchdog timeout │
│ If compromised, safety │ Rejects out-of-bounds cmds │
│ controller STILL enforces │ Runs on separate hardware │
│ physical limits. │ Does NOT run ROS2 or Linux │
└──────────────────────────────┴───────────────────────────────┘
命令速度验证与速率限制
在驱动程序层强制执行——这是执行器前的最后一道防线:
# velocity_safety_gate.py
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
class VelocitySafetyGate(Node):
def __init__(self):
super().__init__('velocity_safety_gate')
self.declare_parameter('max_linear_vel', 1.0) # m/s
self.declare_parameter('max_angular_vel', 2.0) # rad/s
self.declare_parameter('max_linear_accel', 0.5) # m/s^2
self.declare_parameter('cmd_timeout_sec', 0.5)
self.declare_parameter('max_cmd_rate_hz', 50.0)
self.max_lin = self.get_parameter('max_linear_vel').value
self.max_ang = self.get_parameter('max_angular_vel').value
self.max_acc = self.get_parameter('max_linear_accel').value
self.timeout = self.get_parameter('cmd_timeout_sec').value
self.min_period = 1.0 / self.get_parameter('max_cmd_rate_hz').value
self.last_cmd_time = self.get_clock().now()
self.last_linear = 0.0
self.last_pub_sec = 0.0
self.sub = self.create_subscription(Twist, 'cmd_vel_raw', self.on_cmd, 10)
self.pub = self.create_publisher(Twist, 'cmd_vel', 10)
self.create_timer(0.1, self.watchdog_check)
def on_cmd(self, msg: Twist):
now = self.get_clock().now()
now_sec = now.nanoseconds / 1e9
if (now_sec - self.last_pub_sec) < self.min_period:
return # Rate limit exceeded — drop
msg.linear.x = max(-self.max_lin, min(self.max_lin, msg.linear.x))
msg.angular.z = max(-self.max_ang, min(self.max_ang, msg.angular.z))
dt = (now - self.last_cmd_time).nanoseconds / 1e9
if dt > 0:
accel = abs(msg.linear.x - self.last_linear) / dt
if accel > self.max_acc:
sign = 1.0 if msg.linear.x > self.last_linear else -1.0
msg.linear.x = self.last_linear + sign * self.max_acc * dt
self.pub.publish(msg)
self.last_cmd_time = now
self.last_linear = msg.linear.x
self.last_pub_sec = now_sec
def watchdog_check(self):
elapsed = (self.get_clock().now() - self.last_cmd_time).nanoseconds / 1e9
if elapsed > self.timeout:
self.pub.publish(Twist()) # No command → zero velocity
看门狗独立于应用软件
# Hardware watchdog — kernel resets system if not fed
import os
class HardwareWatchdog:
"""Uses /dev/watchdog. If not fed within timeout, kernel triggers reset."""
def __init__(self):
self.fd = os.open('/dev/watchdog', os.O_WRONLY) # Starts countdown
def feed(self):
os.write(self.fd, b'\x00') # Reset countdown
def close(self):
os.write(self.fd, b'V') # Magic close — disarm gracefully
os.close(self.fd)
# /etc/watchdog.conf
watchdog-device = /dev/watchdog
watchdog-timeout = 15
interval = 5
pidfile = /var/run/robot-safety-monitor.pid
max-load-1 = 24
安全启动与固件
带 Overlay 的只读根文件系统
# /etc/fstab
/dev/mmcblk0p2 / ext4 ro,noatime,errors=remount-ro 0 1
tmpfs /tmp tmpfs nosuid,nodev,size=128M 0 0
tmpfs /var/log tmpfs nosuid,nodev,size=128M 0 0
/dev/mmcblk0p3 /data ext4 rw,noatime,nosuid,nodev 0 2
# Alternative: overlayroot — all writes go to tmpfs, lost on reboot
sudo apt install overlayroot -y
# /etc/overlayroot.conf → overlayroot="tmpfs:swap=1"
已签名容器镜像
#!/bin/bash
set -euo pipefail
IMAGE="registry.myrobot.io/robot/navigation"
TAG="v$(cat VERSION)-$(git rev-parse --short HEAD)"
docker build -t "${IMAGE}:${TAG}" -f Dockerfile.prod .
trivy image --exit-code 1 --severity CRITICAL "${IMAGE}:${TAG}"
docker push "${IMAGE}:${TAG}"
cosign sign --key env://COSIGN_PRIVATE_KEY "${IMAGE}:${TAG}"
syft "${IMAGE}:${TAG}" -o spdx-json > sbom.json
cosign attach sbom --sbom sbom.json "${IMAGE}:${TAG}"
基于 TPM 的磁盘加密
# LUKS + TPM2 for unattended encrypted boot
sudo cryptsetup luksFormat /dev/mmcblk0p3
sudo cryptsetup luksOpen /dev/mmcblk0p3 robot-data
sudo systemd-cryptenroll /dev/mmcblk0p3 --tpm2-device=auto --tpm2-pcrs=0+7
# Disk decrypts only on original hardware with unmodified firmware
固件更新验证
from pathlib import Path
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes, serialization
import subprocess
def verify_and_flash(firmware: Path, signature: Path, pubkey_path: Path):
pubkey = serialization.load_pem_public_key(pubkey_path.read_bytes())
try:
pubkey.verify(signature.read_bytes(), firmware.read_bytes(),
ec.ECDSA(hashes.SHA256()))
except Exception:
raise SecurityError("Firmware signature verification FAILED — aborting")
subprocess.run(['flash-tool', '--write', str(firmware)], check=True)
审计与监控
ROS2 安全日志记录
# topic_auditor.py — logs publications on sensitive topics
import rclpy, json, time
from rclpy.node import Node
from geometry_msgs.msg import Twist
class TopicAuditor(Node):
def __init__(self):
super().__init__('topic_auditor')
self.log = open('/var/log/ros2_audit.jsonl', 'a')
self.create_subscription(Twist, '/cmd_vel', self.audit_cmd_vel, 10)
def audit_cmd_vel(self, msg: Twist):
record = {'ts': time.time(), 'topic': '/cmd_vel',
'lin_x': msg.linear.x, 'ang_z': msg.angular.z}
self.log.write(json.dumps(record) + '\n')
self.log.flush()
if abs(msg.linear.x) > 0.8 or abs(msg.angular.z) > 1.5:
self.get_logger().warn(f'HIGH VEL: lin={msg.linear.x:.2f} ang={msg.angular.z:.2f}')
命令主题上的入侵检测
# cmd_vel_anomaly_detector.py
import numpy as np
from collections import deque
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
class CmdVelAnomalyDetector(Node):
def __init__(self):
super().__init__('cmd_vel_anomaly_detector')
self.window = deque(maxlen=100)
self.alert_pub = self.create_publisher(Twist, '/security/cmd_vel_alert', 10)
self.create_subscription(Twist, '/cmd_vel', self.on_cmd, 10)
def on_cmd(self, msg: Twist):
self.window.append((msg.linear.x, msg.angular.z))
if len(self.window) < 20:
return
vels = np.array(list(self.window))
z_scores = np.abs((np.array([msg.linear.x, msg.angular.z]) - vels.mean(0)) / (vels.std(0) + 1e-6))
if np.any(z_scores > 3.0):
self.get_logger().error(f'ANOMALY: lin={msg.linear.x:.3f} ang={msg.angular.z:.3f} z={z_scores}')
self.alert_pub.publish(msg)
机器人系统的 auditd 规则
# /etc/audit/rules.d/robot-security.rules
-w /opt/robot/sros2_keystore/ -p rwxa -k robot_keystore
-w /etc/robot/ -p wa -k robot_config
-w /home/robot-admin/.ssh/ -p wa -k ssh_keys
-w /opt/robot/firmware/ -p rwxa -k firmware_access
-w /etc/systemd/system/robot- -p wa -k robot_services
-a always,exit -F arch=b64 -F euid=0 -S execve -k root_commands
-w /dev/bus/usb/ -p rwxa -k usb_access
-w /etc/netplan/ -p wa -k network_config
-w /etc/iptables/ -p wa -k firewall_config
-w /usr/bin/docker -p x -k docker_exec
sudo auditctl -R /etc/audit/rules.d/robot-security.rules
sudo systemctl enable --now auditd
sudo ausearch -k robot_keystore --start today
机器人安全反模式
1. 未经身份验证的 /cmd_vel
**问题:**默认情况下,ROS2 允许任何 DDS 参与者向 /cmd_vel 发布消息。局域网上任意一台机器发出一条命令,就能让机器人移动。
# BAD: anyone on the network can do this
ros2 topic pub /cmd_vel geometry_msgs/Twist "{linear: {x: 999.0}}"
**修复方法:**使用带有 Enforce 的 SROS2。限制只有已授权的 enclave 才能向 /cmd_vel 发布消息。将速度安全门控作为第二道检查。
# GOOD: unauthorized publish rejected at DDS layer
export ROS_SECURITY_ENABLE=true
export ROS_SECURITY_STRATEGY=Enforce
2. 整个机器人机群共用 SSH 密钥
**问题:**一把密钥遭到泄露,整个机群就会被攻陷。
# BAD: same key for all robots
ssh-copy-id -i ~/.ssh/fleet_key.pub robot@robot-001
ssh-copy-id -i ~/.ssh/fleet_key.pub robot@robot-002
**修复方法:**为每台机器人使用唯一密钥。使用有效期较短的证书和 SSH CA。
# GOOD: SSH CA issues 8-hour certs per session
ssh-keygen -s /etc/ssh/ca_key -I "session-$(date +%s)" -n robot-admin -V +8h ~/.ssh/id_ed25519.pub
3. 以 root 身份运行所有节点
**问题:**任一节点遭到攻陷,攻击者就能获得系统的完整 root 访问权限。
# BAD:
sudo ros2 launch my_robot bringup.launch.py
**修复方法:**以非特权用户身份运行。使用 udev 规则访问硬件,而不是使用 root。
# GOOD:
sudo -u robot ros2 launch my_robot bringup.launch.py
# /etc/udev/rules.d/99-robot.rules:
# SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", MODE="0660", GROUP="robot"
4. 不进行网络分段
**问题:**所有流量都处于同一平面网络。被攻陷的 IP 摄像头可以访问电机控制器。
**修复方法:**通过 VLAN 分段,并设置 VLAN 间防火墙规则。请参阅“网络加固”部分。
# BAD: everything on 192.168.1.0/24
# GOOD: VLAN 10 control (wired), VLAN 20 data, VLAN 30 mgmt (jump host)
5. 在启动文件中硬编码凭据
**问题:**版本控制中的凭据会因仓库访问权限、CI 日志和 Docker 镜像层而暴露。
# BAD: in params.yaml tracked by git
cloud_connector:
ros__parameters:
aws_access_key: "AKIAIOSFODNN7EXAMPLE"
**修复方法:**从受保护的文件中读取环境变量。使用 gitleaks 扫描仓库。
# GOOD: secrets injected at runtime via systemd EnvironmentFile
gitleaks detect --source . --verbose # Pre-commit check
6. 通过网络触发急停
**问题:**仅将通过 ROS2 触发的软件急停用作安全机制。网络中断就无法停止机器人。
# BAD: sole e-stop is a ROS2 topic subscriber
self.create_subscription(Bool, '/e_stop', self.software_estop, 10)
**修复方法:**使用硬接线急停电路。软件急停是额外的保护层,绝不能作为唯一的控制路径。
7. 不轮换证书
**问题:**SROS2 证书生成后从不轮换。密钥一旦泄露,攻击者就能永久访问。
**修复方法:**通过 cron 每月自动轮换。在权限 XML 中明确设置有效期。通过机群管理功能支持紧急轮换。
8. 为了方便而禁用安全功能
**问题:**由于“太难配置”或“会增加延迟”,在生产环境中禁用了 SROS2。这是机器人安全领域最常见的故障。
# BAD: "temporary" becomes permanent
export ROS_SECURITY_ENABLE=false
**修复方法:**从第一天起就在 CI/CD 中启用安全功能。测试必须在 Enforce 模式下通过。
# GOOD: CI enforces security
export ROS_SECURITY_ENABLE=true
export ROS_SECURITY_STRATEGY=Enforce
ros2 launch my_robot test.launch.py # Must pass with security on
机器人安全检查清单
- 已启用 SROS2 的
Enforce策略 — 所有节点均使用加密且经过身份验证的 DDS - 每个节点使用独立 enclave,并遵循最小权限原则 — 每个节点仅发布/订阅所需主题
- 网络划分为控制、数据和管理 VLAN — 各区域之间设置防火墙规则
- 已禁用 DDS 多播 — 仅使用单播对等节点列表,不在 LAN 上自动发现
- 已加固 SSH — 仅允许密钥认证、使用非默认端口、启用 fail2ban、禁止 root 登录
- 未硬编码凭据 — 从权限为 640 的环境文件中读取密钥
- 按计划轮换证书 — 每月自动轮换,并明确设置有效期
- 容器以非 root 用户运行 — 使用 USER 指令、启用 no-new-privileges、移除所有 capabilities
- E-stop 不依赖硬件之外的软件 — 即使所有软件和网络均不可用,硬接线电路仍可工作
- 安全控制器运行在独立硬件上 — 在主计算设备之外强制执行速度/工作空间限制
- 在驱动程序层验证命令速度 — 限幅、速率限制,并通过看门狗将速度置零
- 已启用 auditd 监控 — 记录密钥库访问、配置更改、USB 事件和 root 命令
- 固件更新经过签名验证 — 电机控制器或安全 MCU 上不得运行未签名代码
- 在 CI/CD 中进行安全测试 — 在集成测试中启用 SROS2 Enforce,并在流水线中扫描镜像