Skip to content

Commit 9a65b40

Browse files
author
Joe Ellis
committed
Add Unix peer credentials authenticator
This authenticator uses Unix peer credentials for authentication. Unix peer credentials provide direct access to the (effective) uid/gid on the other end of a domain socket connect, without cooperation between the endpoints. This means that we can determine the uid/gid of the connecting process, and therefore infer the username of the user that owns said process. This authenticator: - grabs the (uid, gid) pair of the connecting process. - determine the username of the owner of the connecting process based on the uid. - creates an `ApplicationName` based on the username. Note that this patch depends on the following PR being merged: rust-lang/rust#75148 At the time of writing, this PR is currently under review and is not merged into the Rust stdlib. This patch therefore will not build with the current a stable/nightly compiler. Signed-off-by: Joe Ellis <[email protected]>
1 parent 9530d64 commit 9a65b40

File tree

8 files changed

+149
-5
lines changed

8 files changed

+149
-5
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ libc = "0.2.72"
4949
[dev-dependencies]
5050
ring = "0.16.12"
5151
lazy_static = "1.4.0"
52+
whoami = "0.9.0"
5253

5354
[build-dependencies]
5455
bindgen = "0.54.0"

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ This project uses the following third party crates:
114114
* picky (MIT and Apache-2.0)
115115
* users (MIT)
116116
* libc (MIT and Apache-2.0)
117+
* whoami (MIT or BSL-1.0)
117118

118119
This project uses the following third party libraries:
119120
* [**Mbed Crypto**](https://github.com/ARMmbed/mbed-crypto) (Apache-2.0)

src/authenticators/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//! Currently only a simple Direct Authenticator component is implemented.
1313
1414
pub mod direct_authenticator;
15+
pub mod uds_authenticator;
1516

1617
use crate::front::listener::ConnectionMetadata;
1718
use parsec_interface::requests::request::RequestAuth;
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright 2019 Contributors to the Parsec project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
//! Unix domain socket (UDS) peer credentials authenticator
4+
//!
5+
//! The `UDSAuthenticator` uses peer credentials from Unix domain sockets to perform
6+
//! authentication. As such, it uses the UID/GID to grab a username for the owner of the connecting
7+
//! process. This is used as the application name.
8+
9+
use super::ApplicationName;
10+
use super::Authenticate;
11+
use crate::front::listener::ConnectionMetadata;
12+
use log::error;
13+
use parsec_interface::requests::request::RequestAuth;
14+
use parsec_interface::requests::{ResponseStatus, Result};
15+
use users::get_user_by_uid;
16+
17+
#[derive(Copy, Clone, Debug)]
18+
pub struct UDSAuthenticator;
19+
20+
impl Authenticate for UDSAuthenticator {
21+
fn authenticate(
22+
&self,
23+
_auth: &RequestAuth,
24+
meta: Option<ConnectionMetadata>,
25+
) -> Result<ApplicationName> {
26+
let meta = match meta {
27+
Some(meta) => meta,
28+
None => {
29+
error!("Authenticator did not receive any metadata; cannot authenticate.");
30+
return Err(ResponseStatus::AuthenticationError);
31+
}
32+
};
33+
34+
let ucred = match meta {
35+
ConnectionMetadata::PeerCredentials(ucred) => ucred,
36+
// TODO: add wildcard pattern when `ConnectionMetadata` has more possibilities.
37+
};
38+
39+
let user = match get_user_by_uid(ucred.uid) {
40+
Some(user) => user,
41+
None => {
42+
error!("Couldn't get username for UID!");
43+
return Err(ResponseStatus::AuthenticationError);
44+
}
45+
};
46+
47+
let username = match user.name().to_str() {
48+
Some(username) => username,
49+
None => {
50+
error!("Couldn't get username for user!");
51+
return Err(ResponseStatus::AuthenticationError);
52+
}
53+
};
54+
55+
Ok(ApplicationName(String::from(username)))
56+
}
57+
}
58+
59+
#[cfg(test)]
60+
mod test {
61+
use super::super::Authenticate;
62+
use super::UDSAuthenticator;
63+
use crate::front::listener::ConnectionMetadata;
64+
use parsec_interface::requests::request::RequestAuth;
65+
use parsec_interface::requests::ResponseStatus;
66+
use std::os::unix::net::UCred;
67+
use std::os::unix::net::UnixStream;
68+
69+
#[test]
70+
fn successful_authentication() {
71+
// This test should PASS; we are verifying that our username gets set as the application
72+
// secret when using UDS authentication.
73+
74+
// Create two connected sockets.
75+
let (sock_a, _sock_b) = UnixStream::pair().unwrap();
76+
let (cred_a, _cred_b) = (sock_a.peer_cred().unwrap(), _sock_b.peer_cred().unwrap());
77+
78+
let authenticator = UDSAuthenticator {};
79+
80+
let req_auth = RequestAuth::new("secret".into());
81+
let conn_metadata = Some(ConnectionMetadata::PeerCredentials(cred_a));
82+
83+
let auth_name = authenticator
84+
.authenticate(&req_auth, conn_metadata)
85+
.expect("Failed to authenticate");
86+
87+
assert_eq!(auth_name.get_name(), whoami::username());
88+
}
89+
90+
#[test]
91+
fn unsuccessful_authentication_no_user() {
92+
// This test should FAIL; we're trying to use a non-existent user for authentication.
93+
let authenticator = UDSAuthenticator {};
94+
95+
let req_auth = RequestAuth::new("secret".into());
96+
let bogus_ucred = UCred {
97+
// 65535 is a special UID. This value is unused/avoided because it was the API error return
98+
// value when uid_t was 16 bits. We should be safe to use this to represent a user that
99+
// does not exist.
100+
uid: 65535,
101+
// 65534 is a special GID that many Linux distributions map to the group name
102+
// `nogroup`.
103+
gid: 65534,
104+
};
105+
let conn_metadata = Some(ConnectionMetadata::PeerCredentials(bogus_ucred));
106+
107+
let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
108+
109+
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
110+
}
111+
112+
#[test]
113+
fn unsuccessful_authentication_no_metadata() {
114+
// Create two connected sockets.
115+
let authenticator = UDSAuthenticator {};
116+
let req_auth = RequestAuth::new("secret".into());
117+
118+
let conn_metadata = None;
119+
let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
120+
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
121+
}
122+
123+
#[test]
124+
fn unsuccessful_authentication_wrong_metadata() {
125+
// TODO: this test needs implementing when we have more than one metadata type in
126+
// `PeerCredentials::ConnectionMetadata`. At the moment, the compiler just complains with
127+
// an 'unreachable branch' message.
128+
}
129+
}

src/front/domain_socket.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
//! Expose Parsec functionality using Unix domain sockets as an IPC layer.
66
//! The local socket is created at a predefined location.
77
use super::listener;
8-
use listener::Connection;
98
use listener::Listen;
9+
use listener::{Connection, ConnectionMetadata};
1010
use log::error;
1111
#[cfg(not(feature = "no-parsec-user-and-clients-group"))]
1212
use std::ffi::CString;
@@ -202,11 +202,12 @@ impl Listen for DomainSocketListener {
202202
format_error!("Failed to set stream as blocking", err);
203203
None
204204
} else {
205+
let ucred = stream.peer_cred().expect(
206+
"Failed to get peer credentials for Unix domain socket connection.",
207+
);
205208
Some(Connection {
206209
stream: Box::new(stream),
207-
// TODO: when possible, we want to replace this with the (uid, gid, pid)
208-
// triple for peer credentials. See listener.rs.
209-
metadata: None,
210+
metadata: Some(ConnectionMetadata::PeerCredentials(ucred)),
210211
})
211212
}
212213
}

src/front/listener.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//! of the IPC mechanism used as a Parsec front.
88
use derivative::Derivative;
99
use serde::Deserialize;
10+
use std::os::unix::net::UCred;
1011
use std::time::Duration;
1112

1213
// This trait is created to allow the iterator returned by incoming to iterate over a trait object
@@ -29,7 +30,7 @@ pub struct ListenerConfig {
2930
/// Specifies metadata associated with a connection, if any.
3031
#[derive(Copy, Clone, Debug)]
3132
pub enum ConnectionMetadata {
32-
// TODO: nothing here right now. Metadata types will be added as needed.
33+
PeerCredentials(UCred),
3334
}
3435

3536
/// Represents a connection to a single client. Contains a stream, used for communication with the

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
)]
3737
// This one is hard to avoid.
3838
#![allow(clippy::multiple_crate_versions)]
39+
// TODO: remove this if/when the Unix peer credentials PR gets merged. Link
40+
// here for reference: https://github.com/rust-lang/rust/pull/75148
41+
#![feature(peer_credentials_unix_socket)]
3942

4043
#[allow(unused)]
4144
macro_rules! format_error {

0 commit comments

Comments
 (0)