-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #214 from joechrisellis/uds_authenticator
Add Unix peer credentials authenticator
- Loading branch information
Showing
5 changed files
with
355 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
196 changes: 196 additions & 0 deletions
196
src/authenticators/unix_peer_credentials_authenticator/mod.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
// Copyright 2020 Contributors to the Parsec project. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
//! Unix peer credentials authenticator | ||
//! | ||
//! The `UnixPeerCredentialsAuthenticator` uses Unix peer credentials to perform authentication. As | ||
//! such, it uses the effective Unix user ID (UID) to authenticate the connecting process. Unix | ||
//! peer credentials also allow us to access the effective Unix group ID (GID) of the connecting | ||
//! process, although this information is currently unused. | ||
//! | ||
//! Currently, the stringified UID is used as the application name. | ||
use super::ApplicationName; | ||
use super::Authenticate; | ||
use crate::front::listener::ConnectionMetadata; | ||
use log::error; | ||
use parsec_interface::operations::list_authenticators; | ||
use parsec_interface::requests::request::RequestAuth; | ||
use parsec_interface::requests::AuthType; | ||
use parsec_interface::requests::{ResponseStatus, Result}; | ||
use parsec_interface::secrecy::ExposeSecret; | ||
use std::convert::TryInto; | ||
|
||
/// Unix peer credentials authenticator. | ||
#[derive(Copy, Clone, Debug)] | ||
pub struct UnixPeerCredentialsAuthenticator; | ||
|
||
impl Authenticate for UnixPeerCredentialsAuthenticator { | ||
fn describe(&self) -> Result<list_authenticators::AuthenticatorInfo> { | ||
Ok(list_authenticators::AuthenticatorInfo { | ||
description: String::from( | ||
"Uses Unix peer credentials to authenticate the client. Verifies that the self-declared \ | ||
Unix user identifier (UID) in the request's authentication header matches that which is \ | ||
found from the peer credentials." | ||
), | ||
version_maj: 0, | ||
version_min: 1, | ||
version_rev: 0, | ||
id: AuthType::PeerCredentials, | ||
}) | ||
} | ||
|
||
fn authenticate( | ||
&self, | ||
auth: &RequestAuth, | ||
meta: Option<ConnectionMetadata>, | ||
) -> Result<ApplicationName> { | ||
// Parse authentication request. | ||
let expected_uid_bytes = auth.buffer.expose_secret(); | ||
|
||
const EXPECTED_UID_SIZE_BYTES: usize = 4; | ||
let expected_uid: [u8; EXPECTED_UID_SIZE_BYTES] = | ||
expected_uid_bytes.as_slice().try_into().map_err(|_| { | ||
error!( | ||
"UID in authentication request is not the right size (expected: {}, got: {}).", | ||
EXPECTED_UID_SIZE_BYTES, | ||
expected_uid_bytes.len() | ||
); | ||
ResponseStatus::AuthenticationError | ||
})?; | ||
let expected_uid = u32::from_le_bytes(expected_uid); | ||
|
||
let meta = meta.ok_or_else(|| { | ||
error!("Authenticator did not receive any metadata; cannot perform authentication."); | ||
ResponseStatus::AuthenticationError | ||
})?; | ||
|
||
#[allow(unreachable_patterns)] | ||
let (uid, _gid, _pid) = match meta { | ||
ConnectionMetadata::UnixPeerCredentials { uid, gid, pid } => (uid, gid, pid), | ||
_ => { | ||
error!("Wrong metadata type given to Unix peer credentials authenticator."); | ||
return Err(ResponseStatus::AuthenticationError); | ||
} | ||
}; | ||
|
||
// Authentication is successful if the _actual_ UID from the Unix peer credentials equals | ||
// the self-declared UID in the authentication request. | ||
if uid == expected_uid { | ||
Ok(ApplicationName(uid.to_string())) | ||
} else { | ||
error!("Declared UID in authentication request does not match the process's UID."); | ||
Err(ResponseStatus::AuthenticationError) | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::super::Authenticate; | ||
use super::UnixPeerCredentialsAuthenticator; | ||
use crate::front::domain_socket::peer_credentials; | ||
use crate::front::listener::ConnectionMetadata; | ||
use parsec_interface::requests::request::RequestAuth; | ||
use parsec_interface::requests::ResponseStatus; | ||
use rand::Rng; | ||
use std::os::unix::net::UnixStream; | ||
use users::get_current_uid; | ||
|
||
#[test] | ||
fn successful_authentication() { | ||
// This test should PASS; we are verifying that our username gets set as the application | ||
// secret when using Unix peer credentials authentication with Unix domain sockets. | ||
|
||
// Create two connected sockets. | ||
let (sock_a, _sock_b) = UnixStream::pair().unwrap(); | ||
let (cred_a, _cred_b) = ( | ||
peer_credentials::peer_cred(&sock_a).unwrap(), | ||
peer_credentials::peer_cred(&_sock_b).unwrap(), | ||
); | ||
|
||
let authenticator = UnixPeerCredentialsAuthenticator {}; | ||
|
||
let req_auth_data = cred_a.uid.to_le_bytes().to_vec(); | ||
let req_auth = RequestAuth::new(req_auth_data); | ||
let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials { | ||
uid: cred_a.uid, | ||
gid: cred_a.gid, | ||
pid: None, | ||
}); | ||
|
||
let auth_name = authenticator | ||
.authenticate(&req_auth, conn_metadata) | ||
.expect("Failed to authenticate"); | ||
|
||
assert_eq!(auth_name.get_name(), get_current_uid().to_string()); | ||
} | ||
|
||
#[test] | ||
fn unsuccessful_authentication_wrong_declared_uid() { | ||
// This test should FAIL; we are trying to authenticate, but we are declaring the wrong | ||
// UID. | ||
|
||
// Create two connected sockets. | ||
let (sock_a, _sock_b) = UnixStream::pair().unwrap(); | ||
let (cred_a, _cred_b) = ( | ||
peer_credentials::peer_cred(&sock_a).unwrap(), | ||
peer_credentials::peer_cred(&_sock_b).unwrap(), | ||
); | ||
|
||
let authenticator = UnixPeerCredentialsAuthenticator {}; | ||
|
||
let wrong_uid = cred_a.uid + 1; | ||
let wrong_req_auth_data = wrong_uid.to_le_bytes().to_vec(); | ||
let req_auth = RequestAuth::new(wrong_req_auth_data); | ||
let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials { | ||
uid: cred_a.uid, | ||
gid: cred_a.gid, | ||
pid: cred_a.pid, | ||
}); | ||
|
||
let auth_result = authenticator.authenticate(&req_auth, conn_metadata); | ||
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError)); | ||
} | ||
|
||
#[test] | ||
fn unsuccessful_authentication_garbage_data() { | ||
// This test should FAIL; we are sending garbage (random) data in the request. | ||
|
||
// Create two connected sockets. | ||
let (sock_a, _sock_b) = UnixStream::pair().unwrap(); | ||
let (cred_a, _cred_b) = ( | ||
peer_credentials::peer_cred(&sock_a).unwrap(), | ||
peer_credentials::peer_cred(&_sock_b).unwrap(), | ||
); | ||
|
||
let authenticator = UnixPeerCredentialsAuthenticator {}; | ||
|
||
let garbage_data = rand::thread_rng().gen::<[u8; 32]>().to_vec(); | ||
let req_auth = RequestAuth::new(garbage_data); | ||
let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials { | ||
uid: cred_a.uid, | ||
gid: cred_a.gid, | ||
pid: cred_a.pid, | ||
}); | ||
|
||
let auth_result = authenticator.authenticate(&req_auth, conn_metadata); | ||
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError)); | ||
} | ||
|
||
#[test] | ||
fn unsuccessful_authentication_no_metadata() { | ||
let authenticator = UnixPeerCredentialsAuthenticator {}; | ||
let req_auth = RequestAuth::new("secret".into()); | ||
|
||
let conn_metadata = None; | ||
let auth_result = authenticator.authenticate(&req_auth, conn_metadata); | ||
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError)); | ||
} | ||
|
||
#[test] | ||
fn unsuccessful_authentication_wrong_metadata() { | ||
// TODO(new_metadata_variant): this test needs implementing when we have more than one | ||
// metadata type. At the moment, the compiler just complains with an 'unreachable branch' | ||
// message. | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.