Skip to main content

opcua_server/
authenticator.rs

1//! The [AuthManager] trait, and tooling related to this.
2
3use async_trait::async_trait;
4
5use opcua_crypto::{SecurityPolicy, Thumbprint};
6use opcua_types::{
7    ByteString, Error, MessageSecurityMode, NodeId, StatusCode, UAString, UserTokenPolicy,
8    UserTokenType,
9};
10use tracing::{debug, error};
11
12use crate::identity_token::{
13    POLICY_ID_ANONYMOUS, POLICY_ID_ISSUED_TOKEN_NONE, POLICY_ID_ISSUED_TOKEN_RSA_15,
14    POLICY_ID_ISSUED_TOKEN_RSA_OAEP, POLICY_ID_ISSUED_TOKEN_RSA_OAEP_SHA256,
15    POLICY_ID_USER_PASS_NONE, POLICY_ID_USER_PASS_RSA_15, POLICY_ID_USER_PASS_RSA_OAEP,
16    POLICY_ID_USER_PASS_RSA_OAEP_SHA256, POLICY_ID_X509,
17};
18
19use super::{
20    address_space::AccessLevel, config::ANONYMOUS_USER_TOKEN_ID, ServerEndpoint, ServerUserToken,
21};
22use std::{collections::BTreeMap, fmt::Debug};
23
24/// Debug-safe wrapper around a password.
25#[derive(Clone, PartialEq, Eq)]
26pub struct Password(String);
27
28impl Debug for Password {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        f.debug_tuple("Password").field(&"****").finish()
31    }
32}
33
34impl Password {
35    /// Create a new debug-safe password.
36    pub fn new(password: String) -> Self {
37        Self(password)
38    }
39
40    /// get the inner value. Note: you should make sure not to log this!
41    pub fn get(&self) -> &str {
42        &self.0
43    }
44}
45
46/// A unique identifier for a _user_. Distinct from a client/session, a user can
47/// have multiple sessions at the same time, and is typically the value we use to
48/// control access.
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct UserToken(pub String);
51
52/// Key used to identify a user.
53/// Goes beyond just the identity token, since some services require
54/// information about the application URI and security mode as well.
55#[derive(Debug, Clone)]
56pub struct UserSecurityKey {
57    /// Raw user token.
58    pub token: UserToken,
59    /// Connection security mode.
60    pub security_mode: MessageSecurityMode,
61    /// Client application URI.
62    pub application_uri: String,
63}
64
65impl UserToken {
66    /// `true` if this is an anonymous user token.
67    pub fn is_anonymous(&self) -> bool {
68        self.0 == ANONYMOUS_USER_TOKEN_ID
69    }
70}
71
72/// Permissions for the core and diagnostics node managers.
73#[derive(Default, Debug, Clone)]
74pub struct CoreServerPermissions {
75    /// Whether the user can read the server diagnostics.
76    pub read_diagnostics: bool,
77}
78
79#[allow(unused)]
80#[async_trait]
81/// The AuthManager trait is used to let servers control access to the server.
82/// It serves two main purposes:
83///
84/// - It validates user credentials and returns a user token. Two clients with the
85///   same user token are considered the _same_ user, and have some ability to interfere
86///   with each other.
87/// - It uses user tokens to check access levels.
88///
89/// Note that the only async methods are the ones validating access tokens. This means
90/// that these methods should load and store any information you need to check user
91/// access level down the line.
92///
93/// This is currently the only way to restrict access to core resources. For resources in
94/// your own custom node managers you are free to use whatever access regime you want.
95pub trait AuthManager: Send + Sync + 'static {
96    /// Validate whether an anonymous user is allowed to access the given endpoint.
97    /// This does not return a user token, all anonymous users share the same special token.
98    async fn authenticate_anonymous_token(&self, endpoint: &ServerEndpoint) -> Result<(), Error> {
99        Err(Error::new(
100            StatusCode::BadIdentityTokenRejected,
101            "Anonymous identity token unsupported",
102        ))
103    }
104
105    /// Validate the given username and password for `endpoint`.
106    /// This should return a user token associated with the user, for example the username itself.
107    async fn authenticate_username_identity_token(
108        &self,
109        endpoint: &ServerEndpoint,
110        username: &str,
111        password: &Password,
112    ) -> Result<UserToken, Error> {
113        Err(Error::new(
114            StatusCode::BadIdentityTokenRejected,
115            "Username identity token unsupported",
116        ))
117    }
118
119    /// Validate the signing thumbprint for `endpoint`.
120    /// This should return a user token associated with the user.
121    async fn authenticate_x509_identity_token(
122        &self,
123        endpoint: &ServerEndpoint,
124        signing_thumbprint: &Thumbprint,
125    ) -> Result<UserToken, Error> {
126        Err(Error::new(
127            StatusCode::BadIdentityTokenRejected,
128            "X509 identity token unsupported",
129        ))
130    }
131
132    /// Validate the given issued identity token for `endpoint`.
133    /// This should return a user token associated with the user.
134    async fn authenticate_issued_identity_token(
135        &self,
136        endpoint: &ServerEndpoint,
137        token: &ByteString,
138    ) -> Result<UserToken, Error> {
139        Err(Error::new(
140            StatusCode::BadIdentityTokenRejected,
141            "Issued identity token unsupported",
142        ))
143    }
144
145    /// Return the effective user access level for the given node ID
146    fn effective_user_access_level(
147        &self,
148        token: &UserToken,
149        user_access_level: AccessLevel,
150        node_id: &NodeId,
151    ) -> AccessLevel {
152        user_access_level
153    }
154
155    /// Return whether a method is actually user executable, overriding whatever is returned by the
156    /// node manager.
157    fn is_user_executable(&self, token: &UserToken, method_id: &NodeId) -> bool {
158        true
159    }
160
161    /// Return the valid user token policies for the given endpoint.
162    /// Only valid tokens will be passed to the authenticator.
163    fn user_token_policies(&self, endpoint: &ServerEndpoint) -> Vec<UserTokenPolicy>;
164
165    /// Return whether the endpoint supports anonymous authentication.
166    fn supports_anonymous(&self, endpoint: &ServerEndpoint) -> bool {
167        self.user_token_policies(endpoint)
168            .iter()
169            .any(|e| e.token_type == UserTokenType::Anonymous)
170    }
171
172    /// Return whether the endpoint supports username/password authentication.
173    fn supports_user_pass(&self, endpoint: &ServerEndpoint) -> bool {
174        self.user_token_policies(endpoint)
175            .iter()
176            .any(|e| e.token_type == UserTokenType::UserName)
177    }
178
179    /// Return whether the endpoint supports x509-certificate authentication.
180    fn supports_x509(&self, endpoint: &ServerEndpoint) -> bool {
181        self.user_token_policies(endpoint)
182            .iter()
183            .any(|e| e.token_type == UserTokenType::Certificate)
184    }
185
186    /// Returns whether the endpoint supports issued-token authentication.
187    fn supports_issued_token(&self, endpoint: &ServerEndpoint) -> bool {
188        self.user_token_policies(endpoint)
189            .iter()
190            .any(|e| e.token_type == UserTokenType::IssuedToken)
191    }
192
193    /// Return the permissions for the core server for the given user.
194    fn core_permissions(&self, token: &UserToken) -> CoreServerPermissions {
195        CoreServerPermissions::default()
196    }
197}
198
199/// A simple authenticator that keeps a map of valid users in memory.
200/// In production applications you will almost always want to create your own
201/// custom authenticator.
202pub struct DefaultAuthenticator {
203    users: BTreeMap<String, ServerUserToken>,
204}
205
206impl DefaultAuthenticator {
207    /// Create a new default authenticator with the given set of users.
208    pub fn new(users: BTreeMap<String, ServerUserToken>) -> Self {
209        Self { users }
210    }
211}
212
213#[async_trait]
214impl AuthManager for DefaultAuthenticator {
215    async fn authenticate_anonymous_token(&self, endpoint: &ServerEndpoint) -> Result<(), Error> {
216        if !endpoint.user_token_ids.contains(ANONYMOUS_USER_TOKEN_ID) {
217            return Err(Error::new(
218                StatusCode::BadIdentityTokenRejected,
219                format!(
220                    "Endpoint \"{}\" does not support anonymous authentication",
221                    endpoint.path
222                ),
223            ));
224        }
225        Ok(())
226    }
227
228    async fn authenticate_username_identity_token(
229        &self,
230        endpoint: &ServerEndpoint,
231        username: &str,
232        password: &Password,
233    ) -> Result<UserToken, Error> {
234        let token_password = password.get();
235        for user_token_id in &endpoint.user_token_ids {
236            if let Some(server_user_token) = self.users.get(user_token_id) {
237                if server_user_token.is_user_pass() && server_user_token.user == username {
238                    // test for empty password
239                    let valid = if let Some(server_password) = server_user_token.pass.as_ref() {
240                        server_password.as_bytes() == token_password.as_bytes()
241                    } else {
242                        token_password.is_empty()
243                    };
244
245                    if !valid {
246                        error!(
247                            "Cannot authenticate \"{}\", password is invalid",
248                            server_user_token.user
249                        );
250                        return Err(Error::new(
251                            StatusCode::BadIdentityTokenRejected,
252                            format!("Cannot authenticate user \"{username}\""),
253                        ));
254                    } else {
255                        return Ok(UserToken(user_token_id.clone()));
256                    }
257                }
258            }
259        }
260        error!(
261            "Cannot authenticate \"{}\", user not found for endpoint",
262            username
263        );
264        Err(Error::new(
265            StatusCode::BadIdentityTokenRejected,
266            format!("Cannot authenticate \"{username}\""),
267        ))
268    }
269
270    async fn authenticate_x509_identity_token(
271        &self,
272        endpoint: &ServerEndpoint,
273        signing_thumbprint: &Thumbprint,
274    ) -> Result<UserToken, Error> {
275        // Check the endpoint to see if this token is supported
276        for user_token_id in &endpoint.user_token_ids {
277            if let Some(server_user_token) = self.users.get(user_token_id) {
278                if let Some(ref user_thumbprint) = server_user_token.thumbprint {
279                    // The signing cert matches a user's identity, so it is valid
280                    if user_thumbprint == signing_thumbprint {
281                        return Ok(UserToken(user_token_id.clone()));
282                    }
283                }
284            }
285        }
286        Err(Error::new(
287            StatusCode::BadIdentityTokenRejected,
288            "Authentication failed",
289        ))
290    }
291
292    fn user_token_policies(&self, endpoint: &ServerEndpoint) -> Vec<UserTokenPolicy> {
293        let mut user_identity_tokens = Vec::with_capacity(3);
294
295        // Anonymous policy
296        if endpoint.user_token_ids.contains(ANONYMOUS_USER_TOKEN_ID) {
297            user_identity_tokens.push(UserTokenPolicy {
298                policy_id: UAString::from(POLICY_ID_ANONYMOUS),
299                token_type: UserTokenType::Anonymous,
300                issued_token_type: UAString::null(),
301                issuer_endpoint_url: UAString::null(),
302                security_policy_uri: UAString::null(),
303            });
304        }
305        // User pass policy
306        if endpoint.user_token_ids.iter().any(|id| {
307            id != ANONYMOUS_USER_TOKEN_ID
308                && self.users.get(id).is_some_and(|token| token.is_user_pass())
309        }) {
310            // The endpoint may set a password security policy
311            user_identity_tokens.push(UserTokenPolicy {
312                policy_id: user_pass_security_policy_id(endpoint),
313                token_type: UserTokenType::UserName,
314                issued_token_type: UAString::null(),
315                issuer_endpoint_url: UAString::null(),
316                security_policy_uri: user_pass_security_policy_uri(endpoint),
317            });
318        }
319        // X509 policy
320        if endpoint.user_token_ids.iter().any(|id| {
321            id != ANONYMOUS_USER_TOKEN_ID && self.users.get(id).is_some_and(|token| token.is_x509())
322        }) {
323            user_identity_tokens.push(UserTokenPolicy {
324                policy_id: UAString::from(POLICY_ID_X509),
325                token_type: UserTokenType::Certificate,
326                issued_token_type: UAString::null(),
327                issuer_endpoint_url: UAString::null(),
328                security_policy_uri: UAString::from(SecurityPolicy::Basic128Rsa15.to_uri()),
329            });
330        }
331
332        if user_identity_tokens.is_empty() {
333            debug!(
334                "user_identity_tokens() returned zero endpoints for endpoint {} / {} {}",
335                endpoint.path, endpoint.security_policy, endpoint.security_mode
336            );
337        }
338
339        user_identity_tokens
340    }
341
342    fn core_permissions(&self, token: &UserToken) -> CoreServerPermissions {
343        self.users
344            .get(token.0.as_str())
345            .map(|r| CoreServerPermissions {
346                read_diagnostics: r.read_diagnostics,
347            })
348            .unwrap_or_default()
349    }
350}
351
352/// Get the username and password policy ID for the given endpoint.
353pub fn user_pass_security_policy_id(endpoint: &ServerEndpoint) -> UAString {
354    match endpoint.password_security_policy() {
355        SecurityPolicy::None => POLICY_ID_USER_PASS_NONE,
356        SecurityPolicy::Basic128Rsa15 => POLICY_ID_USER_PASS_RSA_15,
357        SecurityPolicy::Basic256
358        | SecurityPolicy::Basic256Sha256
359        | SecurityPolicy::Aes128Sha256RsaOaep => POLICY_ID_USER_PASS_RSA_OAEP,
360        SecurityPolicy::Aes256Sha256RsaPss => POLICY_ID_USER_PASS_RSA_OAEP_SHA256,
361        _ => {
362            panic!("Invalid security policy for username and password")
363        }
364    }
365    .into()
366}
367
368/// Get the issued token policy ID for the given endpoint.
369pub fn issued_token_security_policy(endpoint: &ServerEndpoint) -> UAString {
370    match endpoint.password_security_policy() {
371        SecurityPolicy::None => POLICY_ID_ISSUED_TOKEN_NONE,
372        SecurityPolicy::Basic128Rsa15 => POLICY_ID_ISSUED_TOKEN_RSA_15,
373        SecurityPolicy::Basic256
374        | SecurityPolicy::Basic256Sha256
375        | SecurityPolicy::Aes128Sha256RsaOaep => POLICY_ID_ISSUED_TOKEN_RSA_OAEP,
376        SecurityPolicy::Aes256Sha256RsaPss => POLICY_ID_ISSUED_TOKEN_RSA_OAEP_SHA256,
377        _ => {
378            panic!("Invalid security policy for username and password")
379        }
380    }
381    .into()
382}
383
384/// Get the username and password policy URI for the given endpioint.
385pub fn user_pass_security_policy_uri(_endpoint: &ServerEndpoint) -> UAString {
386    // TODO we could force the security policy uri for passwords to be something other than the default
387    //  here to ensure they're secure even when the endpoint's security policy is None.
388    UAString::null()
389}