Skip to content

Commit b0c7c6b

Browse files
committed
devenv: implement basic filesystem sandbox
1 parent 6bde927 commit b0c7c6b

File tree

6 files changed

+401
-100
lines changed

6 files changed

+401
-100
lines changed

devenv-sandbox/Cargo.lock

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

devenv-sandbox/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "devenv-sandbox"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
landlock = "0.4.1"
8+
9+
[workspace]

devenv-sandbox/src/main.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use landlock::{
2+
path_beneath_rules, Access, AccessFs, Ruleset, RulesetAttr, RulesetCreatedAttr, RulesetError,
3+
RulesetStatus, ABI,
4+
};
5+
use std::env;
6+
use std::path::PathBuf;
7+
use std::process::{Command, ExitStatus};
8+
9+
fn main() -> Result<(), Box<dyn std::error::Error>> {
10+
let args: Vec<String> = env::args().collect();
11+
12+
if args.len() < 2 {
13+
eprintln!("Usage: {} <command> [args...]", args[0]);
14+
std::process::exit(1);
15+
}
16+
17+
// Get DEVENV_ROOT from environment
18+
let devenv_root = match env::var("DEVENV_ROOT") {
19+
Ok(path) => path,
20+
Err(_) => {
21+
eprintln!("DEVENV_ROOT environment variable is not set");
22+
std::process::exit(1);
23+
}
24+
};
25+
26+
let runtime_dir = match env::var("XDG_RUNTIME_DIR") {
27+
Ok(path) => path,
28+
Err(_) => {
29+
eprintln!("XDG_RUNTIME_DIR environment variable is not set");
30+
std::process::exit(1);
31+
}
32+
};
33+
34+
// TODO: make sure that the user cannot modify this within the shell
35+
let home_dir = match env::var("HOME") {
36+
Ok(path) => path,
37+
Err(_) => {
38+
eprintln!("HOME environment variable is not set");
39+
std::process::exit(1);
40+
}
41+
};
42+
43+
// Verify the path exists
44+
let devenv_path = PathBuf::from(&devenv_root);
45+
if !devenv_path.exists() {
46+
eprintln!("DEVENV_ROOT path does not exist: {}", devenv_root);
47+
std::process::exit(1);
48+
}
49+
50+
// Set up landlock sandboxing
51+
match setup_landlock_sandbox(&devenv_root, &runtime_dir, &home_dir) {
52+
Ok(status) => match status {
53+
RulesetStatus::FullyEnforced => {
54+
println!("Landlock: Fully sandboxed to {}", devenv_root)
55+
}
56+
RulesetStatus::PartiallyEnforced => {
57+
println!("Landlock: Partially sandboxed to {}", devenv_root)
58+
}
59+
RulesetStatus::NotEnforced => {
60+
println!("Landlock: Not sandboxed! Please update your kernel.")
61+
}
62+
},
63+
Err(e) => {
64+
eprintln!("Failed to set up landlock sandbox: {}", e);
65+
std::process::exit(1);
66+
}
67+
}
68+
69+
// Execute the command
70+
let command = &args[1];
71+
let command_args = if args.len() > 2 { &args[2..] } else { &[] };
72+
73+
let status = match execute_command(command, command_args) {
74+
Ok(status) => status,
75+
Err(e) => {
76+
eprintln!("Failed to execute command: {}", e);
77+
std::process::exit(1);
78+
}
79+
};
80+
81+
// Exit with the same status as the executed command
82+
std::process::exit(status.code().unwrap_or(1));
83+
}
84+
85+
fn setup_landlock_sandbox(devenv_root: &str, runtime_dir: &str, home: &str) -> Result<RulesetStatus, RulesetError> {
86+
let abi = ABI::V2;
87+
88+
// Create a ruleset that only allows access to the DEVENV_ROOT directory
89+
let status = Ruleset::default()
90+
.handle_access(AccessFs::from_all(abi))?
91+
.create()?
92+
.add_rules(path_beneath_rules(&[
93+
devenv_root,
94+
runtime_dir,
95+
// for Python uv
96+
&(home.to_owned()+"/.cache/uv"),
97+
// error without: GC_push_all_stacks: pthread_getattr_np failed!
98+
"/proc", // TODO: limit further. I was too lazy.
99+
// process-compose logs here
100+
"/tmp", // TODO: can we limit this further?
101+
// required by process-compose for tui
102+
"/dev/tty",
103+
// for redirecting output into /dev/null
104+
"/dev/null",
105+
], AccessFs::from_all(abi)))?
106+
.add_rules(path_beneath_rules(&[
107+
"/nix",
108+
"/proc/stat",
109+
], AccessFs::from_read(abi)))?
110+
.restrict_self()?;
111+
112+
Ok(status.ruleset)
113+
}
114+
115+
fn execute_command(command: &str, args: &[String]) -> Result<ExitStatus, std::io::Error> {
116+
let status = Command::new(command).args(args).status()?;
117+
118+
Ok(status)
119+
}
120+

devenv.nix

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
{ inputs, pkgs, lib, config, ... }: {
1+
{
2+
inputs,
3+
pkgs,
4+
lib,
5+
config,
6+
...
7+
}:
8+
{
29
env.DEVENV_NIX = inputs.nix.packages.${pkgs.stdenv.system}.nix;
310
# ignore annoying browserlists warning that breaks pre-commit hooks
411
env.BROWSERSLIST_IGNORE_OLD_DATA = "1";
@@ -126,7 +133,9 @@
126133
{ pkgs, ... }: {
127134
128135
# Enable all languages tooling!
129-
${lib.concatStringsSep "\n " (map (lang: "languages.${lang}.enable = true;") (builtins.attrNames config.languages))}
136+
${lib.concatStringsSep "\n " (
137+
map (lang: "languages.${lang}.enable = true;") (builtins.attrNames config.languages)
138+
)}
130139
131140
# If you're missing a language, please contribute it by following examples of other languages <3
132141
}
@@ -138,12 +147,16 @@
138147
exec = ''
139148
cat > docs/services-all.md <<EOF
140149
\`\`\`nix
141-
${lib.concatStringsSep "\n " (map (lang: "services.${lang}.enable = true;") (builtins.attrNames config.services))}
150+
${lib.concatStringsSep "\n " (
151+
map (lang: "services.${lang}.enable = true;") (builtins.attrNames config.services)
152+
)}
142153
\`\`\`
143154
EOF
144155
cat > docs/languages-all.md <<EOF
145156
\`\`\`nix
146-
${lib.concatStringsSep "\n " (map (lang: "languages.${lang}.enable = true;") (builtins.attrNames config.languages))}
157+
${lib.concatStringsSep "\n " (
158+
map (lang: "languages.${lang}.enable = true;") (builtins.attrNames config.languages)
159+
)}
147160
\`\`\`
148161
EOF
149162
'';
@@ -162,55 +175,55 @@
162175
description = "Generate missing template markdown files";
163176
exec = ''
164177
165-
process_directory() {
166-
local nix_dir=$1
167-
local md_dir=$2
168-
local category=$3
178+
process_directory() {
179+
local nix_dir=$1
180+
local md_dir=$2
181+
local category=$3
169182
170-
nixFiles=($(ls $nix_dir/*.nix))
171-
mdFiles=($(ls $md_dir/*.md))
183+
nixFiles=($(ls $nix_dir/*.nix))
184+
mdFiles=($(ls $md_dir/*.md))
172185
173-
declare -a nixList
174-
declare -a mdList
186+
declare -a nixList
187+
declare -a mdList
175188
176-
# Remove extensions and populate lists
177-
for file in "''${nixFiles[@]}"; do
178-
baseName=$(basename "$file" .nix)
179-
nixList+=("$baseName")
180-
done
189+
# Remove extensions and populate lists
190+
for file in "''${nixFiles[@]}"; do
191+
baseName=$(basename "$file" .nix)
192+
nixList+=("$baseName")
193+
done
181194
182-
for file in "''${mdFiles[@]}"; do
183-
baseName=$(basename "$file" .md)
184-
mdList+=("$baseName")
185-
done
195+
for file in "''${mdFiles[@]}"; do
196+
baseName=$(basename "$file" .md)
197+
mdList+=("$baseName")
198+
done
186199
187-
IFS=$'\n' sorted_nix=($(sort <<<"''${nixList[*]}"))
188-
IFS=$'\n' sorted_md=($(sort <<<"''${mdList[*]}"))
200+
IFS=$'\n' sorted_nix=($(sort <<<"''${nixList[*]}"))
201+
IFS=$'\n' sorted_md=($(sort <<<"''${mdList[*]}"))
189202
190-
# Compare and create missing files
191-
missing_files=()
192-
for item in "''${sorted_nix[@]}"; do
193-
if [[ ! " ''${sorted_md[@]} " =~ " $item " ]]; then
194-
missing_files+=("$item")
195-
cat << EOF > "$md_dir/$item.md"
203+
# Compare and create missing files
204+
missing_files=()
205+
for item in "''${sorted_nix[@]}"; do
206+
if [[ ! " ''${sorted_md[@]} " =~ " $item " ]]; then
207+
missing_files+=("$item")
208+
cat << EOF > "$md_dir/$item.md"
196209
197210
198-
[comment]: # (Please add your documentation on top of this line)
211+
[comment]: # (Please add your documentation on top of this line)
199212
200-
@AUTOGEN_OPTIONS@
201-
EOF
202-
echo "Created missing file: $md_dir/$item.md"
203-
fi
204-
done
213+
@AUTOGEN_OPTIONS@
214+
EOF
215+
echo "Created missing file: $md_dir/$item.md"
216+
fi
217+
done
205218
206-
if [ ''${#missing_files[@]} -eq 0 ]; then
207-
echo "All $category docs markdown files are present."
208-
fi
209-
}
219+
if [ ''${#missing_files[@]} -eq 0 ]; then
220+
echo "All $category docs markdown files are present."
221+
fi
222+
}
210223
211-
process_directory "src/modules/languages" "docs/individual-docs/languages" "language"
212-
process_directory "src/modules/services" "docs/individual-docs/services" "service"
213-
process_directory "src/modules/process-managers" "docs/individual-docs/process-managers" "process manager"
224+
process_directory "src/modules/languages" "docs/individual-docs/languages" "language"
225+
process_directory "src/modules/services" "docs/individual-docs/services" "service"
226+
process_directory "src/modules/process-managers" "docs/individual-docs/process-managers" "process manager"
214227
'';
215228
};
216229

@@ -248,4 +261,7 @@ EOF
248261
files = "docs/assets/extra.css";
249262
};
250263
};
264+
enterShell = ''
265+
ls ~/.ssh
266+
'';
251267
}

0 commit comments

Comments
 (0)