- Have ROS 2 (
ros_core
) installed, here assumed ROS Dashing located at/opt/ros/dashing
- ROS 2 development tools (
sudo apt install python3-colcon-common-extensions python3-rosdep
)
source /opt/ros/dashing/setup.bash
export CONFBOT_WS=$HOME/confbot_demo/confbot_ws
mkdir -p $CONFBOT_WS/src && cd $CONFBOT_WS && git clone https://github.com/Karsten1987/confbot_robot src/confbot_robot
To make the rest of the demo easier we recommend you to place this export in your .bashrc
echo "export CONFBOT_WS=$HOME/confbot_demo/confbot_ws" >> ~/.bashrc
cd $CONFBOT_WS
sed -i s@/Users/karsten/workspace/osrf/confbot_robot_ws/@$CONFBOT_WS/@g `grep -lr "/karsten/" --exclude Linux_Tutorial.md`
sudo rosdep init
rosdep update
rosdep install -y --from-paths src --ignore-src
colcon build --merge-install --cmake-args --no-warn-unused-cli -DSECURITY=ON -DBUILD_TESTING=OFF
sudo apt-get install -y \
ros-$ROS_DISTRO-ros-core \
ros-$ROS_DISTRO-ros2bag \
ros-$ROS_DISTRO-rosbag2 \
ros-$ROS_DISTRO-rosbag2-converter-default-plugins \
ros-$ROS_DISTRO-rosbag2-storage-default-plugins \
ros-$ROS_DISTRO-rqt-graph \
ros-$ROS_DISTRO-rqt-image-view \
ros-$ROS_DISTRO-rqt-plot \
ros-$ROS_DISTRO-rqt-publisher \
ros-$ROS_DISTRO-rqt-service-caller \
ros-$ROS_DISTRO-rviz2
In each terminal from now on, always assume use a sourced environment:
source $CONFBOT_WS/install/setup.bash
You can also add it to your .bashrc for convenience
echo "source $CONFBOT_WS/install/setup.bash" >> ~/.bashrc
Terminal 1:
ros2 launch confbot_bringup confbot_bringup.launch.py
Terminal 2:
$ ros2 node list
/launch_ros
/robot_state_publisher
/safe_zone_publisher
/confbot_driver_container
/confbot_driver
/twist_publisher
/confbot_sensors_container
/confbot_laser
/static_tf_wheel_left
/static_tf_wheel_right
/static_tf_caster_wheel_front
/static_tf_caster_wheel_rear
$ ros2 topic list -t
/cmd_vel [geometry_msgs/Twist]
/confbot_laser/transition_event [lifecycle_msgs/TransitionEvent]
/danger_zone [visualization_msgs/Marker]
/joint_states [sensor_msgs/JointState]
/parameter_events [rcl_interfaces/ParameterEvent]
/robot_description [std_msgs/String]
/rosout [rcl_interfaces/Log]
/safe_zone [visualization_msgs/Marker]
/tf [tf2_msgs/TFMessage]
/tf_static [tf2_msgs/TFMessage]
$ ros2 topic echo /cmd_vel
linear:
x: 0.10000000149011612
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 0.10000000149011612
---
linear:
x: 0.10000000149011612
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 0.10000000149011612
---
^C
Terminal 2:
ros2 run rviz2 rviz2 -d `ros2 pkg prefix confbot_bringup --share`/config/confbot.rviz
Terminal 3:
Leverage rqt_graph
to inspect the ROS node graph
ros2 run rqt_gui rqt_gui --perspective-file `ros2 pkg prefix confbot_bringup --share`/config/confbot_bringup_qt.perspective
Hit the refresh button to actualize the graph.
Terminal 3:
The node /twist_publisher
has a speed parameter.
$ ros2 param list /twist_publisher
speed
use_sim_time
We can check it's initial value:
$ ros2 param get /twist_publisher speed
Double value is: 0.10000000149011612
Let's try to increase the speed of our robot by setting the parameter to 0.2.
$ ros2 param set /twist_publisher speed 0.2
Set parameter successful
In RViz: robot goes twice as fast
Terminal 3:
$ ros2 param get /twist_publisher speed
Double value is: 0.2
$ ros2 param set /twist_publisher speed 0.1
Set parameter successful
$ ros2 param get /twist_publisher speed
Double value is: 0.1
Terminal 3:
$ ros2 lifecycle nodes
/confbot_laser
$ ros2 lifecycle list /confbot_laser
- configure [1]
Start: unconfigured
Goal: configuring
- shutdown [5]
Start: unconfigured
Goal: shuttingdown
$ ros2 lifecycle set /confbot_laser configure
Transitioning successful
$ ros2 lifecycle list /confbot_laser
- cleanup [2]
Start: inactive
Goal: cleaningup
- activate [3]
Start: inactive
Goal: activating
- shutdown [6]
Start: inactive
Goal: shuttingdown
$ ros2 lifecycle set /confbot_laser activate
In RViz: the laser node is now publishing fake laser data!
Terminal 3: Listing running components:
$ ros2 component list
/confbot_driver_container
1 /confbot_driver
2 /twist_publisher
Unload the twist_publisher
component:
$ ros2 component unload /confbot_driver_container 2
Unloaded component 2 from '/confbot_driver_container' container node
In RViz the robot stops moving
List available components:
$ ros2 component types
confbot_sensors
confbot_sensors::nodes::ConfbotLaser
confbot_driver
confbot_driver::nodes::ConfbotDriver
confbot_driver::nodes::TwistPublisher
Load a component:
$ ros2 component load /confbot_driver_container confbot_driver confbot_driver::nodes::TwistPublisher
Loaded component 3 into '/confbot_driver_container' container node as '/twist_publisher'
Robot starts moving again in RViz
$ ros2 component list
/confbot_driver_container
1 /confbot_driver
3 /twist_publisher
/confbot_sensors_container
1 /confbot_laser
In the example above, the robot got moved around by sending velocity commands on a specific topic called /cmd_vel
.
This makes sense when considering the twist publisher being a joystick, where commands shall be sent to the robot as long as the joystick buttons are pressed.
However, one can think of another example such as a navigation goal, where the robot is directed to move for a certain amount of time with a specific velocity. For this use case, we introduced an action server where we can asynchronously send a single command to the robot and periodically ask for feedback of its state. In order for this to visualize properly, we want to shutdown the twist publisher first.
$ ros2 component unload /confbot_driver_container 3
Next, we provide an already prepared executable which sends an action goal to the robot (drive in circle for 10 seconds) and then asks for the current robot state until the action is finished.
$ ros2 run confbot_driver confbot_actionclient
As you can see on the terminal output, we can get information about the current state:
[INFO] [confbot_actionclient]: Sending goal
[INFO] [confbot_actionclient]: Waiting for result
[INFO] [confbot_actionclient]: Confbot traveled 0.100000 so far in 0 seconds
...
[INFO] [confbot_actionclient]: Confbot traveled 10.000002 so far in 9 seconds
[INFO] [confbot_actionclient]: result received
[INFO] [confbot_actionclient]: robot traveled 10.100002 meters
There further exists a command line tool which allows to send the same goal without the need of an executable.
ros2 action send_goal /move_command confbot_msgs/action/MoveCommand "{duration: {sec: 10}, linear_velocity: 0.1, angular_velocity: 0.1}"
Waiting for an action server to become available...
Sending goal:
duration:
sec: 10
nanosec: 0
linear_velocity: 0.1
angular_velocity: 0.1
Goal accepted with ID: 128044c1ecaa46d287941597273fcdb7
Result:
distance_traveled: 10.000001907348633
Goal finished with status: SUCCEEDED
Optionaly we can print the feedback message using the --feedback
flag.
Let's start our twist_publisher component again before experimetning with rosbags
$ ros2 component load /confbot_driver_container confbot_driver confbot_driver::nodes::TwistPublisher
Loaded component 4 into '/confbot_driver_container' container node as '/twist_publisher'
Starting from Dashing, rosbag2 is available and can be used to record and replay ros2 topics.
For our demo, we are going to record the cmd_vel
topic and replay it afterwards.
The user is encouraged to follow the steps below, but for completeness, we provided already pre-recorded bag files in the confbot_driver/resources
folder.
It is important to make sure that the twist_publisher
node is currently running and correctly publishing data on the cmd_vel
topic.
cd $CONFBOT_WS
ros2 bag record -o ros2_cmd_vel_bag /cmd_vel
[INFO] [rosbag2_storage]: Opened database 'ros2_cmd_vel_bag'.
[INFO] [rosbag2_transport]: Listening for topics...
[INFO] [rosbag2_transport]: Subscribed to topic '/cmd_vel'
[INFO] [rosbag2_transport]: All requested topics are subscribed. Stopping discovery...
^C[INFO] [rclcpp]: signal_handler(signal_value=2)
The command above records all incoming data on the cmd_vel
topic.
We can use the rosbag command line tool to introspect our recording:
ros2 bag info ros2_cmd_vel_bag
Files: ros2_cmd_vel_bag.db3
Bag size: 56.4 KiB
Storage id: sqlite3
Duration: 6.897s
Start: Jun 4 2019 22:33:28.963 (1559712808.963)
End Jun 4 2019 22:33:35.861 (1559712815.861)
Messages: 70
Topic information: Topic: /cmd_vel | Type: geometry_msgs/msg/Twist | Count: 70 | Serialization Format: cdr
Before replaying our ros2_cmd_vel_bag
bagfile, we have to stop the twist_publisher
to make sure we don't have two nodes publishing on the cmd_vel
topic simultaneously.
In our demo, the twist publisher is loaded under the /confbot_driver_container
with ID 2 as shown above.
ros2 component unload /confbot_driver_container 4
Unloaded component 4 from '/confbot_driver_container' container node
With the twist publisher stopped, you can see that the robot does not move any longer in RViz. The next step is now to replay the bagfile:
ros2 bag play ros2_cmd_vel_bag
[INFO] [rosbag2_storage]: Opened database 'ros2_cmd_vel_bag'.
While the bag file is playing, our confbot robot moves again in RViz until the bag file is completely replayed.
For further demo purposes, you can find a legacy ROS 1 bag file in confbot_driver/resources
.
The current rosbag2 implementation provides plugins which allow to replay - and replay only - existing legacy ROS 1 bag files.
Needless to say, this requires a parallel ROS 1 melodic installation and thus only works on Linux 18.04.
First, you will need to make sure to have the ROS 1 repositories in your sources:
sudo apt install -y ros-dashing-rosbag2-bag-v2-plugins
In a fresh terminal:
source /opt/ros/melodic/setup.bash
cd $CONFBOT_WS/src/confbot_robot/confbot_driver/resources
rosbag info ros1_cmd_vel.bag
You can see that this is a ROS1 rosbag as usual. When specifying the plugin for reading legacy rosbags in ROS2, we can load the same information in ROS:
source /opt/ros/melodic/setup.bash
source /opt/ros/dashing/setup.bash
cd $CONFBOT_WS/src/confbot_robot/confbot_driver/resources
ros2 bag info -s rosbag_v2 ros1_cmd_vel.bag
The ROS 1 bag file can be replayed with almost the same command above:
ros2 bag play -s rosbag_v2 ros1_cmd_vel.bag
That will just as before publish data on the cmd_vel
topic and the robot moves again.
However, the data is actually coming from an old ROS 1 bag file.
Let's start our twist_publisher
once again:
ros2 component load /confbot_driver_container confbot_driver confbot_driver::nodes::TwistPublisher
Terminal 3: Tempering with the system:
ros2 topic pub /cmd_vel geometry_msgs/Twist "{linear: {x: -0.5}, angular: {z: -0.5}}" -r 10
Robot now goes backward in RViz. If you command the robot in a non-circular motion, the robot will eventually move out of its safe zone and gets eaten by the shark! We definitely want to secure our little poor confbot :)
Please make sure to terminate all the running nodes by Ctrl+C them (Terminals 1, 2 and 3).
We will now use ROS2 Security to harden the system.
In a new terminal
export ROS_SECURITY_ROOT_DIRECTORY=$CONFBOT_WS/confbot_keystore
source /opt/ros/dashing/setup.bash
cd $CONFBOT_WS
colcon build --merge-install --cmake-args -DSECURITY=ON -DPOLICY_FILE=$CONFBOT_WS/src/confbot_robot/confbot_security/safe_zone_publisher_policies.xml --packages-select confbot_security --cmake-force-configure
The confbot_keystore
directory content will look similar to:
/tmp/confbot_ws/confbot_keystore
├── 1000.pem
├── 1001.pem
├── 1002.pem
├── 1003.pem
├── 1004.pem
├── 1005.pem
├── 1006.pem
├── ca.cert.pem
├── ca_conf.cnf
├── ca.key.pem
├── confbot_driver
│ ├── cert.pem
│ ├── ecdsaparam
│ ├── governance.p7s
│ ├── identity_ca.cert.pem
│ ├── key.pem
│ ├── permissions_ca.cert.pem
│ ├── permissions.p7s
│ ├── permissions.xml
│ ├── req.pem
│ └── request.cnf
├── confbot_laser
│ ├── cert.pem
│ ├── ecdsaparam
│ ├── governance.p7s
│ ├── identity_ca.cert.pem
│ ├── key.pem
│ ├── permissions_ca.cert.pem
│ ├── permissions.p7s
│ ├── permissions.xml
│ ├── req.pem
│ └── request.cnf
├── ecdsaparam
├── governance.p7s
├── governance.xml
├── index.txt
├── index.txt.attr
├── index.txt.attr.old
├── index.txt.old
├── launch_ros
│ ├── cert.pem
│ ├── ecdsaparam
│ ├── governance.p7s
│ ├── identity_ca.cert.pem
│ ├── key.pem
│ ├── permissions_ca.cert.pem
│ ├── permissions.p7s
│ ├── permissions.xml
│ ├── req.pem
│ └── request.cnf
├── robot_state_publisher
│ ├── cert.pem
│ ├── ecdsaparam
│ ├── governance.p7s
│ ├── identity_ca.cert.pem
│ ├── key.pem
│ ├── permissions_ca.cert.pem
│ ├── permissions.p7s
│ ├── permissions.xml
│ ├── req.pem
│ └── request.cnf
├── rviz
│ ├── cert.pem
│ ├── ecdsaparam
│ ├── governance.p7s
│ ├── identity_ca.cert.pem
│ ├── key.pem
│ ├── permissions_ca.cert.pem
│ ├── permissions.p7s
│ ├── permissions.xml
│ ├── req.pem
│ └── request.cnf
├── safe_zone_publisher
│ ├── cert.pem
│ ├── ecdsaparam
│ ├── governance.p7s
│ ├── identity_ca.cert.pem
│ ├── key.pem
│ ├── permissions_ca.cert.pem
│ ├── permissions.p7s
│ ├── permissions.xml
│ ├── req.pem
│ └── request.cnf
├── serial
├── serial.old
└── twist_publisher
├── cert.pem
├── ecdsaparam
├── governance.p7s
├── identity_ca.cert.pem
├── key.pem
├── permissions_ca.cert.pem
├── permissions.p7s
├── permissions.xml
├── req.pem
└── request.cnf
This means only the following nodes have been allowed to be used in a secured environment:
- /confbot_driver
- /confbot_driver_container
- /confbot_laser
- /confbot_sensors_container
- /launch_ros
- /robot_state_publisher
- /rviz
All terminals are assumed to have the environment setup and security environment variables set
source $CONFBOT_WS/install/setup.bash
export ROS_SECURITY_ENABLE=true
export ROS_SECURITY_STRATEGY=Enforce
export ROS_SECURITY_ROOT_DIRECTORY=$CONFBOT_WS/confbot_keystore
Terminal 1: launch the system
ros2 launch confbot_bringup confbot_bringup_activated.launch.py
Terminal 2: visualize the system
ros2 run rviz2 rviz2 -d `ros2 pkg prefix confbot_bringup --share`/config/confbot.rviz
In RViz we will see the robot with the laser points but no sharks.
$ ros2 topic pub /cmd_vel geometry_msgs/Twist "linear: {x: 0}" -r 100
Unknown error creating node: SECURITY ERROR: directory /tmp/confbot_demo/confbot_ws/confbot_keystore/_ros2cli_publisher_geometry_msgs_Twist does not exist. Lookup strategy: MATCH_EXACT, at /tmp/binarydeb/ros-dashing-rcl-0.7.4/src/rcl/security_directory.c:256
This fails because the node used by ros2 topic
has not been allowed to join the secured network
$ ros2 run confbot_driver twist_publisher __node:=my_hacky_node
terminate called after throwing an instance of 'rclcpp::exceptions::RCLError'
what(): failed to initialize rcl node: SECURITY ERROR: directory /tmp/confbot_demo/confbot_ws/confbot_keystore/my_hacky_node does not exist. Lookup strategy: MATCH_EXACT, at /tmp/binarydeb/ros-dashing-rcl-0.7.4/src/rcl/security_directory.c:256
This fails because, while the node /twist_publisher
is allowed to join the network, the node /my_hacky_node
is not.
Let's now see how to specify what actions a given node is allowed to perform.
Here we will look at the /safe_zone_publisher
node's permissions.
This node's sole function is to publish the safe zone where the robot is operating (green zone in RViz) and the danger zone (red shark).
Let's have a look at the safe_zone_publisher_policies.xml
file
cat $CONFBOT_WS/src/confbot_robot/confbot_security/safe_zone_publisher_policies.xml
Imagine an attacker is able to inject code into our /safe_zone_publisher
node to make it send velocity commands to the robot.
cd $CONFBOT_WS/src/confbot_robot/confbot_tools/confbot_tools
cp safe_zone_publisher_hacked.py safe_zone_publisher.py
git diff safe_zone_publisher.py
Let's try to run this compromised node:
$ python3 safe_zone_publisher.py
...
2019-06-06 14:10:19.198 [SECURITY Error] Error checking creation of local writer d0.9b.b9.b.37.3.30.e1.8f.25.89.75|0.0.10.3 (rt/cmd_vel topic not found in allow rule. (/tmp/binarydeb/ros-dashing-fastrtps-1.8.0/src/cpp/security/accesscontrol/Permissions.cpp:1111))
...
Here it fails with rt/cmd_vel topic not found in allow rule.
meaning that the node is not allowed to publish on the ROS topic /cmd_vel
.
Make sure to kill all the running nodes.
Shell 1: Set up Gazebo environment variables and launch the simulation
# this is needed for gazebo_ros_pkgs to find the Gazebo plugins such as libCameraPlugin.so
source /usr/share/gazebo/setup.sh
# set the GAZEBO_MODEL_PATH for Gazebo to find our robot 3D model
export GAZEBO_MODEL_PATH=$CONFBOT_WS/install/share/:$GAZEBO_MODEL_PATH
ros2 launch confbot_simulation empty_world.launch.py
Shell 2: Spawn our robot in Gazebo
ros2 run confbot_simulation urdf_spawner.py `ros2 pkg prefix confbot_description --share`/urdf/confbot.urdf
This load command might take a while as Gazebo will update its current model data base.
Then we give a velocity command to our robot
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "{angular: {z: 1}}" -1
Shell 3: See the camera output in rqt
ros2 run rqt_image_view rqt_image_view
Inside the rqt gui you can choose the image topic to be /confbot_camera/image_raw
.
You should then end up with a similar screen as such:
Shell 4: monitor our robot with RViz
ros2 run rviz2 rviz2 -d `ros2 pkg prefix confbot_simulation --share`/config/config_gazebo.rviz
TODO: remove following lines and place them in an RViz config file
Toggle off and on the Image
plugin
We can see our robot using a differential drive controller, a simulated laser scanner and a simulated camera!