1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
//! DDS Security configuration helpers.
//!
//! CycloneDDS implements DDS Security via plugins configured through QoS
//! properties. This module provides a builder-style API for setting up
//! authentication, access control, and cryptography.
//!
//! # Example
//!
//! ```no_run
//! use cyclonedds::{DomainParticipant, QosBuilder, SecurityConfig};
//!
//! let security = SecurityConfig::new()
//! .identity_ca("certs/identity_ca.pem")
//! .identity_certificate("certs/cert.pem")
//! .identity_private_key("certs/key.pem")
//! .governance("governance.xml")
//! .permissions("permissions.xml")
//! .permissions_ca("certs/permissions_ca.pem");
//!
//! let qos = QosBuilder::new()
//! .security(security)
//! .build()
//! .unwrap();
//!
//! let participant = DomainParticipant::with_qos(0, &qos).unwrap();
//! ```
use crate::QosBuilder;
/// Builder for DDS Security configuration.
///
/// Security is enabled by setting specific QoS properties that tell
/// CycloneDDS which plugins to load and where to find certificates,
/// keys, and policy files.
#[derive(Debug, Clone, Default)]
pub struct SecurityConfig {
identity_ca: Option<String>,
identity_certificate: Option<String>,
identity_private_key: Option<String>,
governance: Option<String>,
permissions: Option<String>,
permissions_ca: Option<String>,
crl: Option<String>,
auth_plugin: String,
access_plugin: String,
crypto_plugin: String,
}
impl SecurityConfig {
/// Create a new security config with default plugin names.
pub fn new() -> Self {
Self {
auth_plugin: "dds.sec.auth.builtin.PKI-DH".into(),
access_plugin: "dds.sec.access.builtin.Access-Permissions".into(),
crypto_plugin: "dds.sec.crypto.builtin.AES-GCM-GMAC".into(),
..Default::default()
}
}
/// Path to the Identity CA certificate (PEM).
pub fn identity_ca(mut self, path: impl Into<String>) -> Self {
self.identity_ca = Some(path.into());
self
}
/// Path to the participant identity certificate (PEM).
pub fn identity_certificate(mut self, path: impl Into<String>) -> Self {
self.identity_certificate = Some(path.into());
self
}
/// Path to the participant identity private key (PEM).
pub fn identity_private_key(mut self, path: impl Into<String>) -> Self {
self.identity_private_key = Some(path.into());
self
}
/// Path to the Governance XML document.
pub fn governance(mut self, path: impl Into<String>) -> Self {
self.governance = Some(path.into());
self
}
/// Path to the Permissions XML document.
pub fn permissions(mut self, path: impl Into<String>) -> Self {
self.permissions = Some(path.into());
self
}
/// Path to the Permissions CA certificate (PEM).
pub fn permissions_ca(mut self, path: impl Into<String>) -> Self {
self.permissions_ca = Some(path.into());
self
}
/// Path to the Certificate Revocation List (CRL) in PEM format.
pub fn crl(mut self, path: impl Into<String>) -> Self {
self.crl = Some(path.into());
self
}
/// Override the authentication plugin name (default: `dds.sec.auth.builtin.PKI-DH`).
pub fn auth_plugin(mut self, name: impl Into<String>) -> Self {
self.auth_plugin = name.into();
self
}
/// Override the access-control plugin name (default: `dds.sec.access.builtin.Access-Permissions`).
pub fn access_plugin(mut self, name: impl Into<String>) -> Self {
self.access_plugin = name.into();
self
}
/// Override the cryptography plugin name (default: `dds.sec.crypto.builtin.AES-GCM-GMAC`).
pub fn crypto_plugin(mut self, name: impl Into<String>) -> Self {
self.crypto_plugin = name.into();
self
}
/// Validate that all configured certificate and policy files exist
/// and appear to be valid PEM documents.
///
/// Returns `Ok(())` if all configured files are present and readable,
/// or `Err` with a description of the first problem found.
pub fn validate(&self) -> crate::DdsResult<()> {
let paths = [
("identity_ca", self.identity_ca.as_deref()),
("identity_certificate", self.identity_certificate.as_deref()),
("identity_private_key", self.identity_private_key.as_deref()),
("governance", self.governance.as_deref()),
("permissions", self.permissions.as_deref()),
("permissions_ca", self.permissions_ca.as_deref()),
("crl", self.crl.as_deref()),
];
for (name, maybe_path) in &paths {
if let Some(path) = maybe_path {
let contents = std::fs::read_to_string(path).map_err(|e| {
crate::DdsError::BadParameter(format!(
"security {} file '{}' not readable: {}",
name, path, e
))
})?;
if !contents.contains("-----BEGIN") {
return Err(crate::DdsError::BadParameter(format!(
"security {} file '{}' does not appear to be a valid PEM document",
name, path
)));
}
}
}
Ok(())
}
/// Create a new `SecurityConfig` with the same settings.
///
/// This is a semantic wrapper for certificate hot-reload workflows:
/// after updating certificate files on disk, call `reload()` and then
/// `validate()` before recreating the `DomainParticipant`.
pub fn reload(&self) -> Self {
self.clone()
}
/// Apply this security configuration to a [`QosBuilder`].
pub fn apply_to(self, builder: QosBuilder) -> QosBuilder {
let mut b = builder
.property("dds.sec.auth.plugin", &self.auth_plugin)
.property("dds.sec.access.plugin", &self.access_plugin)
.property("dds.sec.crypto.plugin", &self.crypto_plugin);
if let Some(path) = self.identity_ca {
b = b.property("dds.sec.auth.identity_ca", format!("file:{}", path));
}
if let Some(path) = self.identity_certificate {
b = b.property(
"dds.sec.auth.identity_certificate",
format!("file:{}", path),
);
}
if let Some(path) = self.identity_private_key {
b = b.property("dds.sec.auth.private_key", format!("file:{}", path));
}
if let Some(path) = self.governance {
b = b.property("dds.sec.access.governance", format!("file:{}", path));
}
if let Some(path) = self.permissions {
b = b.property("dds.sec.access.permissions", format!("file:{}", path));
}
if let Some(path) = self.permissions_ca {
b = b.property("dds.sec.access.permissions_ca", format!("file:{}", path));
}
if let Some(path) = self.crl {
b = b.property("dds.sec.auth.crl", format!("file:{}", path));
}
b
}
}
impl QosBuilder {
/// Convenience: apply a [`SecurityConfig`] directly.
pub fn security(self, config: SecurityConfig) -> Self {
config.apply_to(self)
}
}