Skip to content

Commit a9266cd

Browse files
emilyalbinijclulow
andcommitted
user: implement user factory
Co-Authored-By: Joshua M. Clulow <jmc@oxide.computer>
1 parent 58fcace commit a9266cd

19 files changed

Lines changed: 1854 additions & 2 deletions

File tree

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ members = [
1010
"download",
1111
"factory/aws",
1212
"factory/gimlet",
13+
"factory/user",
1314
"factory/lab",
1415
"factory/propolis",
1516
"github/client",
@@ -45,6 +46,7 @@ wrong_self_convention = "allow"
4546
[workspace.dependencies]
4647
ansi-to-html = { version = "0.2", features = [ "lazy-init" ] }
4748
anyhow = "1"
49+
async-compression = { version = "0.4", features = ["gzip", "tokio"] }
4850
aws-config = "1"
4951
aws-credential-types = "1"
5052
aws-runtime = "1"

common/src/unix.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
use anyhow::{anyhow, Result};
6+
use serde::{Deserialize, Serialize};
67
use std::ffi::{CStr, CString};
78
use std::io::{Error as IoError, ErrorKind};
89
use std::path::PathBuf;
@@ -60,10 +61,16 @@ pub fn getuid() -> Uid {
6061
Uid(unsafe { libc::getuid() })
6162
}
6263

63-
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
64+
#[derive(
65+
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
66+
)]
67+
#[serde(transparent)]
6468
pub struct Uid(pub u32);
6569

66-
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
70+
#[derive(
71+
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
72+
)]
73+
#[serde(transparent)]
6774
pub struct Gid(pub u32);
6875

6976
fn catch_errno<T, F: Fn() -> T>(f: F) -> Result<T, IoError> {

factory/user/Cargo.toml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[package]
2+
name = "buildomat-factory-user"
3+
version.workspace = true
4+
edition.workspace = true
5+
license.workspace = true
6+
7+
[lints]
8+
workspace = true
9+
10+
[dependencies]
11+
buildomat-common = { path = "../../common" }
12+
buildomat-database = { path = "../../database" }
13+
buildomat-client = { path = "../../client" }
14+
buildomat-types = { path = "../../types" }
15+
16+
anyhow = { workspace = true }
17+
async-compression = { workspace = true }
18+
base64 = { workspace = true }
19+
chrono = { workspace = true }
20+
getopts = { workspace = true }
21+
libc = { workspace = true }
22+
rand = { workspace = true }
23+
reqwest = { workspace = true }
24+
rusty_ulid = { workspace = true }
25+
schemars = { workspace = true }
26+
sea-query = { workspace = true }
27+
serde = { workspace = true }
28+
serde_json = { workspace = true }
29+
slog = { workspace = true }
30+
slog-term = { workspace = true }
31+
smf = { workspace = true }
32+
strum = { workspace = true }
33+
tempfile = { workspace = true }
34+
tokio = { workspace = true }
35+
toml = { workspace = true }
36+
usdt = { workspace = true }

factory/user/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# User Factory
2+
3+
The user factory creates unprivileged users on a shared illumos machine to run
4+
jobs. The factory ensures each job runs in an ephemeral system user with no
5+
privileges and no ability to write files outside of allowlisted directories, and
6+
cleans up any leftover files at the end of the job.
7+
8+
It currently only supports running on illumos, and requires that the factory
9+
runs with the `Primary Administrator` profile.
10+
11+
## Slots
12+
13+
The factory configuration has to define one or more named "slots". Each slot
14+
has a buildomat target it supports, plus optional configuration to define its
15+
execution environment. When the a job comes in from the buildomat core server,
16+
the factory will only accept it if there is a free slot for that job's target.
17+
This means the number of slots is also the concurrency limit on that system.
18+
19+
The minimal slot configuration requires only a slot name and the ID of the
20+
buildomat target the slot supports:
21+
22+
```toml
23+
[slots.NAME]
24+
target = "TARGET_ID"
25+
```
26+
27+
Additional configuration options are available:
28+
29+
* **`add_to_groups = ["GROUP", "GROUP"]`**: add the ephemeral system user to
30+
these groups in addition to the primary (ephemeral) group. This can be useful
31+
to allow jobs running in the slot to access system capabilities guarded by
32+
group membership.
33+
34+
* **`env.KEY = "VALUE"`**: set arbitrary environment variables in jobs executed
35+
inside of this slot.
36+
37+
An example of a full slot configuration:
38+
39+
```toml
40+
[slots.hubris-1]
41+
target = "..."
42+
env.HUMILITY_ENVIRONMENT = "/opt/ci/humility/1.json"
43+
add_to_groups = ["usb"]
44+
```
45+
46+
## Isolation
47+
48+
Each job runs as an ephemeral user, with randomly generated UID and GID,
49+
guaranteed not to collide with any UID and GID currently in use on the system.
50+
The name of the user is the worker ID prefixed by `bmat-`.
51+
52+
The whole filesystem is enforced to be read-only, except for a few allowlisted
53+
directories. All writeable directories are empty at the time the job starts, and
54+
are purged at the end of the job. The following directiories are writeable:
55+
56+
* `/home/$USER`
57+
* `/input`
58+
* `/opt/buildomat` (used by the buildomat agent)
59+
* `/tmp`
60+
* `/var/run` (used by the buildomat agent)
61+
* `/var/tmp`
62+
* `/work`

factory/user/schema.sql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- v 1
2+
CREATE TABLE worker (
3+
id TEXT NOT NULL PRIMARY KEY,
4+
slot TEXT NOT NULL,
5+
state TEXT NOT NULL,
6+
leased_job TEXT NOT NULL,
7+
bootstrap TEXT NOT NULL
8+
);
9+
10+
-- v 2
11+
CREATE INDEX worker_state ON worker (state);

factory/user/smf/worker.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version='1.0'?>
2+
<!DOCTYPE service_bundle SYSTEM '/usr/share/lib/xml/dtd/service_bundle.dtd.1'>
3+
<service_bundle name='buildomat-worker' type='manifest'>
4+
<service name='site/buildomat/factory-user-worker' type='service' version='0'>
5+
<exec_method name='start' type='method' timeout_seconds='60' exec='%EXEC%' />
6+
<exec_method name='stop' type='method' timeout_seconds='60' exec=':kill' />
7+
</service>
8+
</service_bundle>

0 commit comments

Comments
 (0)