目标:了解将安全工件部署到生产系统时的最佳实践。
教程级别:高级
时间:20 分钟
内容
背景
先决条件
一般准则
构建部署场景
生成 Docker 镜像
理解 compose 文件
运行示例
检查容器
背景
典型的部署场景通常涉及将容器化的应用程序或软件包发送到远程系统。在部署启用安全性的应用程序时应特别注意,要求用户考虑打包文件的敏感性。
遵循 DDS 安全标准 https://www.omg.org/spec/DDS-SECURITY/1.1/About-DDS-SECURITY/ , sros2
包提供了一系列实用工具,以高度模块化和灵活的方式管理 ROS 2 环境下的安全性。
组织不同证书、密钥和目录的基本核心指南仍然是避免系统安全受到威胁的关键因素。这包括保护意识和选择在远程生产系统上部署的必要文件最小集合的标准,以尽量减少安全暴露。
先决条件
带有 compose 插件的 docker 安装。请参阅 Docker 安装 https://docs.docker.com/engine/install/ 和 Compose 插件 https://docs.docker.com/compose/install/ 中详细的安装步骤。
(推荐)对 ROS 2 安全设计的基本理解 https://design.ros2.org/articles/ros2_dds_security.html 。
(推荐)完成以前的安全教程。特别是:
设置安全 https://docs.ros.org/en/jazzy/Tutorials/Advanced/Security/Introducing-ros2-security.html
理解安全密钥库 https://docs.ros.org/en/jazzy/Tutorials/Advanced/Security/The-Keystore.html
设置访问控制 https://docs.ros.org/en/jazzy/Tutorials/Advanced/Security/Access-Controls.html
一般准则
ROS 2 利用 DDS 安全扩展来确保同一飞地内消息交换的安全性。飞地内的不同签名文件和证书是由受信任实体的证书颁发机构 (CA) 的私钥和证书生成的。事实上,每个飞地可以为身份和权限选择两个不同的 CA。这些 CA 工件存储在密钥库的 private/
和 public/
子目录中,文件夹结构如下:
keystore
├── enclaves
│ └── ...
│ └── ...
├── private
│ └── ...
└── public
└── ...
在典型的生产系统部署中,创建和使用特定证书颁发机构的一个良好实践是:
在仅供内部使用的组织系统内创建它。
生成/修改所需的飞地时请记住:
并非所有生成的飞地都应部署到所有目标设备。
一种合理的进行方式是每个应用程序有一个独立的区域,从而实现关注点分离。
将 public/
与相应的 enclaves/
一起在设置期间运送到不同的远程生产设备中。
保管和保护组织中的 private/
密钥和/或认证请求。
请注意,如果 private/
文件丢失,将无法更改访问权限、添加或修改安全配置文件。
此外,还可以考虑进一步的做法:
授予对
enclaves/
目录内容的只读权限。如果提供了符合 PKCS#11 标准的 URI 用于生成飞地的私钥,则可以使用硬件安全模块 (HSM) 来存储它们。https://en.wikipedia.org/wiki/Hardware_security_module
下表描述了与推荐位置相关的密钥库目录的前述声明摘要:
目录 / 位置 |
组织 |
目标设备 |
材料敏感性 |
---|---|---|---|
公共 |
✓ |
✓ |
低 |
私密 |
✓ |
✕ |
高 |
飞地 |
✓ |
✓ |
中等 |
Directory / Location |
Organization |
Target Device |
Material Sensitivity |
---|---|---|---|
public |
✓ |
✓ |
Low |
private |
✓ |
✕ |
High |
enclaves |
✓ |
✓ |
Medium |
构建部署场景
为了说明一个简单的部署场景,将在 ros:<DISTRO>
提供的镜像之上构建一个新的 docker 镜像。从该镜像开始,将创建三个容器,目的是:
在本地主机的共享卷中初始化密钥库。
模拟两个已部署的远程设备以安全的方式相互交互。
在此示例中,本地主机充当组织的系统。让我们从创建一个工作区文件夹开始:
mkdir ~/security_gd_tutorial
cd ~/security_gd_tutorial
生成 Docker 镜像
要构建一个新的 Docker 镜像,需要一个 Dockerfile。可以使用以下命令获取本教程中建议的 Dockerfile:
# Download the Dockerfile
wget https://raw.githubusercontent.com/ros2/ros2_documentation/jazzy/source/Tutorials/Advanced/Security/resources/deployment_gd/Dockerfile
# 定义一个构建参数ROS_DISTRO,默认值为humble
ARG ROS_DISTRO=humble
# 使用基础镜像ros:${ROS_DISTRO}-ros-base
FROM ros:${ROS_DISTRO}-ros-base
# 安装所需的软件包
RUN apt-get update && apt-get install -y \ # 更新包列表并安装以下软件包
tree \ # 安装tree工具,用于显示目录树
ros-${ROS_DISTRO}-demo-nodes-cpp \ # 安装ROS示例节点(C++版本)
ros-${ROS_DISTRO}-demo-nodes-py && \ # 安装ROS示例节点(Python版本)
rm -rf /var/lib/apt/lists/* # 删除apt缓存,减小镜像体积
现在,用以下命令构建 Docker 镜像:
# Build the base image
sudo apt install docker.io
sudo docker build -t ros2_security/deployment_tutorial --build-arg ROS_DISTRO=jazzy .
sudo docker build \
--build-arg HTTP_PROXY=http://127.0.0.1:2334 \
--build-arg HTTPS_PROXY=http://1270.0.1:2334 \
--build-arg NO_PROXY=localhost,127.0.0.1,.example.com \
-t ros2_security/deployment_tutorial --build-arg ROS_DISTRO=jazzy .
网络原因docker安装镜像失败
# docker/buildx https://github.com/docker/buildx/releases/
理解 compose 文件
编排配置文件使用镜像来创建作为服务的容器。在本教程中,配置中定义了三个服务:
keystore-creator:与之前的教程类似,它在内部初始化一个新的密钥库树目录。这将创建 enclaves/ public/ 和 private/,在 ROS 2 安全飞地 https://design.ros2.org/articles/ros2_security_enclaves.html 中有更详细的解释。
keystore
目录被配置为跨容器的共享卷。监听器和对话者:在本教程中充当远程设备角色。所需的
Security
环境变量以及来自共享卷的必要密钥库文件已被获取。
可以下载 compose 配置 yaml 文件: (compose.deployment.yaml)
# Download the compose file
wget https://raw.githubusercontent.com/ros2/ros2_documentation/jazzy/source/Tutorials/Advanced/Security/resources/deployment_gd/compose.deployment.yaml
services:
# 在共享卷中创建密钥存储
keystore-creator: # 定义服务名称为keystore-creator
image: ros2_security/deployment_tutorial:latest # 使用ros2_security/deployment_tutorial:latest镜像
volumes:
- ./keystore:/keystore # 将本地的./keystore挂载到容器的/keystore
command: # 执行的命令
- /bin/bash # 使用/bin/bash作为shell
- -c # 执行-c后面的多行命令
- |
ros2 security create_keystore /keystore # 创建密钥存储在/keystore路径
ros2 security create_enclave /keystore /talker_listener/talker # 为talker创建安全区
ros2 security create_enclave /keystore /talker_listener/listener # 为listener创建安全区
# 启动一个模拟远程设备的安全监听器
listener: # 定义服务名称为listener
image: ros2_security/deployment_tutorial:latest # 使用ros2_security/deployment_tutorial:latest镜像
container_name: tutorial-listener-1 # 容器名称为tutorial-listener-1
environment: # 设置环境变量
- ROS_SECURITY_KEYSTORE=/keystore # 指定密钥存储路径
- ROS_SECURITY_ENABLE=true # 启用ROS2安全特性
- ROS_SECURITY_STRATEGY=Enforce # 安全策略为Enforce
volumes: # 挂载卷
- ./keystore/enclaves/governance.p7s:/keystore/enclaves/governance.p7s # 挂载governance.p7s文件
- ./keystore/enclaves/governance.xml:/keystore/enclaves/governance.xml # 挂载governance.xml文件
- ./keystore/enclaves/talker_listener/listener:/keystore/enclaves/talker_listener/listener # 挂载listener的安全区
- ./keystore/public:/keystore/public # 挂载public目录
command: ros2 run demo_nodes_py listener --ros-args --enclave /talker_listener/listener # 运行监听器节点,并指定安全区
depends_on: # 依赖关系
keystore-creator: # 依赖于keystore-creator服务
condition: service_completed_successfully # 当keystore-creator服务成功完成时启动
# 启动一个模拟另一个远程设备的安全发布器
talker: # 定义服务名称为talker
image: ros2_security/deployment_tutorial:latest # 使用ros2_security/deployment_tutorial:latest镜像
container_name: tutorial-talker-1 # 容器名称为tutorial-talker-1
environment: # 设置环境变量
- ROS_SECURITY_KEYSTORE=/keystore # 指定密钥存储路径
- ROS_SECURITY_ENABLE=true # 启用ROS2安全特性
- ROS_SECURITY_STRATEGY=Enforce # 安全策略为Enforce
volumes: # 挂载卷
- ./keystore/enclaves/governance.p7s:/keystore/enclaves/governance.p7s # 挂载governance.p7s文件
- ./keystore/enclaves/governance.xml:/keystore/enclaves/governance.xml # 挂载governance.xml文件
- ./keystore/enclaves/talker_listener/talker:/keystore/enclaves/talker_listener/talker # 挂载talker的安全区
- ./keystore/public:/keystore/public # 挂载public目录
command: ros2 run demo_nodes_cpp talker --ros-args --enclave /talker_listener/talker # 运行发布器节点,并指定安全区
depends_on: # 依赖关系
keystore-creator: # 依赖于keystore-creator服务
condition: service_completed_successfully # 当keystore-creator服务成功完成时启动
运行示例
在相同的工作目录 ~/security_gd_tutorial
中,运行:
# Start the example
docker compose -f compose.deployment.yaml up
这应该产生以下输出:
tutorial-listener-1:
Found security directory: /keystore/enclaves/talker_listener/listener
tutorial-talker-1:
Found security directory: /keystore/enclaves/talker_listener/talker
tutorial-listener-1:
Publishing: 'Hello World: <number>'
tutorial-talker-1:
I heard: [Hello World: <number>]
检查容器
在运行模拟本教程中两个远程设备的容器时,通过打开两个不同的终端并输入以下内容来连接到每个设备:
# Terminal 1
docker exec -it tutorial-listener-1 bash
cd keystore
tree
# Terminal 2
docker exec -it tutorial-talker-1 bash
cd keystore
tree
应获得类似于下图所示的输出:
# Terminal 1
keystore
├── enclaves
│ ├── governance.p7s
│ ├── governance.xml
│ └── talker_listener
│ └── listener
│ ├── cert.pem
│ ├── governance.p7s
│ ├── identity_ca.cert.pem
│ ├── key.pem
│ ├── permissions_ca.cert.pem
│ ├── permissions.p7s
│ └── permissions.xml
└── public
├── ca.cert.pem
├── identity_ca.cert.pem
└── permissions_ca.cert.pem
# Terminal 2
keystore
├── enclaves
│ ├── governance.p7s
│ ├── governance.xml
│ └── talker_listener
│ └── talker
│ ├── cert.pem
│ ├── governance.p7s
│ ├── identity_ca.cert.pem
│ ├── key.pem
│ ├── permissions_ca.cert.pem
│ ├── permissions.p7s
│ └── permissions.xml
└── public
├── ca.cert.pem
├── identity_ca.cert.pem
└── permissions_ca.cert.pem
请注意:
private/ 文件夹没有移动,而是留在本地主机(组织)中。
每个部署的设备都包含其应用所需的最低限度的飞地。
注意
为了简化起见,在此飞地内,身份和权限均使用相同的 CA。