Skip to content

Commit 17e340f

Browse files
committed
Make environment variables to preserve configurable
We were preserving $TMPDIR specfically in a hard-coded way and there was no way to opt out of it if desired. Now it has come to light that $LD_LIBRARY_PATH is also cleared when entering an spfs runtime. We would like to preserve that value too. Add a new configuration option so each site can decide to opt in to the preserving. Doing so may come with risks of privilege escalation. Signed-off-by: J Robert Ray <[email protected]>
1 parent 1c03e43 commit 17e340f

File tree

15 files changed

+174
-75
lines changed

15 files changed

+174
-75
lines changed

.github/workflows/rust.yml

+6
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ jobs:
102102
run: |
103103
export PATH="$PWD/target/debug:$PATH"
104104
make packages.lint
105+
- name: Configure SPFS for Integration tests
106+
run: |
107+
cat << EOF > /etc/spfs.toml
108+
[environment]
109+
variable_names_to_preserve = ["TMPDIR"]
110+
EOF
105111
- name: SPFS Integration Tests - Regular User
106112
run: |
107113
# Run tests as a normal user to verify privilege escalation

.site/spi/run_integration_tests.sh

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ mkdir -p "$ORIGIN_REPO"
1111
# Pre-create a repo
1212
SPFS_REMOTE_origin_ADDRESS="file://${ORIGIN_REPO}?create=true" spfs ls-tags -r origin
1313
export SPFS_REMOTE_origin_ADDRESS="file://${ORIGIN_REPO}"
14+
cat << EOF > /etc/spfs.toml
15+
[environment]
16+
variable_names_to_preserve = ["TMPDIR"]
17+
EOF
1418
# Run tests as a normal user to verify privilege escalation
1519
useradd -m e2e
1620
su e2e -c /tests/run_tests.sh
1721
# Run tests that need root
18-
tests/run_privileged_tests.sh
22+
tests/run_privileged_tests.sh

Cargo.lock

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

crates/spfs-cli/cmd-enter/src/cmd_enter.rs

+41-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33
// https://github.com/spkenv/spk
44

5+
use std::borrow::Cow;
56
use std::ffi::OsString;
67
#[cfg(feature = "sentry")]
78
use std::sync::atomic::Ordering;
@@ -12,6 +13,7 @@ use clap::{Args, Parser};
1213
use cli::configure_sentry;
1314
use miette::{Context, Result};
1415
use spfs::monitor::SPFS_MONITOR_FOREGROUND_LOGGING_VAR;
16+
use spfs::runtime::EnvKeyValue;
1517
use spfs::storage::fs::RenderSummary;
1618
use spfs_cli_common as cli;
1719
use spfs_cli_common::CommandName;
@@ -80,13 +82,34 @@ pub struct RemountArgs {
8082
enabled: bool,
8183
}
8284

85+
fn parse_env_key_value(s: &str) -> Result<EnvKeyValue> {
86+
let parts: Vec<&str> = s.splitn(2, '=').collect();
87+
if parts.len() != 2 {
88+
miette::bail!("Invalid environment key-value pair (missing '='): {s}");
89+
}
90+
if parts[0].is_empty() {
91+
miette::bail!("Invalid environment key-value pair (empty key): {s}");
92+
}
93+
if parts[0].contains(|c: char| c.is_whitespace()) {
94+
miette::bail!("Invalid environment key-value pair (key contains whitespace): {s}");
95+
}
96+
Ok(EnvKeyValue(parts[0].to_string(), parts[1].to_string()))
97+
}
98+
8399
#[derive(Debug, Args)]
84100
#[group(id = "enter_grp")]
85101
pub struct EnterArgs {
86102
/// The value to set $TMPDIR to in new environment
103+
///
104+
/// Deprecated: use --environment-override instead
87105
#[clap(long)]
88106
tmpdir: Option<String>,
89107

108+
/// Optional keys and values to set in the new environment, in the form
109+
/// KEY=VALUE. This option can be repeated.
110+
#[clap(long, value_parser = parse_env_key_value)]
111+
environment_override: Vec<EnvKeyValue>,
112+
90113
/// Put the rendering and syncing times into environment variables
91114
#[clap(long)]
92115
metrics_in_env: bool,
@@ -138,6 +161,7 @@ impl CmdEnter {
138161
// this function will eventually be required to discover the overlayfs
139162
// attributes. It can take many milliseconds to run so we prime the cache as
140163
// soon as possible in a separate thread
164+
141165
std::thread::spawn(spfs::runtime::overlayfs::overlayfs_available_options_prime_cache);
142166

143167
let mut runtime = self.load_runtime(config).await?;
@@ -231,7 +255,14 @@ impl CmdEnter {
231255
}
232256
};
233257

234-
owned.ensure_startup_scripts(self.enter.tmpdir.as_ref())?;
258+
let mut environment_overrides = Cow::Borrowed(&self.enter.environment_override);
259+
if let Some(tmpdir) = &self.enter.tmpdir {
260+
environment_overrides
261+
.to_mut()
262+
.push(EnvKeyValue("TMPDIR".to_string(), tmpdir.to_string()));
263+
}
264+
265+
owned.ensure_startup_scripts(environment_overrides.as_slice())?;
235266
std::env::set_var("SPFS_RUNTIME", owned.name());
236267

237268
Ok(Some(owned))
@@ -254,7 +285,15 @@ impl CmdEnter {
254285
let start_time = Instant::now();
255286
let render_summary = spfs::initialize_runtime(&mut owned).await?;
256287
self.report_render_summary(render_summary, start_time.elapsed().as_secs_f64());
257-
owned.ensure_startup_scripts(self.enter.tmpdir.as_ref())?;
288+
289+
let mut environment_overrides = Cow::Borrowed(&self.enter.environment_override);
290+
if let Some(tmpdir) = &self.enter.tmpdir {
291+
environment_overrides
292+
.to_mut()
293+
.push(EnvKeyValue("TMPDIR".to_string(), tmpdir.to_string()));
294+
}
295+
296+
owned.ensure_startup_scripts(environment_overrides.as_slice())?;
258297
std::env::set_var("SPFS_RUNTIME", owned.name());
259298

260299
Ok(Some(owned))

crates/spfs/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ gitignore = "1.0"
5050
glob = { workspace = true }
5151
hyper = { version = "0.14.16", features = ["client"] }
5252
indicatif = { workspace = true }
53-
itertools = "0.10.3"
53+
itertools = { workspace = true }
5454
libc = { workspace = true }
5555
miette = { workspace = true }
5656
nix = { workspace = true, features = ["fs"] }

crates/spfs/src/bootstrap.rs

+15-9
Original file line numberDiff line numberDiff line change
@@ -351,15 +351,21 @@ where
351351

352352
let mut enter_args = Vec::new();
353353

354-
// Capture the current $TMPDIR value here before it is lost when running
355-
// privileged process spfs-enter.
356-
if let Some(tmpdir_value_for_child_process) = std::env::var_os("TMPDIR") {
357-
tracing::trace!(
358-
?tmpdir_value_for_child_process,
359-
"capture existing value for $TMPDIR (build_spfs_enter_command)"
360-
);
361-
362-
enter_args.extend(["--tmpdir".into(), tmpdir_value_for_child_process]);
354+
// Capture the configured environment variable values here before they are
355+
// possibly lost when running privileged process spfs-enter.
356+
let config = crate::get_config()?;
357+
for key in &config.environment.variable_names_to_preserve {
358+
if let Ok(value) = std::env::var(key) {
359+
tracing::trace!(
360+
?key,
361+
?value,
362+
"capture existing variable (build_spfs_enter_command)"
363+
);
364+
enter_args.extend([
365+
"--environment_override".into(),
366+
format!("{key}={value}").into(),
367+
]);
368+
}
363369
}
364370

365371
enter_args.extend([

crates/spfs/src/bootstrap_test.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ async fn test_shell_initialization_startup_scripts(
5757

5858
let tmp_startup_dir = tmpdir.path().join("startup.d");
5959
std::fs::create_dir(&tmp_startup_dir).unwrap();
60-
rt.ensure_startup_scripts(None).unwrap();
60+
rt.ensure_startup_scripts(&[]).unwrap();
6161
for startup_script in &[&rt.config.sh_startup_file, &rt.config.csh_startup_file] {
6262
let mut cmd = Command::new("sed");
6363
cmd.arg("-i");
@@ -129,7 +129,7 @@ async fn test_shell_initialization_no_startup_scripts(shell: &str, tmpdir: tempf
129129

130130
let tmp_startup_dir = tmpdir.path().join("startup.d");
131131
std::fs::create_dir(&tmp_startup_dir).unwrap();
132-
rt.ensure_startup_scripts(None).unwrap();
132+
rt.ensure_startup_scripts(&[]).unwrap();
133133
for startup_script in &[&rt.config.sh_startup_file, &rt.config.csh_startup_file] {
134134
let mut cmd = Command::new("sed");
135135
cmd.arg("-i");

crates/spfs/src/config.rs

+19
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,24 @@ pub struct Sentry {
464464
pub email_domain: Option<String>,
465465
}
466466

467+
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
468+
#[serde(default)]
469+
pub struct Environment {
470+
/// Environment variables names to preserve when creating an spfs
471+
/// environment.
472+
///
473+
/// Most environment variables are preserved by default but a few are
474+
/// cleared for security purposes. Known values include `TMPDIR` and
475+
/// `LD_LIBRARY_PATH`. Any variable listed here will be propagated into a
476+
/// new spfs runtime by capturing their values before running spfs-enter and
477+
/// then setting them back to the captured values from inside the spfs
478+
/// runtime startup script.
479+
///
480+
/// Any variables listed here that are not present in the environment will
481+
/// remain unset in the new spfs environment.
482+
pub variable_names_to_preserve: Vec<String>,
483+
}
484+
467485
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
468486
#[serde(default)]
469487
pub struct Config {
@@ -474,6 +492,7 @@ pub struct Config {
474492
pub fuse: Fuse,
475493
pub monitor: Monitor,
476494
pub sentry: Sentry,
495+
pub environment: Environment,
477496
}
478497

479498
impl Config {

crates/spfs/src/runtime/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub use storage::{
2424
BindMount,
2525
Config,
2626
Data,
27+
EnvKeyValue,
2728
KeyValuePair,
2829
KeyValuePairBuf,
2930
LiveLayer,

0 commit comments

Comments
 (0)