pub const AGENT_PATH: &str = "/usr/local/bin/alien-sandbox-agent";
pub const AGENT_PORT: u16 = 8971;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Isolation {
UidSplit,
Platform,
}
impl Isolation {
pub fn env_value(self) -> &'static str {
match self {
Self::UidSplit => "uid-split",
Self::Platform => "platform",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Authorization {
Transport,
Capability,
}
impl Authorization {
pub fn env_value(self) -> &'static str {
match self {
Self::Transport => "transport",
Self::Capability => "capability",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SandboxImage {
pub exec_uid: u32,
pub session_root: &'static str,
pub port: u16,
pub authorization: Authorization,
pub isolation: Isolation,
}
pub const AWS_MICROVM: SandboxImage = SandboxImage {
exec_uid: 60000,
session_root: "/sandbox",
port: AGENT_PORT,
authorization: Authorization::Transport,
isolation: Isolation::UidSplit,
};
pub const GCP_AGENT_PLATFORM: SandboxImage = SandboxImage {
exec_uid: 1000,
session_root: "/sandbox",
port: 8080,
authorization: Authorization::Transport,
isolation: Isolation::Platform,
};
pub fn identity_setup(image: &SandboxImage) -> String {
let SandboxImage {
exec_uid,
session_root,
..
} = *image;
format!(
r#"RUN printf 'sandbox:x:{exec_uid}:{exec_uid}::{session_root}:/sbin/nologin\n' >> /etc/passwd \
&& printf 'sandbox:x:{exec_uid}:\n' >> /etc/group \
&& mkdir -p {session_root} \
&& chown {exec_uid}:{exec_uid} {session_root} \
&& chmod 0700 {session_root}"#
)
}
pub fn contract_env(image: &SandboxImage) -> String {
let SandboxImage {
exec_uid,
session_root,
port,
authorization,
isolation,
} = *image;
format!(
r#"ENV ALIEN_SANDBOX_ROOT={session_root} \
ALIEN_SANDBOX_PORT={port} \
ALIEN_SANDBOX_AUTHORIZATION={authorization} \
ALIEN_SANDBOX_EXEC_UID={exec_uid} \
ALIEN_SANDBOX_EXEC_GID={exec_uid} \
ALIEN_SANDBOX_ISOLATION={isolation}"#,
authorization = authorization.env_value(),
isolation = isolation.env_value(),
)
}
pub fn entrypoint(image: &SandboxImage) -> String {
let ending = match image.isolation {
Isolation::UidSplit => String::new(),
Isolation::Platform => format!(
"# Explicit gid so a runtime that does not read /etc/passwd cannot start the agent in \
group 0, which\n# makes the exec drop a privilege crossing whose setgroups needs a \
CAP_SETGID this image lacks, so\n# every exec fails.\nUSER {uid}:{uid}\n",
uid = image.exec_uid
),
};
format!(
"EXPOSE {port}\n{ending}ENTRYPOINT [\"{AGENT_PATH}\"]",
port = image.port
)
}
#[cfg(test)]
const GCP_DOCKERFILE: &str = "docker/Dockerfile.alien-sandbox-agent";
#[cfg(test)]
const GCP_DOCKERFILE_UPDATE: &str = "UPDATE_SANDBOX_AGENT_DOCKERFILE";
#[cfg(test)]
fn gcp_agent_platform_dockerfile() -> String {
let image = &GCP_AGENT_PLATFORM;
format!(
r#"# Generated by `cargo test -p alien-core --lib sandbox_image`. Do not edit by hand.
# Regenerate with {GCP_DOCKERFILE_UPDATE}=1 in front of that command.
#
# Multi-arch build for the alien-sandbox-agent Docker image
# Run directly as the GCP Agent Platform sandbox; nothing layers on top of it
FROM docker.io/chainguard/wolfi-base:latest AS binary-selector
COPY target/aarch64-unknown-linux-musl/release/alien-sandbox-agent /tmp/alien-sandbox-agent-aarch64
COPY target/x86_64-unknown-linux-musl/release/alien-sandbox-agent /tmp/alien-sandbox-agent-x86_64
ARG TARGETARCH
RUN case "$TARGETARCH" in \
amd64) cp /tmp/alien-sandbox-agent-x86_64 /tmp/alien-sandbox-agent ;; \
arm64) cp /tmp/alien-sandbox-agent-aarch64 /tmp/alien-sandbox-agent ;; \
*) echo "unsupported TARGETARCH '$TARGETARCH'" >&2; exit 1 ;; \
esac
FROM docker.io/chainguard/wolfi-base:latest
# git is for the sandboxed command, not the agent, and pulls 24 transitive packages. That cost
# lands here because this image is the sandbox, with no customer base image underneath to carry it.
RUN apk add --no-cache git
# Root-owned and unwritable by uid {exec_uid}: the supervised command runs under that uid and must not
# be able to rewrite its own supervisor.
COPY --from=binary-selector --chown=0:0 --chmod=0755 \
/tmp/alien-sandbox-agent {AGENT_PATH}
# Numeric ids and a plain append rather than adduser, which differs across base distributions.
# Linux runs a process under a uid with no passwd entry, but tooling inside the sandbox reads one.
{identity}
# The template carries no env, so the contract lives here, and none of it is optional. transport
# serves an uncapabilitied request only from a socket `peer::transport_may_serve` cannot trace
# back to the exec uid, and the agent refuses the mode where /proc/net/tcp is unreadable.
{env}
# The release build resolves tracing-subscriber once across every package it names, and five of
# them ask for env-filter, so the agent's fmt::init() has an EnvFilter under it. Unset, that
# filter discards the startup warning saying this image serves requests without a capability.
ENV RUST_LOG=info
{entrypoint}
"#,
exec_uid = image.exec_uid,
identity = identity_setup(image),
env = contract_env(image),
entrypoint = entrypoint(image),
)
}
#[cfg(test)]
mod tests {
use super::*;
fn committed_path() -> std::path::PathBuf {
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join(GCP_DOCKERFILE)
}
fn first_difference(committed: &str, rendered: &str) -> String {
for (index, (left, right)) in committed.lines().zip(rendered.lines()).enumerate() {
if left != right {
let line = index + 1;
return format!("line {line}: committed {left:?}, contract renders {right:?}");
}
}
format!(
"committed has {} lines, the contract renders {}",
committed.lines().count(),
rendered.lines().count()
)
}
#[test]
fn the_committed_gcp_dockerfile_is_what_the_contract_renders() {
let path = committed_path();
let rendered = gcp_agent_platform_dockerfile();
if std::env::var_os(GCP_DOCKERFILE_UPDATE).is_some() {
std::fs::write(&path, &rendered)
.unwrap_or_else(|error| panic!("{} must be writable: {error}", path.display()));
return;
}
let committed = std::fs::read_to_string(&path)
.unwrap_or_else(|error| panic!("{} must be readable: {error}", path.display()));
assert!(
committed == rendered,
"{GCP_DOCKERFILE} has drifted from the contract it is rendered from.\n\
{}\n\
Regenerate it: {GCP_DOCKERFILE_UPDATE}=1 cargo test -p alien-core --lib sandbox_image",
first_difference(&committed, &rendered)
);
}
#[test]
fn the_two_images_carry_the_identities_their_stacks_were_built_against() {
assert_eq!(AWS_MICROVM.port, 8971);
assert_eq!(AWS_MICROVM.exec_uid, 60000);
assert_eq!(AWS_MICROVM.session_root, "/sandbox");
assert_eq!(GCP_AGENT_PLATFORM.port, 8080);
assert_eq!(GCP_AGENT_PLATFORM.exec_uid, 1000);
assert_eq!(GCP_AGENT_PLATFORM.session_root, "/sandbox");
for image in [&AWS_MICROVM, &GCP_AGENT_PLATFORM] {
assert_ne!(image.exec_uid, 0, "the exec uid must never be root");
}
}
#[test]
fn the_ending_an_image_declares_follows_its_isolation() {
assert!(!entrypoint(&AWS_MICROVM).contains("USER "));
assert!(contract_env(&AWS_MICROVM).contains("ALIEN_SANDBOX_ISOLATION=uid-split"));
assert!(entrypoint(&GCP_AGENT_PLATFORM).contains("\nUSER 1000:1000\n"));
assert!(contract_env(&GCP_AGENT_PLATFORM).contains("ALIEN_SANDBOX_ISOLATION=platform"));
}
}