mcpField
R
robotics-security

robotics-security

github.com/arpitg1304セキュリティ386111 回インストール11 週間前に更新
リポジトリ

robotics-security について

robotics-securityは、ROS2ロボットシステム向けのセキュリティスキルです。DDSのアクセス制御、ネットワークやホストの強化、コンテナ保護、物理安全設計を扱い、ロボットやフリートの監査・保護に役立ちます。

主な機能

  1. 01SROS2の認証・暗号化・アクセス制御
  2. 02制御・データ・管理ネットワークの分離
  3. 03SSH・ホスト・コンテナの強化
  4. 04環境変数による秘密情報と証明書の管理
  5. 05ソフトウェアに依存しない非常停止の設計

活用シーン

  1. 01ROS2ノードのDDS権限設定
  2. 02ロボットネットワークと搭載コンピューターの保護
  3. 03フリートの認証情報と証明書のローテーション
  4. 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 シェル、ファームウェアの書き込み、データの持ち出し
署名されていないファームウェア更新モーターコントローラーに永続的なバックドアが仕込まれる

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>

権限 — エンクレーブごとのパブリッシュ/サブスクライブルールです。

<?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 は DDS レイヤーの権限によって拒否されるため、/cmd_vel にパブリッシュできません。

ネットワークの強化

ネットワークセグメンテーション
┌───────────────────┬──────────────────┬────────────────────────┐
│   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

セキュアブートとファームウェア

オーバーレイを使用した読み取り専用ルートファイルシステム
# /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 にパブリッシュできます。LAN 上のどのマシンからでも、1 つのコマンドでロボットを動かせます。

# BAD: anyone on the network can do this
ros2 topic pub /cmd_vel geometry_msgs/Twist "{linear: {x: 999.0}}"

対策: Enforce を使用した SROS2 を導入します。/cmd_vel へのパブリッシュを、認可されたエンクレーブに制限します。二次チェックとして速度安全ゲートを設けます。

# GOOD: unauthorized publish rejected at DDS layer
export ROS_SECURITY_ENABLE=true
export ROS_SECURITY_STRATEGY=Enforce
2. ロボット群で SSH キーを共有する

問題: 1 つのキーが侵害されると、ロボット群全体が侵害されます。

# 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 として実行する

問題: ノードが 1 つ侵害されると、システム全体への root アクセスを許してしまいます。

# BAD:
sudo ros2 launch my_robot bringup.launch.py

対策: 非特権ユーザーとして実行します。root の代わりに udev ルールを使ってハードウェアへのアクセスを許可します。

# 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

ロボティクスのセキュリティチェックリスト

  1. SROS2 が Enforce 戦略で有効 — すべてのノードで、暗号化および認証済みの DDS を使用します
  2. 最小権限のアクセス許可を持つノードごとのエンクレーブ — 各ノードは必要なトピックのみをパブリッシュ/サブスクライブします
  3. ネットワークを制御用/データ用/管理用 VLAN に分割 — ゾーン間にファイアウォールルールを設定します
  4. DDS マルチキャストを無効化 — ユニキャストのピアリストのみを使用し、LAN 上で自動検出を行いません
  5. SSH を強化 — 鍵認証のみ、デフォルト以外のポート、fail2ban を使用し、root ログインを禁止します
  6. 認証情報をハードコードしない — 権限 640 の環境ファイルからシークレットを取得します
  7. スケジュールに従って証明書をローテーション — 毎月のローテーションを自動化し、有効期間を明示します
  8. コンテナを非 root ユーザーで実行 — USER ディレクティブを設定し、no-new-privileges を有効にして、すべてのケーパビリティを削除します
  9. E-stop をハードウェアから独立 — すべてのソフトウェア/ネットワークが停止しても、ハードワイヤード回路が機能します
  10. 安全コントローラーを別のハードウェアに配置 — 速度/作業領域の制限をメインコンピューティング環境の外部で適用します
  11. コマンド速度をドライバーレベルで検証 — クランプ、レート制限、ゼロにするウォッチドッグを実装します
  12. auditd による監視を有効化 — キーストアへのアクセス、設定変更、USB イベント、root コマンドを記録します
  13. ファームウェア更新の署名を検証 — モーターコントローラーや安全 MCU で未署名のコードを使用しません
  14. CI/CD でセキュリティをテスト — 統合テストで SROS2 の Enforce を適用し、パイプラインでイメージをスキャンします

コメント