use super::{AdminList, Application, ApplicationIdentity, Authenticate};
use crate::front::listener::ConnectionMetadata;
use crate::utils::config::Admin;
use log::error;
use parsec_interface::operations::list_authenticators;
use parsec_interface::requests::AuthType;
use parsec_interface::requests::request::RequestAuth;
use parsec_interface::requests::{ResponseStatus, Result};
use parsec_interface::secrecy::ExposeSecret;
use std::convert::TryInto;
#[derive(Clone, Debug)]
pub struct UnixPeerCredentialsAuthenticator {
admins: AdminList,
}
impl UnixPeerCredentialsAuthenticator {
pub fn new(admins: Vec<Admin>) -> Self {
UnixPeerCredentialsAuthenticator {
admins: admins.into(),
}
}
}
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::UnixPeerCredentials,
})
}
fn authenticate(
&self,
auth: &RequestAuth,
meta: Option<ConnectionMetadata>,
) -> Result<Application> {
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);
}
};
if uid == expected_uid {
let app_name = uid.to_string();
let is_admin = self.admins.is_admin(&app_name);
Ok(Application {
identity: ApplicationIdentity {
name: app_name,
auth: AuthType::UnixPeerCredentials.into(),
},
is_admin,
})
} 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 libc::{getuid, uid_t};
use parsec_interface::requests::ResponseStatus;
use parsec_interface::requests::request::RequestAuth;
use rand::Rng;
use std::os::unix::net::UnixStream;
#[test]
fn successful_authentication() {
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 {
admins: Default::default(),
};
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 application = authenticator
.authenticate(&req_auth, conn_metadata)
.expect("Failed to authenticate");
let current_uid: uid_t = unsafe { getuid() };
assert_eq!(application.identity.name, current_uid.to_string());
assert!(!application.is_admin);
}
#[test]
fn unsuccessful_authentication_wrong_declared_uid() {
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 {
admins: Default::default(),
};
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)
.unwrap_err();
assert_eq!(auth_result, ResponseStatus::AuthenticationError);
}
#[test]
fn unsuccessful_authentication_garbage_data() {
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 {
admins: Default::default(),
};
let garbage_data = rand::thread_rng().r#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)
.unwrap_err();
assert_eq!(auth_result, ResponseStatus::AuthenticationError);
}
#[test]
fn unsuccessful_authentication_no_metadata() {
let authenticator = UnixPeerCredentialsAuthenticator {
admins: Default::default(),
};
let req_auth = RequestAuth::new("secret".into());
let conn_metadata = None;
let auth_result = authenticator
.authenticate(&req_auth, conn_metadata)
.unwrap_err();
assert_eq!(auth_result, ResponseStatus::AuthenticationError);
}
#[test]
fn admin_check() {
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 current_uid: uid_t = unsafe { getuid() };
let admin = toml::from_str(&format!("name = '{}'", current_uid)).unwrap();
let authenticator = UnixPeerCredentialsAuthenticator {
admins: vec![admin].into(),
};
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 application = authenticator
.authenticate(&req_auth, conn_metadata)
.expect("Failed to authenticate");
assert_eq!(application.identity.name, current_uid.to_string());
assert!(application.is_admin);
}
#[test]
fn unsuccessful_authentication_wrong_metadata() {
}
}