Skip to content

Commit c02b5ef

Browse files
author
Joe Ellis
committed
Add peer credentials authenticator
This authenticator uses peer credentials for authentication. The specific type of peer credentials in mind at the moment are Unix peer credentials, but this can be extended in the future. 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 trivially determine the uid/gid of the connecting process, which we can then use for authentication. This authenticator: - grabs the (uid, gid) pair of the connecting process. - grabs the self-declared uid sent in the authentication request. - verifies that authentication is successful by checking that the self-declared uid in the authentication request is equal to the actual uid from the peer credentials. - if authentication was successful, creates an `ApplicationName` based on the uid. The authenticator is hidden behind the Cargo feature `peer-credentials-authenticator`. Note that gid is currently unused by the authenticator. Also 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 d56700c commit c02b5ef

File tree

4 files changed

+218
-8
lines changed

4 files changed

+218
-8
lines changed

src/authenticators/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
//! used throughout the service for identifying the request initiator. The input to an authentication
99
//! is the `RequestAuth` field of a request, which is parsed by the authenticator specified in the header.
1010
//! The authentication functionality is abstracted through an `Authenticate` trait.
11-
//!
12-
//! Currently only a simple Direct Authenticator component is implemented.
1311
1412
pub mod direct_authenticator;
1513

14+
pub mod unix_peer_credentials_authenticator;
15+
1616
use crate::front::listener::ConnectionMetadata;
1717
use parsec_interface::operations::list_authenticators;
1818
use parsec_interface::requests::request::RequestAuth;
@@ -35,7 +35,7 @@ pub trait Authenticate {
3535
/// Authenticates a `RequestAuth` payload and returns the `ApplicationName` if successful. A
3636
/// optional `ConnectionMetadata` object is passed in too, since it is sometimes possible to
3737
/// perform authentication based on the connection's metadata (i.e. as is the case for UNIX
38-
/// domain sockets with peer credentials).
38+
/// domain sockets with Unix peer credentials).
3939
///
4040
/// # Errors
4141
///
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
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+
12+
use super::ApplicationName;
13+
use super::Authenticate;
14+
use crate::front::listener::ConnectionMetadata;
15+
use log::error;
16+
use parsec_interface::operations::list_authenticators;
17+
use parsec_interface::requests::request::RequestAuth;
18+
use parsec_interface::requests::AuthType;
19+
use parsec_interface::requests::{ResponseStatus, Result};
20+
use parsec_interface::secrecy::ExposeSecret;
21+
use std::convert::TryInto;
22+
23+
/// Unix peer credentials authenticator.
24+
#[derive(Copy, Clone, Debug)]
25+
pub struct UnixPeerCredentialsAuthenticator;
26+
27+
impl Authenticate for UnixPeerCredentialsAuthenticator {
28+
fn describe(&self) -> Result<list_authenticators::AuthenticatorInfo> {
29+
Ok(list_authenticators::AuthenticatorInfo {
30+
description: String::from(
31+
"Uses Unix peer credentials to authenticate the client. Verifies that the self-declared \
32+
Unix user identifier (UID) in the request's authentication header matches that which is \
33+
found from the peer credentials."
34+
),
35+
version_maj: 0,
36+
version_min: 1,
37+
version_rev: 0,
38+
id: AuthType::PeerCredentials,
39+
})
40+
}
41+
42+
fn authenticate(
43+
&self,
44+
auth: &RequestAuth,
45+
meta: Option<ConnectionMetadata>,
46+
) -> Result<ApplicationName> {
47+
// Parse authentication request.
48+
let expected_uid_bytes = auth.buffer.expose_secret();
49+
50+
const EXPECTED_UID_SIZE_BYTES: usize = 4;
51+
let expected_uid: [u8; EXPECTED_UID_SIZE_BYTES] =
52+
expected_uid_bytes.as_slice().try_into().map_err(|_| {
53+
error!(
54+
"UID in authentication request is not the right size (expected: {}, got: {}).",
55+
EXPECTED_UID_SIZE_BYTES,
56+
expected_uid_bytes.len()
57+
);
58+
ResponseStatus::AuthenticationError
59+
})?;
60+
let expected_uid = u32::from_le_bytes(expected_uid);
61+
62+
let meta = meta.ok_or_else(|| {
63+
error!("Authenticator did not receive any metadata; cannot perform authentication.");
64+
ResponseStatus::AuthenticationError
65+
})?;
66+
67+
#[allow(unreachable_patterns)]
68+
let (uid, _gid, _pid) = match meta {
69+
ConnectionMetadata::UnixPeerCredentials { uid, gid, pid } => (uid, gid, pid),
70+
_ => {
71+
error!("Wrong metadata type given to Unix peer credentials authenticator.");
72+
return Err(ResponseStatus::AuthenticationError);
73+
}
74+
};
75+
76+
// Authentication is successful if the _actual_ UID from the Unix peer credentials equals
77+
// the self-declared UID in the authentication request.
78+
if uid == expected_uid {
79+
Ok(ApplicationName(uid.to_string()))
80+
} else {
81+
error!("Declared UID in authentication request does not match the process's UID.");
82+
Err(ResponseStatus::AuthenticationError)
83+
}
84+
}
85+
}
86+
87+
#[cfg(test)]
88+
mod test {
89+
use super::super::Authenticate;
90+
use super::UnixPeerCredentialsAuthenticator;
91+
use crate::front::listener::ConnectionMetadata;
92+
use parsec_interface::requests::request::RequestAuth;
93+
use parsec_interface::requests::ResponseStatus;
94+
use rand::Rng;
95+
use std::os::unix::net::UnixStream;
96+
use users::get_current_uid;
97+
98+
#[test]
99+
fn successful_authentication() {
100+
// This test should PASS; we are verifying that our username gets set as the application
101+
// secret when using Unix peer credentials authentication with Unix domain sockets.
102+
103+
// Create two connected sockets.
104+
let (sock_a, _sock_b) = UnixStream::pair().unwrap();
105+
let (cred_a, _cred_b) = (sock_a.peer_cred().unwrap(), _sock_b.peer_cred().unwrap());
106+
107+
let authenticator = UnixPeerCredentialsAuthenticator {};
108+
109+
let req_auth_data = cred_a.uid.to_le_bytes().to_vec();
110+
let req_auth = RequestAuth::new(req_auth_data);
111+
let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
112+
uid: cred_a.uid,
113+
gid: cred_a.gid,
114+
pid: None,
115+
});
116+
117+
let auth_name = authenticator
118+
.authenticate(&req_auth, conn_metadata)
119+
.expect("Failed to authenticate");
120+
121+
assert_eq!(auth_name.get_name(), get_current_uid().to_string());
122+
}
123+
124+
#[test]
125+
fn unsuccessful_authentication_wrong_declared_uid() {
126+
// This test should FAIL; we are trying to authenticate, but we are declaring the wrong
127+
// UID.
128+
129+
// Create two connected sockets.
130+
let (sock_a, _sock_b) = UnixStream::pair().unwrap();
131+
let (cred_a, _cred_b) = (sock_a.peer_cred().unwrap(), _sock_b.peer_cred().unwrap());
132+
133+
let authenticator = UnixPeerCredentialsAuthenticator {};
134+
135+
let wrong_uid = cred_a.uid + 1;
136+
let wrong_req_auth_data = wrong_uid.to_le_bytes().to_vec();
137+
let req_auth = RequestAuth::new(wrong_req_auth_data);
138+
let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
139+
uid: cred_a.uid,
140+
gid: cred_a.gid,
141+
pid: cred_a.pid,
142+
});
143+
144+
let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
145+
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
146+
}
147+
148+
#[test]
149+
fn unsuccessful_authentication_garbage_data() {
150+
// This test should FAIL; we are sending garbage (random) data in the request.
151+
152+
// Create two connected sockets.
153+
let (sock_a, _sock_b) = UnixStream::pair().unwrap();
154+
let (cred_a, _cred_b) = (sock_a.peer_cred().unwrap(), _sock_b.peer_cred().unwrap());
155+
156+
let authenticator = UnixPeerCredentialsAuthenticator {};
157+
158+
let garbage_data = rand::thread_rng().gen::<[u8; 32]>().to_vec();
159+
let req_auth = RequestAuth::new(garbage_data);
160+
let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials {
161+
uid: cred_a.uid,
162+
gid: cred_a.gid,
163+
pid: cred_a.pid,
164+
});
165+
166+
let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
167+
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
168+
}
169+
170+
#[test]
171+
fn unsuccessful_authentication_no_metadata() {
172+
let authenticator = UnixPeerCredentialsAuthenticator {};
173+
let req_auth = RequestAuth::new("secret".into());
174+
175+
let conn_metadata = None;
176+
let auth_result = authenticator.authenticate(&req_auth, conn_metadata);
177+
assert_eq!(auth_result, Err(ResponseStatus::AuthenticationError));
178+
}
179+
180+
#[test]
181+
fn unsuccessful_authentication_wrong_metadata() {
182+
// TODO(new_metadata_variant): this test needs implementing when we have more than one
183+
// metadata type. At the moment, the compiler just complains with an 'unreachable branch'
184+
// message.
185+
}
186+
}

src/front/domain_socket.rs

+16-4
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,23 @@ impl Listen for DomainSocketListener {
202202
format_error!("Failed to set stream as blocking", err);
203203
None
204204
} else {
205+
let ucred = stream
206+
.peer_cred()
207+
.map_err(|err| {
208+
format_error!(
209+
"Failed to grab peer credentials metadata from UnixStream",
210+
err
211+
);
212+
err
213+
})
214+
.ok()?;
205215
Some(Connection {
206216
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,
217+
metadata: Some(ConnectionMetadata::UnixPeerCredentials {
218+
uid: ucred.uid,
219+
gid: ucred.gid,
220+
pid: ucred.pid,
221+
}),
210222
})
211223
}
212224
}

src/front/listener.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,19 @@ pub struct ListenerConfig {
3434
/// Specifies metadata associated with a connection, if any.
3535
#[derive(Copy, Clone, Debug)]
3636
pub enum ConnectionMetadata {
37-
// TODO: nothing here right now. Metadata types will be added as needed.
37+
/// Unix peer credentials metadata for Unix domain sockets.
38+
UnixPeerCredentials {
39+
/// The effective UID of the connecting process.
40+
uid: u32,
41+
/// The effective GID of the connecting process.
42+
gid: u32,
43+
/// The optional PID of the connecting process. This is an Option<u32> because not all
44+
/// platforms support retrieving PID via a domain socket.
45+
pid: Option<i32>,
46+
},
47+
// NOTE: there is currently only _one_ variant of the ConnectionMetadata enum. When a second
48+
// variant is added, you will need to update some tests!
49+
// You should grep the tests for `TODO(new_metadata_variant)` and update them accordingly.
3850
}
3951

4052
/// Represents a connection to a single client

0 commit comments

Comments
 (0)