Skip to main content

opcua_server/config/
server.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2024 Adam Lock
4
5//! Provides configuration settings for the server including serialization and deserialization from file.
6
7use std::{
8    collections::BTreeMap,
9    path::{Path, PathBuf},
10};
11
12use serde::{Deserialize, Serialize};
13use tracing::{trace, warn};
14
15use crate::constants;
16use opcua_core::{comms::url::url_matches_except_host, config::Config};
17use opcua_crypto::{CertificateStore, SecurityPolicy, Thumbprint};
18use opcua_types::{
19    ApplicationDescription, ApplicationType, DecodingOptions, LocalizedText, MessageSecurityMode,
20    UAString,
21};
22
23use super::{endpoint::ServerEndpoint, limits::Limits};
24
25/// Token ID for the anonymous user token.
26pub const ANONYMOUS_USER_TOKEN_ID: &str = "ANONYMOUS";
27
28#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
29/// Configuration for the TCP transport.
30pub struct TcpConfig {
31    /// Timeout for hello on a session in seconds
32    pub hello_timeout: u32,
33    /// The hostname to supply in the endpoints
34    pub host: String,
35    /// The port number of the service
36    pub port: u16,
37}
38
39#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default)]
40/// User token handled by the default authenticator.
41pub struct ServerUserToken {
42    /// User name
43    pub user: String,
44    /// Password
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub pass: Option<String>,
47    /// X509 file path (as a string)
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub x509: Option<String>,
50    /// X509 thumbprint.
51    #[serde(skip)]
52    pub thumbprint: Option<Thumbprint>,
53    #[serde(default)]
54    /// Access to read diagnostics on the server.
55    pub read_diagnostics: bool,
56}
57
58impl ServerUserToken {
59    /// Create a user pass token
60    pub fn user_pass<T>(user: T, pass: T) -> Self
61    where
62        T: Into<String>,
63    {
64        ServerUserToken {
65            user: user.into(),
66            pass: Some(pass.into()),
67            x509: None,
68            thumbprint: None,
69            read_diagnostics: false,
70        }
71    }
72
73    /// Create an X509 token.
74    pub fn x509<T>(user: T, cert_path: &Path) -> Self
75    where
76        T: Into<String>,
77    {
78        ServerUserToken {
79            user: user.into(),
80            pass: None,
81            x509: Some(cert_path.to_string_lossy().to_string()),
82            thumbprint: None,
83            read_diagnostics: false,
84        }
85    }
86
87    /// Read an X509 user token's certificate from disk and then hold onto the thumbprint for it.
88    pub fn read_thumbprint(&mut self) {
89        if self.is_x509() && self.thumbprint.is_none() {
90            // As part of validation, we're going to try and load the x509 certificate from disk, and
91            // obtain its thumbprint. This will be used when a session is activated.
92            if let Some(ref x509_path) = self.x509 {
93                let path = PathBuf::from(x509_path);
94                if let Ok(x509) = CertificateStore::read_cert(&path) {
95                    self.thumbprint = Some(x509.thumbprint());
96                }
97            }
98        }
99    }
100
101    /// Test if the token is valid. This does not care for x509 tokens if the cert is present on
102    /// the disk or not.
103    pub fn validate(&self, id: &str) -> Result<(), Vec<String>> {
104        let mut errors = Vec::new();
105        if id == ANONYMOUS_USER_TOKEN_ID {
106            errors.push(format!(
107                "User token {id} is invalid because id is a reserved value, use another value."
108            ));
109        }
110        if self.user.is_empty() {
111            errors.push(format!("User token {id} has an empty user name."));
112        }
113        if self.pass.is_some() && self.x509.is_some() {
114            errors.push(format!(
115                "User token {id} holds a password and certificate info - it cannot be both."
116            ));
117        } else if self.pass.is_none() && self.x509.is_none() {
118            errors.push(format!(
119                "User token {id} fails to provide a password or certificate info."
120            ));
121        }
122        if errors.is_empty() {
123            Ok(())
124        } else {
125            Err(errors)
126        }
127    }
128
129    /// Return `true` if this token is for username/password auth.
130    pub fn is_user_pass(&self) -> bool {
131        self.x509.is_none()
132    }
133
134    /// Return `true` if this token is for X509-based auth.
135    pub fn is_x509(&self) -> bool {
136        self.x509.is_some()
137    }
138
139    /// Set the ability for the user to read diagnostics on the server.
140    pub fn read_diagnostics(mut self, read: bool) -> Self {
141        self.read_diagnostics = read;
142        self
143    }
144}
145
146#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
147/// Configuration for certificate validation.
148pub struct CertificateValidation {
149    /// Auto trusts client certificates. For testing/samples only unless you're sure what you're
150    /// doing.
151    pub trust_client_certs: bool,
152    /// Check the valid from/to fields of a certificate
153    pub check_time: bool,
154}
155
156impl Default for CertificateValidation {
157    fn default() -> Self {
158        Self {
159            trust_client_certs: false,
160            check_time: true,
161        }
162    }
163}
164
165#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
166/// Server configuration object.
167pub struct ServerConfig {
168    /// An id for this server
169    pub application_name: String,
170    /// A description for this server
171    pub application_uri: String,
172    /// Product url
173    pub product_uri: String,
174    /// Autocreates public / private keypair if they don't exist. For testing/samples only
175    /// since you do not have control of the values
176    #[serde(default)]
177    pub create_sample_keypair: bool,
178    /// Path to a custom certificate, to be used instead of the default .der certificate
179    #[serde(default)]
180    pub certificate_path: Option<PathBuf>,
181    /// Path to a custom private key, to be used instead of the default private key
182    #[serde(default)]
183    pub private_key_path: Option<PathBuf>,
184    /// Checks the certificate's time validity
185    #[serde(default)]
186    pub certificate_validation: CertificateValidation,
187    /// PKI folder, either absolute or relative to executable
188    pub pki_dir: PathBuf,
189    /// Url to a discovery server - adding this string causes the server to assume you wish to
190    /// register the server with a discovery server.
191    #[serde(default)]
192    pub discovery_server_url: Option<String>,
193    /// tcp configuration information
194    pub tcp_config: TcpConfig,
195    /// Server OPA UA limits
196    #[serde(default)]
197    pub limits: Limits,
198    /// Supported locale ids
199    #[serde(default)]
200    pub locale_ids: Vec<String>,
201    /// User tokens
202    pub user_tokens: BTreeMap<String, ServerUserToken>,
203    /// discovery endpoint url which may or may not be the same as the service endpoints below.
204    pub discovery_urls: Vec<String>,
205    /// Default endpoint id
206    #[serde(default)]
207    pub default_endpoint: Option<String>,
208    /// Endpoints supported by the server
209    pub endpoints: BTreeMap<String, ServerEndpoint>,
210    /// Interval in milliseconds between each time the subscriptions are polled.
211    #[serde(default = "defaults::subscription_poll_interval_ms")]
212    pub subscription_poll_interval_ms: u64,
213    /// Default publish request timeout.
214    #[serde(default = "defaults::publish_timeout_default_ms")]
215    pub publish_timeout_default_ms: u64,
216    /// Max message timeout for non-publish requests.
217    /// Will not be applied for requests that are handled synchronously.
218    /// Set to 0 for no timeout, meaning that a timeout will only be applied if
219    /// the client requests one.
220    /// If this is greater than zero and the client requests a timeout of 0,
221    /// this will be used.
222    #[serde(default = "defaults::max_timeout_ms")]
223    pub max_timeout_ms: u32,
224    /// Maximum lifetime of secure channel tokens. The client will request a number,
225    /// this just sets an upper limit on that value.
226    /// Note that there is no lower limit, if a client sets an expiry of 0,
227    /// we will just instantly time out.
228    #[serde(default = "defaults::max_secure_channel_token_lifetime_ms")]
229    pub max_secure_channel_token_lifetime_ms: u32,
230    /// Maximum time before a session will be timed out. The client will request
231    /// a number, this just sets the upper limit on that value.
232    /// Note that there is no lower limit, if a client sets an expiry of 0
233    /// we will instantly time out.
234    #[serde(default = "defaults::max_session_timeout_ms")]
235    pub max_session_timeout_ms: u64,
236    /// Enable server diagnostics.
237    #[serde(default)]
238    pub diagnostics: bool,
239    /// Length of the nonce generated for CreateSession responses.
240    #[serde(default = "defaults::session_nonce_length")]
241    pub session_nonce_length: usize,
242    /// Delay in milliseconds before attempting to connect to a reverse connect target,
243    /// if the previous attempt failed.
244    #[serde(default = "defaults::reverse_connect_failure_delay_ms")]
245    pub reverse_connect_failure_delay_ms: u64,
246}
247
248mod defaults {
249    use crate::constants;
250
251    pub(super) fn subscription_poll_interval_ms() -> u64 {
252        constants::SUBSCRIPTION_TIMER_RATE_MS
253    }
254
255    pub(super) fn publish_timeout_default_ms() -> u64 {
256        constants::DEFAULT_PUBLISH_TIMEOUT_MS
257    }
258
259    pub(super) fn max_timeout_ms() -> u32 {
260        300_000
261    }
262
263    pub(super) fn max_secure_channel_token_lifetime_ms() -> u32 {
264        300_000
265    }
266
267    pub(super) fn max_session_timeout_ms() -> u64 {
268        constants::MAX_SESSION_TIMEOUT
269    }
270
271    pub(super) fn session_nonce_length() -> usize {
272        32
273    }
274
275    pub(super) fn reverse_connect_failure_delay_ms() -> u64 {
276        30_000
277    }
278}
279
280impl Config for ServerConfig {
281    fn validate(&self) -> Result<(), Vec<String>> {
282        let mut errors = Vec::new();
283        if self.application_name.is_empty() {
284            warn!("No application was set");
285        }
286        if self.application_uri.is_empty() {
287            warn!("No application uri was set");
288        }
289        if self.product_uri.is_empty() {
290            warn!("No product uri was set");
291        }
292        if self.endpoints.is_empty() {
293            errors.push("Server configuration is invalid. It defines no endpoints".to_owned());
294        }
295        for (id, endpoint) in &self.endpoints {
296            if let Err(e) = endpoint.validate(id, &self.user_tokens) {
297                errors.push(format!(
298                    "Endpoint {id} failed to validate: {}",
299                    e.join(", ")
300                ));
301            }
302        }
303        if let Some(ref default_endpoint) = self.default_endpoint {
304            if !self.endpoints.contains_key(default_endpoint) {
305                errors.push(format!(
306                    "Endpoints does not contain default endpoint {default_endpoint}"
307                ));
308            }
309        }
310        for (id, user_token) in &self.user_tokens {
311            if let Err(e) = user_token.validate(id) {
312                errors.push(format!(
313                    "User token {id} failed to validate: {}",
314                    e.join(", ")
315                ))
316            }
317        }
318        if self.limits.max_array_length == 0 {
319            errors.push("Server configuration is invalid. Max array length is invalid".to_owned());
320        }
321        if self.limits.max_string_length == 0 {
322            errors.push("Server configuration is invalid. Max string length is invalid".to_owned());
323        }
324        if self.limits.max_byte_string_length == 0 {
325            errors.push(
326                "Server configuration is invalid. Max byte string length is invalid".to_owned(),
327            );
328        }
329        if self.discovery_urls.is_empty() {
330            errors.push("Server configuration is invalid. Discovery urls not set".to_owned());
331        }
332
333        if errors.is_empty() {
334            Ok(())
335        } else {
336            Err(errors)
337        }
338    }
339
340    fn application_name(&self) -> UAString {
341        UAString::from(&self.application_name)
342    }
343
344    fn application_uri(&self) -> UAString {
345        UAString::from(&self.application_uri)
346    }
347
348    fn product_uri(&self) -> UAString {
349        UAString::from(&self.product_uri)
350    }
351
352    fn application_type(&self) -> ApplicationType {
353        ApplicationType::Server
354    }
355
356    fn discovery_urls(&self) -> Option<Vec<UAString>> {
357        let discovery_urls: Vec<UAString> =
358            self.discovery_urls.iter().map(UAString::from).collect();
359        Some(discovery_urls)
360    }
361
362    fn application_description(&self) -> ApplicationDescription {
363        ApplicationDescription {
364            application_uri: self.application_uri(),
365            application_name: LocalizedText::new("", self.application_name().as_ref()),
366            application_type: self.application_type(),
367            product_uri: self.product_uri(),
368            gateway_server_uri: UAString::null(),
369            discovery_profile_uri: UAString::null(),
370            discovery_urls: self.discovery_urls(),
371        }
372    }
373}
374
375impl Default for ServerConfig {
376    fn default() -> Self {
377        let mut pki_dir = std::env::current_dir().unwrap();
378        pki_dir.push(Self::PKI_DIR);
379
380        ServerConfig {
381            application_name: String::new(),
382            application_uri: String::new(),
383            product_uri: String::new(),
384            create_sample_keypair: false,
385            certificate_path: None,
386            private_key_path: None,
387            pki_dir,
388            certificate_validation: CertificateValidation::default(),
389            discovery_server_url: None,
390            tcp_config: TcpConfig {
391                host: "127.0.0.1".to_string(),
392                port: constants::DEFAULT_RUST_OPC_UA_SERVER_PORT,
393                hello_timeout: constants::DEFAULT_HELLO_TIMEOUT_SECONDS,
394            },
395            limits: Limits::default(),
396            user_tokens: BTreeMap::new(),
397            locale_ids: vec!["en".to_string()],
398            discovery_urls: Vec::new(),
399            default_endpoint: None,
400            endpoints: BTreeMap::new(),
401            subscription_poll_interval_ms: defaults::subscription_poll_interval_ms(),
402            publish_timeout_default_ms: defaults::publish_timeout_default_ms(),
403            max_timeout_ms: defaults::max_timeout_ms(),
404            max_secure_channel_token_lifetime_ms: defaults::max_secure_channel_token_lifetime_ms(),
405            max_session_timeout_ms: defaults::max_session_timeout_ms(),
406            diagnostics: false,
407            session_nonce_length: defaults::session_nonce_length(),
408            reverse_connect_failure_delay_ms: defaults::reverse_connect_failure_delay_ms(),
409        }
410    }
411}
412
413impl ServerConfig {
414    /// The default PKI directory
415    pub const PKI_DIR: &'static str = "pki";
416
417    /// Create a new default server config with the given list
418    /// of user tokens and endpoints.
419    pub fn new<T>(
420        application_name: T,
421        user_tokens: BTreeMap<String, ServerUserToken>,
422        endpoints: BTreeMap<String, ServerEndpoint>,
423    ) -> Self
424    where
425        T: Into<String>,
426    {
427        let host = "127.0.0.1".to_string();
428        let port = constants::DEFAULT_RUST_OPC_UA_SERVER_PORT;
429
430        let application_name = application_name.into();
431        let application_uri = format!("urn:{application_name}");
432        let product_uri = format!("urn:{application_name}");
433        let discovery_server_url = Some(constants::DEFAULT_DISCOVERY_SERVER_URL.to_string());
434        let discovery_urls = vec![format!("opc.tcp://{}:{}/", host, port)];
435        let locale_ids = vec!["en".to_string()];
436
437        let mut pki_dir = std::env::current_dir().unwrap();
438        pki_dir.push(Self::PKI_DIR);
439
440        ServerConfig {
441            application_name,
442            application_uri,
443            product_uri,
444            certificate_validation: CertificateValidation {
445                trust_client_certs: false,
446                check_time: true,
447            },
448            pki_dir,
449            discovery_server_url,
450            tcp_config: TcpConfig {
451                host,
452                port,
453                hello_timeout: constants::DEFAULT_HELLO_TIMEOUT_SECONDS,
454            },
455            locale_ids,
456            user_tokens,
457            discovery_urls,
458            endpoints,
459            ..Default::default()
460        }
461    }
462
463    /// Decoding options given by this config.
464    pub fn decoding_options(&self) -> DecodingOptions {
465        DecodingOptions {
466            client_offset: chrono::Duration::zero(),
467            max_message_size: self.limits.max_message_size,
468            max_chunk_count: self.limits.max_chunk_count,
469            max_string_length: self.limits.max_string_length,
470            max_byte_string_length: self.limits.max_byte_string_length,
471            max_array_length: self.limits.max_array_length,
472            ..Default::default()
473        }
474    }
475
476    /// Add an endpoint to the server config.
477    pub fn add_endpoint(&mut self, id: &str, endpoint: ServerEndpoint) {
478        self.endpoints.insert(id.to_string(), endpoint);
479    }
480
481    /// Get x509 thumbprints from registered server user tokens.
482    pub fn read_x509_thumbprints(&mut self) {
483        self.user_tokens
484            .iter_mut()
485            .for_each(|(_, token)| token.read_thumbprint());
486    }
487
488    /// Find the default endpoint
489    pub fn default_endpoint(&self) -> Option<&ServerEndpoint> {
490        if let Some(ref default_endpoint) = self.default_endpoint {
491            self.endpoints.get(default_endpoint)
492        } else {
493            None
494        }
495    }
496
497    /// Find the first endpoint that matches the specified url, security policy and message
498    /// security mode.
499    pub fn find_endpoint(
500        &self,
501        endpoint_url: &str,
502        base_endpoint_url: &str,
503        security_policy: SecurityPolicy,
504        security_mode: MessageSecurityMode,
505    ) -> Option<&ServerEndpoint> {
506        let endpoint = self.endpoints.iter().find(|&(_, e)| {
507            // Test end point's security_policy_uri and matching url
508            if url_matches_except_host(&e.endpoint_url(base_endpoint_url), endpoint_url) {
509                if e.security_policy() == security_policy
510                    && e.message_security_mode() == security_mode
511                {
512                    trace!("Found matching endpoint for url {} - {:?}", endpoint_url, e);
513                    true
514                } else {
515                    false
516                }
517            } else {
518                false
519            }
520        });
521        endpoint.map(|endpoint| endpoint.1)
522    }
523}