parsec_service/authenticators/unix_peer_credentials_authenticator/
mod.rs

1// Copyright 2020 Contributors to the Parsec project.
2// SPDX-License-Identifier: Apache-2.0
3//! Unix peer credentials authenticator
4//!
5//! The `UnixPeerCredentialsAuthenticator` uses Unix peer credentials to perform authentication. As
6//! such, it uses the effective Unix user ID (UID) to authenticate the connecting process. Unix
7//! peer credentials also allow us to access the effective Unix group ID (GID) of the connecting
8//! process, although this information is currently unused.
9//!
10//! Currently, the stringified UID is used as the application name.
11
12use super::{AdminList, Application, ApplicationIdentity, Authenticate};
13use crate::front::listener::ConnectionMetadata;
14use crate::utils::config::Admin;
15use log::error;
16use parsec_interface::operations::list_authenticators;
17use parsec_interface::requests::request::RequestAuth;
18use parsec_interface::requests::AuthType;
19use parsec_interface::requests::{ResponseStatus, Result};
20use parsec_interface::secrecy::ExposeSecret;
21use std::convert::TryInto;
22
23/// Unix peer credentials authenticator.
24#[derive(Clone, Debug)]
25pub struct UnixPeerCredentialsAuthenticator {
26    admins: AdminList,
27}
28
29impl UnixPeerCredentialsAuthenticator {
30    /// Create new Unix peer credentials authenticator
31    pub fn new(admins: Vec<Admin>) -> Self {
32        UnixPeerCredentialsAuthenticator {
33            admins: admins.into(),
34        }
35    }
36}
37
38impl Authenticate for UnixPeerCredentialsAuthenticator {
39    fn describe(&self) -> Result<list_authenticators::AuthenticatorInfo> {
40        Ok(list_authenticators::AuthenticatorInfo {
41            description: String::from(
42                "Uses Unix peer credentials to authenticate the client. Verifies that the self-declared \
43                Unix user identifier (UID) in the request's authentication header matches that which is \
44                found from the peer credentials."
45            ),
46            version_maj: 0,
47            version_min: 1,
48            version_rev: 0,
49            id: AuthType::UnixPeerCredentials,
50        })
51    }
52
53    fn authenticate(
54        &self,
55        auth: &RequestAuth,
56        meta: Option<ConnectionMetadata>,
57    ) -> Result<Application> {
58        // Parse authentication request.
59        let expected_uid_bytes = auth.buffer.expose_secret();
60
61        const EXPECTED_UID_SIZE_BYTES: usize = 4;
62        let expected_uid: [u8; EXPECTED_UID_SIZE_BYTES] =
63            expected_uid_bytes.as_slice().try_into().map_err(|_| {
64                error!(
65                    "UID in authentication request is not the right size (expected: {}, got: {}).",
66                    EXPECTED_UID_SIZE_BYTES,
67                    expected_uid_bytes.len()
68                );
69                ResponseStatus::AuthenticationError
70            })?;
71        let expected_uid = u32::from_le_bytes(expected_uid);
72
73        let meta = meta.ok_or_else(|| {
74            error!("Authenticator did not receive any metadata; cannot perform authentication.");
75            ResponseStatus::AuthenticationError
76        })?;
77
78        #[allow(unreachable_patterns)]
79        let (uid, _gid, _pid) = match meta {
80            ConnectionMetadata::UnixPeerCredentials { uid, gid, pid } => (uid, gid, pid),
81            _ => {
82                error!("Wrong metadata type given to Unix peer credentials authenticator.");
83                return Err(ResponseStatus::AuthenticationError);
84            }
85        };
86
87        // Authentication is successful if the _actual_ UID from the Unix peer credentials equals
88        // the self-declared UID in the authentication request.
89        if uid == expected_uid {
90            let app_name = uid.to_string();
91            let is_admin = self.admins.is_admin(&app_name);
92            Ok(Application {
93                identity: ApplicationIdentity {
94                    name: app_name,
95                    auth: AuthType::UnixPeerCredentials.into(),
96                },
97                is_admin,
98            })
99        } else {
100            error!("Declared UID in authentication request does not match the process's UID.");
101            Err(ResponseStatus::AuthenticationError)
102        }
103    }
104}
105
106#[cfg(test)]
107mod test {
108    use super::super::Authenticate;
109    use super::UnixPeerCredentialsAuthenticator;
110    use crate::front::domain_socket::peer_credentials;
111    use crate::front::listener::ConnectionMetadata;
112    use libc::{getuid, uid_t};
113    use parsec_interface::requests::request::RequestAuth;
114    use parsec_interface::requests::ResponseStatus;
115    use rand::Rng;
116    use std::os::unix::net::UnixStream;
117
118    #[test]
119    fn successful_authentication() {
120        // This test should PASS; we are verifying that our username gets set as the application
121        // secret when using Unix peer credentials authentication with Unix domain sockets.
122
123        // Create two connected sockets.
124        let (sock_a, _sock_b) = UnixStream::pair().unwrap();
125        let (cred_a, _cred_b) = (
126            peer_credentials::peer_cred(&sock_a).unwrap(),
127            peer_credentials::peer_cred(&_sock_b).unwrap(),
128        );
129
130        let authenticator = UnixPeerCredentialsAuthenticator {
131            admins: Default::default(),
132        };
133
134        let req_auth_data = cred_a.uid.to_le_bytes().to_vec();
135        let req_auth = RequestAuth::new(req_auth_data);
136        let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
137            uid: cred_a.uid,
138            gid: cred_a.gid,
139            pid: None,
140        });
141
142        let application = authenticator
143            .authenticate(&req_auth, conn_metadata)
144            .expect("Failed to authenticate");
145
146        let current_uid: uid_t = unsafe { getuid() };
147        assert_eq!(application.identity.name, current_uid.to_string());
148        assert!(!application.is_admin);
149    }
150
151    #[test]
152    fn unsuccessful_authentication_wrong_declared_uid() {
153        // This test should FAIL; we are trying to authenticate, but we are declaring the wrong
154        // UID.
155
156        // Create two connected sockets.
157        let (sock_a, _sock_b) = UnixStream::pair().unwrap();
158        let (cred_a, _cred_b) = (
159            peer_credentials::peer_cred(&sock_a).unwrap(),
160            peer_credentials::peer_cred(&_sock_b).unwrap(),
161        );
162
163        let authenticator = UnixPeerCredentialsAuthenticator {
164            admins: Default::default(),
165        };
166
167        let wrong_uid = cred_a.uid + 1;
168        let wrong_req_auth_data = wrong_uid.to_le_bytes().to_vec();
169        let req_auth = RequestAuth::new(wrong_req_auth_data);
170        let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
171            uid: cred_a.uid,
172            gid: cred_a.gid,
173            pid: cred_a.pid,
174        });
175
176        let auth_result = authenticator
177            .authenticate(&req_auth, conn_metadata)
178            .unwrap_err();
179        assert_eq!(auth_result, ResponseStatus::AuthenticationError);
180    }
181
182    #[test]
183    fn unsuccessful_authentication_garbage_data() {
184        // This test should FAIL; we are sending garbage (random) data in the request.
185
186        // Create two connected sockets.
187        let (sock_a, _sock_b) = UnixStream::pair().unwrap();
188        let (cred_a, _cred_b) = (
189            peer_credentials::peer_cred(&sock_a).unwrap(),
190            peer_credentials::peer_cred(&_sock_b).unwrap(),
191        );
192
193        let authenticator = UnixPeerCredentialsAuthenticator {
194            admins: Default::default(),
195        };
196
197        let garbage_data = rand::thread_rng().gen::<[u8; 32]>().to_vec();
198        let req_auth = RequestAuth::new(garbage_data);
199        let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
200            uid: cred_a.uid,
201            gid: cred_a.gid,
202            pid: cred_a.pid,
203        });
204
205        let auth_result = authenticator
206            .authenticate(&req_auth, conn_metadata)
207            .unwrap_err();
208        assert_eq!(auth_result, ResponseStatus::AuthenticationError);
209    }
210
211    #[test]
212    fn unsuccessful_authentication_no_metadata() {
213        let authenticator = UnixPeerCredentialsAuthenticator {
214            admins: Default::default(),
215        };
216        let req_auth = RequestAuth::new("secret".into());
217
218        let conn_metadata = None;
219        let auth_result = authenticator
220            .authenticate(&req_auth, conn_metadata)
221            .unwrap_err();
222        assert_eq!(auth_result, ResponseStatus::AuthenticationError);
223    }
224
225    #[test]
226    fn admin_check() {
227        // Create two connected sockets.
228        let (sock_a, _sock_b) = UnixStream::pair().unwrap();
229        let (cred_a, _cred_b) = (
230            peer_credentials::peer_cred(&sock_a).unwrap(),
231            peer_credentials::peer_cred(&_sock_b).unwrap(),
232        );
233
234        let current_uid: uid_t = unsafe { getuid() };
235        let admin = toml::from_str(&format!("name = '{}'", current_uid)).unwrap();
236        let authenticator = UnixPeerCredentialsAuthenticator {
237            admins: vec![admin].into(),
238        };
239
240        let req_auth_data = cred_a.uid.to_le_bytes().to_vec();
241        let req_auth = RequestAuth::new(req_auth_data);
242        let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
243            uid: cred_a.uid,
244            gid: cred_a.gid,
245            pid: None,
246        });
247
248        let application = authenticator
249            .authenticate(&req_auth, conn_metadata)
250            .expect("Failed to authenticate");
251
252        assert_eq!(application.identity.name, current_uid.to_string());
253        assert!(application.is_admin);
254    }
255
256    #[test]
257    fn unsuccessful_authentication_wrong_metadata() {
258        // TODO(new_metadata_variant): this test needs implementing when we have more than one
259        // metadata type. At the moment, the compiler just complains with an 'unreachable branch'
260        // message.
261    }
262}