Skip to main content
🔒 SecurityROS 2 · June 2026

ROS 2 Security Guide 2026 — SROS2

Secure your ROS 2 robot with SROS2: create keystores and per-node enclaves, write governance.xml domain policies and permissions.xml topic access rules, enable DDS encryption with ROS_SECURITY_ENABLE, and debug authentication failures.

When You Need SROS2

SROS2 is essential for production deployments: hospital robots (PHI on the network), industrial cobots (prevent unauthorized command injection), autonomous vehicles (safety-critical commands), and any ROS 2 system exposed to untrusted networks. Development robots on isolated LANs can use ROS_SECURITY_STRATEGY=Permissive to test security without blocking communication.

1

SROS2 — DDS Security Overview

SROS2 (Secure ROS 2) implements the Object Management Group DDS Security specification. It provides authentication, encryption, and access control on top of the DDS transport — without changing application code.

text
SROS2 Security Architecture
═══════════════════════════════════════════════════════
DDS Security Spec (OMG) — 5 security plugins:

1. Authentication      — PKIX X.509 mutual authentication (who are you?)
2. Access Control      — XML policy files (what are you allowed to do?)
3. Cryptography        — AES-GCM-GMAC encryption + HMAC signing
4. Logging             — Security events to a dedicated topic
5. Data Tagging        — Classify sensitive messages (optional)

Files per node (called an "enclave"):
  identity_ca.cert.pem    — Certificate Authority that issued node certs
  cert.pem                — Node's own X.509 certificate
  key.pem                 — Node's private key (AES-256 encrypted)
  permissions_ca.cert.pem — CA that signed the permissions XML
  governance.xml(.p7s)    — DDS domain-level security settings
  permissions.xml(.p7s)   — Per-node topic allow/deny rules

Key environment variables:
  ROS_SECURITY_KEYSTORE       — path to the keystore root directory
  ROS_SECURITY_ENABLE         — "true" to enable (default: "false")
  ROS_SECURITY_STRATEGY       — "Enforce" | "Permissive" | "Keystore"
  ROS_SECURITY_ENCLAVE_SEARCH_METHOD  — "MATCH_PREFIX" | "EXACT"

Supported in: ROS 2 Foxy and later (best support in Humble+)
Requires DDS: FastDDS (eProsima) or ConnextDDS (RTI) — not CycloneDDS

SROS2 adds ~5-15% CPU overhead from encryption and signing. For edge robots with constrained compute, use PERMISSIVE mode in development and ENFORCE only in production deployments where security is required.

2

Create Keystore and Enclaves

ros2 security commands create a PKI keystore and per-node enclaves. Each node gets its own certificate signed by the identity CA.

bash
# Step 1: Install SROS2 tools
sudo apt install -y ros-humble-rmw-fastrtps-cpp
pip install lxml

# Step 2: Create the keystore (PKI hierarchy)
ros2 security create_keystore ~/my_robot_keystore

# Structure created:
# ~/my_robot_keystore/
# ├── public/
# │   ├── ca.cert.pem              (Identity CA certificate)
# │   └── permissions_ca.cert.pem  (Permissions CA certificate)
# ├── private/
# │   ├── ca.key.pem               (Identity CA private key — guard this!)
# │   └── permissions_ca.key.pem   (Permissions CA private key)
# └── enclaves/                    (one subfolder per ROS node)

# Step 3: Create an enclave for each node
# Enclave name = the ROS node's fully-qualified name
ros2 security create_enclave ~/my_robot_keystore /my_robot/lidar_node
ros2 security create_enclave ~/my_robot_keystore /my_robot/navigation
ros2 security create_enclave ~/my_robot_keystore /my_robot/controller

# Step 4: Create and sign governance.xml
ros2 security create_permission ~/my_robot_keystore   /my_robot/lidar_node   ~/policies/lidar_node_policy.xml

# Step 5: Verify the keystore
ros2 security list_enclaves ~/my_robot_keystore
3

governance.xml — Domain-Level Policy

governance.xml sets security defaults for the DDS domain. It controls whether discovery is encrypted, whether unauthenticated participants are allowed, and the default topic security.

xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- governance.xml — applied to the whole DDS domain -->
<dds xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="omg_shared_ca_governance.xsd">
  <domain_access_rules>
    <domain_rule>

      <!-- Apply to all domain IDs (0-232) -->
      <domains>
        <id_range>
          <min>0</min>
          <max>232</max>
        </id_range>
      </domains>

      <!-- Only authenticated DDS participants allowed -->
      <allow_unauthenticated_participants>FALSE</allow_unauthenticated_participants>
      <enable_join_access_control>TRUE</enable_join_access_control>

      <!-- Encrypt discovery (prevents scanning the network for topics) -->
      <discovery_protection_kind>ENCRYPT</discovery_protection_kind>

      <!-- Encrypt liveliness heartbeats -->
      <liveliness_protection_kind>ENCRYPT</liveliness_protection_kind>

      <!-- Per-topic security defaults (can be overridden in permissions.xml) -->
      <topic_access_rules>
        <topic_rule>
          <topic_expression>*</topic_expression>
          <!-- Require auth for pub/sub -->
          <enable_discovery_protection>TRUE</enable_discovery_protection>
          <enable_liveliness_protection>TRUE</enable_liveliness_protection>
          <!-- Encrypt all messages by default -->
          <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>

For performance-sensitive topics like /camera/image_raw, use data_protection_kind=SIGN instead of ENCRYPT. Signing adds authentication without the overhead of full encryption — images are large and encryption cost adds up.

4

permissions.xml — Per-Node Access Control

permissions.xml specifies which topics each node is allowed to publish to and subscribe from. The file is signed by the permissions CA private key.

xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- permissions.xml — access rules for /my_robot/lidar_node -->
<dds xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="omg_shared_ca_permissions.xsd">
  <permissions>
    <grant name="lidar_node_permissions">

      <!-- Matches the CN= in the node's certificate -->
      <subject_name>CN=/my_robot/lidar_node,O=RobotCA</subject_name>

      <validity>
        <not_before>2026-06-21T00:00:00</not_before>
        <not_after>2036-06-21T23:59:59</not_after>
      </validity>

      <!-- Allow publishing (what this node produces) -->
      <allow_rule>
        <domains><id>0</id></domains>
        <publish>
          <topics>
            <topic>/scan</topic>
            <topic>/scan/compressed</topic>
            <topic>/diagnostics</topic>
          </topics>
        </publish>
      </allow_rule>

      <!-- Allow subscribing (what this node reads) -->
      <allow_rule>
        <domains><id>0</id></domains>
        <subscribe>
          <topics>
            <topic>/parameter_events</topic>
            <topic>/rosout</topic>
          </topics>
        </subscribe>
      </allow_rule>

      <!-- Deny everything else -->
      <default>DENY</default>

    </grant>
  </permissions>
</dds>
5

Launch Nodes with Security Enabled

Set three environment variables to activate SROS2. The node binary is unchanged — security is handled by the RMW layer.

bash
# ── Terminal: set env vars then run the node ─────────────────
export ROS_SECURITY_KEYSTORE=~/my_robot_keystore
export ROS_SECURITY_ENABLE=true
export ROS_SECURITY_STRATEGY=Enforce    # or: Permissive (dev mode)

# Run the node normally — SROS2 handles authentication transparently
ros2 run my_pkg my_node --ros-args   --enclave /my_robot/lidar_node

# ── Launch file (Python) ──────────────────────────────────────
from launch import LaunchDescription
from launch_ros.actions import Node
import os

def generate_launch_description():
    keystore = os.path.expanduser("~/my_robot_keystore")
    security_env = {
        "ROS_SECURITY_KEYSTORE": keystore,
        "ROS_SECURITY_ENABLE": "true",
        "ROS_SECURITY_STRATEGY": "Enforce",
    }
    return LaunchDescription([
        Node(
            package="my_pkg",
            executable="my_node",
            name="lidar_node",
            namespace="my_robot",
            additional_env=security_env,
            arguments=["--ros-args", "--enclave", "/my_robot/lidar_node"],
        ),
    ])

# ── Test mode: Permissive (logs violations but doesn't block) ──
# Permissive = nodes without valid certs still communicate,
# but violations are logged. Use for development/migration.
export ROS_SECURITY_STRATEGY=Permissive

The enclave path (--enclave /my_robot/lidar_node) must exactly match the directory created by ros2 security create_enclave. If ROS_SECURITY_STRATEGY=Enforce and the enclave is missing, the node refuses to start.

6

Verify and Debug Secure Communications

Check that security is active and diagnose failures using ros2 security commands and FastDDS security logging.

bash
# List all enclaves in the keystore
ros2 security list_enclaves ~/my_robot_keystore

# Check if a running node has security active
# (Secure nodes appear with enclave info in verbose topic info)
ros2 node info /my_robot/lidar_node

# Enable FastDDS security logging (verbose)
export FASTRTPS_DEFAULT_PROFILES_FILE=~/fastdds_security_debug.xml

# fastdds_security_debug.xml:
# <profiles>
#   <participant profile_name="default_participant">
#     <rtps>
#       <propertiesPolicy>
#         <properties>
#           <property><name>dds.sec.log.plugin</name><value>builtin.DDS_LogTopic</value></property>
#           <property><name>dds.sec.log.builtin.DDS_LogTopic.logging_level</name><value>DEBUG</value></property>
#         </properties>
#       </propertiesPolicy>
#     </rtps>
#   </participant>
# </profiles>

# Subscribe to the security log topic
ros2 topic echo /ros_security_logging

# Common error causes:
# "AUTHENTICATION_FAILED"    → certificate mismatch or expired cert
# "ACCESS_CONTROL_VIOLATION" → node tried to pub/sub unauthorized topic
# "Couldn't find enclave"    → ROS_SECURITY_KEYSTORE path wrong or enclave missing

Quick Reference

Command / ConceptDetails
Create keystoreros2 security create_keystore ~/keystore
Create enclaveros2 security create_enclave ~/keystore /robot/node_name
List enclavesros2 security list_enclaves ~/keystore
Enable securityexport ROS_SECURITY_ENABLE=true ROS_SECURITY_KEYSTORE=~/ks ROS_SECURITY_STRATEGY=Enforce
Specify enclaveros2 run pkg node --ros-args --enclave /robot/node_name
Dev/test modeROS_SECURITY_STRATEGY=Permissive (logs but doesn't block)
governance.xmlDomain-level policy: allow_unauthenticated=FALSE, protection_kind=ENCRYPT
permissions.xmlPer-node allow_rule for <publish> and <subscribe> topics
ENCRYPT vs SIGNSIGN=auth only (faster), ENCRYPT=auth+confidentiality (for sensitive data)
Debugros2 topic echo /ros_security_logging + FASTRTPS_DEFAULT_PROFILES_FILE for verbose log