Skip to content

Commit 91c838d

Browse files
committed
Added user and group checks. Auto create socket dir.
Parsec must be run as user `parsec` and `parsec` must be member of group `parsec-client`. This is disabled if feature `testing` is specified. Also auto created `/tmp/parsec` if it does not already exist. Signed-off-by: Samuel Bailey <[email protected]>
1 parent 769a59b commit 91c838d

File tree

3 files changed

+121
-11
lines changed

3 files changed

+121
-11
lines changed

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ picky = "5.0.0"
4343
psa-crypto = { version = "0.3.0" , default-features = false, features = ["operations"], optional = true }
4444
zeroize = { version = "1.1.0", features = ["zeroize_derive"] }
4545
picky-asn1-x509 = { version = "0.1.0", optional = true }
46+
users = "0.10.0"
47+
libc = "0.2.72"
4648

4749
[dev-dependencies]
4850
ring = "0.16.12"
@@ -61,7 +63,8 @@ mbed-crypto-version = "mbedcrypto-2.0.0"
6163
features = ["docs"]
6264

6365
[features]
64-
default = []
66+
default = ["parsec-user-and-clients-group"]
67+
parsec-user-and-clients-group = []
6568
mbed-crypto-provider = ["psa-crypto"]
6669
pkcs11-provider = ["pkcs11", "picky-asn1-der", "picky-asn1", "picky-asn1-x509"]
6770
tpm-provider = ["tss-esapi", "picky-asn1-der", "picky-asn1", "picky-asn1-x509"]

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ This project uses the following third party crates:
112112
* sha2 (MIT and Apache-2.0)
113113
* hex (MIT and Apache-2.0)
114114
* picky (MIT and Apache-2.0)
115+
* users (MIT)
116+
* libc (MIT and Apache-2.0)
115117

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

src/front/domain_socket.rs

Lines changed: 115 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use super::listener;
88
use listener::Connection;
99
use listener::Listen;
1010
use log::error;
11+
#[cfg(feature = "parsec-user-and-clients-group")]
12+
use std::ffi::CString;
1113
use std::fs;
1214
use std::fs::Permissions;
1315
use std::io::{Error, ErrorKind, Result};
@@ -18,6 +20,10 @@ use std::path::Path;
1820
use std::time::Duration;
1921

2022
static SOCKET_PATH: &str = "/tmp/parsec/parsec.sock";
23+
#[cfg(feature = "parsec-user-and-clients-group")]
24+
const PARSEC_USERNAME: &str = "parsec";
25+
#[cfg(feature = "parsec-user-and-clients-group")]
26+
const PARSEC_GROUPNAME: &str = "parsec-clients";
2127

2228
/// Unix Domain Socket IPC manager
2329
///
@@ -32,25 +38,35 @@ pub struct DomainSocketListener {
3238

3339
impl DomainSocketListener {
3440
/// Initialise the connection to the Unix socket.
35-
///
36-
/// # Panics
37-
/// - if a file/socket exists at the path specified for the socket and `remove_file`
38-
/// fails
39-
/// - if binding to the socket path fails
4041
pub fn new(timeout: Duration) -> Result<Self> {
41-
// If this Parsec instance was socket activated (see the `parsec.socket`
42+
#[cfg(feature = "parsec-user-and-clients-group")]
43+
DomainSocketListener::check_user_details()?;
44+
45+
// is Parsec instance was socket activated (see the `parsec.socket`
4246
// file), the listener will be opened by systemd and passed to the
4347
// process.
4448
// If Parsec was service activated or not started under systemd, this
4549
// will return `0`.
4650
let listener = match sd_notify::listen_fds()? {
4751
0 => {
4852
let socket = Path::new(SOCKET_PATH);
49-
50-
if socket.exists() {
51-
fs::remove_file(&socket)?;
53+
if let Some(parent_dir) = socket.parent() {
54+
if !parent_dir.exists() {
55+
fs::create_dir_all(parent_dir)?;
56+
#[cfg(feature = "parsec-user-and-clients-group")]
57+
DomainSocketListener::set_socket_dir_permissions(parent_dir)?;
58+
} else if socket.exists() {
59+
fs::remove_file(&socket)?;
60+
}
61+
} else {
62+
// Invalid path - socket should not be created in root
63+
// `socket.path` returns None if there is no parent directory (i.e. is in root)
64+
error!("Socket path {} is invalid.", SOCKET_PATH);
65+
return Err(Error::new(
66+
ErrorKind::InvalidInput,
67+
"Socket path is invalid",
68+
));
5269
}
53-
5470
let listener = UnixListener::bind(SOCKET_PATH)?;
5571
listener.set_nonblocking(true)?;
5672

@@ -84,6 +100,95 @@ impl DomainSocketListener {
84100

85101
Ok(Self { listener, timeout })
86102
}
103+
104+
#[cfg(feature = "parsec-user-and-clients-group")]
105+
fn set_socket_dir_permissions(parent_dir: &Path) -> Result<()> {
106+
if let Some(parent_dir_str) = parent_dir.to_str() {
107+
fs::set_permissions(parent_dir, Permissions::from_mode(0o750))?;
108+
// Although `parsec` has to be part of the `parsec_clients` group, it may not be the primary group. Therefore force group ownership to `parsec_clients`
109+
if unsafe {
110+
let parent_dir_cstr = CString::new(parent_dir_str)
111+
.expect("Failed to convert socket path parent to cstring");
112+
{
113+
libc::chown(
114+
parent_dir_cstr.as_ptr(),
115+
users::get_current_uid(), // To get to this point, user has to be `parsec`
116+
users::get_group_by_name(PARSEC_GROUPNAME).unwrap().gid(), // `parsec_clients` exists by this point so should be safe
117+
)
118+
}
119+
} != 0
120+
{
121+
error!(
122+
"Changing ownership of {} to user {} and group {} failed.",
123+
parent_dir_str, PARSEC_USERNAME, PARSEC_GROUPNAME
124+
);
125+
return Err(Error::new(
126+
ErrorKind::Other,
127+
"Changing ownership of socket directory failed",
128+
));
129+
}
130+
} else {
131+
error!(
132+
"Error converting {} parent directory to string.",
133+
SOCKET_PATH
134+
);
135+
return Err(Error::new(
136+
ErrorKind::InvalidInput,
137+
"Error retrieving parent directory for socket",
138+
));
139+
}
140+
Ok(())
141+
}
142+
143+
#[cfg(feature = "parsec-user-and-clients-group")]
144+
fn check_user_details() -> Result<()> {
145+
// Check Parsec is running as parsec user
146+
if users::get_current_username() != Some(PARSEC_USERNAME.into()) {
147+
error!(
148+
"Incorrect user. Parsec should be run as user {}.",
149+
PARSEC_USERNAME
150+
);
151+
return Err(Error::new(
152+
ErrorKind::PermissionDenied,
153+
"Parsec run as incorrect user",
154+
));
155+
}
156+
// Check Parsec client group exists and parsec user is a member of it
157+
if let Some(parsec_clients_group) = users::get_group_by_name(PARSEC_GROUPNAME) {
158+
if let Some(groups) = users::get_user_groups(PARSEC_USERNAME, users::get_current_gid())
159+
{
160+
// Split to make `clippy` happy
161+
let parsec_user_in_parsec_clients_group = groups.into_iter().any(|group| {
162+
group.gid() == parsec_clients_group.gid()
163+
&& group.name() == parsec_clients_group.name()
164+
});
165+
// Check the parsec user is a member of the parsec clients group
166+
if parsec_user_in_parsec_clients_group {
167+
return Ok(());
168+
}
169+
error!(
170+
"{} user not a member of {}.",
171+
PARSEC_USERNAME, PARSEC_GROUPNAME
172+
);
173+
Err(Error::new(
174+
ErrorKind::PermissionDenied,
175+
"User permissions incorrect",
176+
))
177+
} else {
178+
error!("Retrieval of groups for user {} failed.", PARSEC_USERNAME);
179+
Err(Error::new(
180+
ErrorKind::InvalidInput,
181+
"Failed to retrieve user groups",
182+
))
183+
}
184+
} else {
185+
error!("{} group does not exist.", PARSEC_GROUPNAME);
186+
Err(Error::new(
187+
ErrorKind::PermissionDenied,
188+
"Group permissions incorrect",
189+
))
190+
}
191+
}
87192
}
88193

89194
impl Listen for DomainSocketListener {

0 commit comments

Comments
 (0)