-
Notifications
You must be signed in to change notification settings - Fork 71
Add Unix peer credentials authenticator #214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
hug-dev
merged 3 commits into
parallaxsecond:master
from
joechrisellis:uds_authenticator
Sep 22, 2020
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 |
---|---|---|
|
@@ -5,8 +5,8 @@ | |
//! Expose Parsec functionality using Unix domain sockets as an IPC layer. | ||
//! The local socket is created at a predefined location. | ||
use super::listener; | ||
use listener::Connection; | ||
use listener::Listen; | ||
use listener::{Connection, ConnectionMetadata}; | ||
use log::error; | ||
#[cfg(not(feature = "no-parsec-user-and-clients-group"))] | ||
use std::ffi::CString; | ||
|
@@ -202,11 +202,22 @@ impl Listen for DomainSocketListener { | |
format_error!("Failed to set stream as blocking", err); | ||
None | ||
} else { | ||
let ucred = peer_credentials::peer_cred(&stream) | ||
.map_err(|err| { | ||
format_error!( | ||
"Failed to grab peer credentials metadata from UnixStream", | ||
err | ||
); | ||
err | ||
}) | ||
.ok()?; | ||
Some(Connection { | ||
stream: Box::new(stream), | ||
// TODO: when possible, we want to replace this with the (uid, gid, pid) | ||
// triple for peer credentials. See listener.rs. | ||
metadata: None, | ||
metadata: Some(ConnectionMetadata::UnixPeerCredentials { | ||
uid: ucred.uid, | ||
gid: ucred.gid, | ||
pid: ucred.pid, | ||
}), | ||
}) | ||
} | ||
} | ||
|
@@ -248,3 +259,128 @@ impl DomainSocketListenerBuilder { | |
})?) | ||
} | ||
} | ||
|
||
// == IMPORTANT NOTE == | ||
// | ||
// The code below has been cherry-picked from the following PR: | ||
// | ||
// https://github.com/rust-lang/rust/pull/75148 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you also please add the stabilizing issue rust-lang/rust#42839? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
// | ||
// At the time of writing (16/09/20), this patch is in the nightly Rust channel. To avoid needing | ||
// to use the nightly compiler to build Parsec, we have instead opted to cherry-pick the change | ||
// from the patch to allow us to use this feature 'early'. | ||
// | ||
// Once the feature hits stable, it should be safe to revert the commit that introduced the changes | ||
// below with `git revert`. You can find the stabilizing Rust issue here: | ||
// | ||
// https://github.com/rust-lang/rust/issues/42839 | ||
|
||
/// Implementation of peer credentials fetching for Unix domain socket. | ||
pub mod peer_credentials { | ||
use libc::{gid_t, pid_t, uid_t}; | ||
|
||
/// Credentials for a UNIX process for credentials passing. | ||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] | ||
pub struct UCred { | ||
/// The UID part of the peer credential. This is the effective UID of the process at the domain | ||
/// socket's endpoint. | ||
pub uid: uid_t, | ||
/// The GID part of the peer credential. This is the effective GID of the process at the domain | ||
/// socket's endpoint. | ||
pub gid: gid_t, | ||
/// The PID part of the peer credential. This field is optional because the PID part of the | ||
/// peer credentials is not supported on every platform. On platforms where the mechanism to | ||
/// discover the PID exists, this field will be populated to the PID of the process at the | ||
/// domain socket's endpoint. Otherwise, it will be set to None. | ||
pub pid: Option<pid_t>, | ||
} | ||
|
||
#[cfg(any(target_os = "android", target_os = "linux"))] | ||
pub use self::impl_linux::peer_cred; | ||
|
||
#[cfg(any( | ||
target_os = "dragonfly", | ||
target_os = "freebsd", | ||
target_os = "ios", | ||
target_os = "macos", | ||
target_os = "openbsd" | ||
))] | ||
pub use self::impl_bsd::peer_cred; | ||
|
||
#[cfg(any(target_os = "linux", target_os = "android"))] | ||
#[allow(missing_docs, trivial_casts)] // docs not required; only used for selective compilation. | ||
pub mod impl_linux { | ||
use super::UCred; | ||
use libc::{c_void, getsockopt, socklen_t, ucred, SOL_SOCKET, SO_PEERCRED}; | ||
use std::os::unix::io::AsRawFd; | ||
use std::os::unix::net::UnixStream; | ||
use std::{io, mem}; | ||
|
||
pub fn peer_cred(socket: &UnixStream) -> io::Result<UCred> { | ||
let ucred_size = mem::size_of::<ucred>(); | ||
|
||
// Trivial sanity checks. | ||
assert!(mem::size_of::<u32>() <= mem::size_of::<usize>()); | ||
assert!(ucred_size <= u32::MAX as usize); | ||
|
||
let mut ucred_size = ucred_size as socklen_t; | ||
let mut ucred: ucred = ucred { | ||
pid: 1, | ||
uid: 1, | ||
gid: 1, | ||
}; | ||
|
||
unsafe { | ||
let ret = getsockopt( | ||
socket.as_raw_fd(), | ||
SOL_SOCKET, | ||
SO_PEERCRED, | ||
&mut ucred as *mut ucred as *mut c_void, | ||
&mut ucred_size, | ||
); | ||
|
||
if ret == 0 && ucred_size as usize == mem::size_of::<ucred>() { | ||
Ok(UCred { | ||
uid: ucred.uid, | ||
gid: ucred.gid, | ||
pid: Some(ucred.pid), | ||
}) | ||
} else { | ||
Err(io::Error::last_os_error()) | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[cfg(any( | ||
target_os = "dragonfly", | ||
target_os = "macos", | ||
target_os = "ios", | ||
target_os = "freebsd", | ||
target_os = "openbsd" | ||
))] | ||
#[allow(missing_docs)] // docs not required; only used for selective compilation. | ||
pub mod impl_bsd { | ||
use super::UCred; | ||
use std::io; | ||
use std::os::unix::io::AsRawFd; | ||
use std::os::unix::net::UnixStream; | ||
|
||
pub fn peer_cred(socket: &UnixStream) -> io::Result<UCred> { | ||
let mut cred = UCred { | ||
uid: 1, | ||
gid: 1, | ||
pid: None, | ||
}; | ||
unsafe { | ||
let ret = libc::getpeereid(socket.as_raw_fd(), &mut cred.uid, &mut cred.gid); | ||
|
||
if ret == 0 { | ||
Ok(cred) | ||
} else { | ||
Err(io::Error::last_os_error()) | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty good idea to use that for unit tests!