Skip to content

Commit 6d9d9d9

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 6d9d9d9

File tree

5 files changed

+125
-10
lines changed

5 files changed

+125
-10
lines changed

Cargo.lock

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+3
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"
@@ -62,6 +64,7 @@ features = ["docs"]
6264

6365
[features]
6466
default = []
67+
no-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

+2
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)

ci.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ while [ "$#" -gt 0 ]; do
6767
PROVIDER_NAME=$1
6868
cp $(pwd)/e2e_tests/provider_cfg/$1/config.toml $CONFIG_PATH
6969
if [ "$PROVIDER_NAME" = "all" ]; then
70-
FEATURES="--features=all-providers"
70+
FEATURES="--features=all-providers,no-parsec-user-and-clients-group"
7171
else
72-
FEATURES="--features=$1-provider"
72+
FEATURES="--features=$1-provider,no-parsec-user-and-clients-group"
7373
fi
7474
;;
7575
*)

src/front/domain_socket.rs

+105-8
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(not(feature = "no-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(not(feature = "no-parsec-user-and-clients-group"))]
24+
const PARSEC_USERNAME: &str = "parsec";
25+
#[cfg(not(feature = "no-parsec-user-and-clients-group"))]
26+
const PARSEC_GROUPNAME: &str = "parsec-clients";
2127

2228
/// Unix Domain Socket IPC manager
2329
///
@@ -32,24 +38,26 @@ 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(not(feature = "no-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() {
53+
let parent_dir = socket.parent().unwrap();
54+
if !parent_dir.exists() {
55+
fs::create_dir_all(parent_dir)?;
56+
} else if socket.exists() {
5157
fs::remove_file(&socket)?;
5258
}
59+
#[cfg(not(feature = "no-parsec-user-and-clients-group"))]
60+
DomainSocketListener::set_socket_dir_permissions(parent_dir)?;
5361

5462
let listener = UnixListener::bind(SOCKET_PATH)?;
5563
listener.set_nonblocking(true)?;
@@ -84,6 +92,95 @@ impl DomainSocketListener {
8492

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

89186
impl Listen for DomainSocketListener {

0 commit comments

Comments
 (0)