Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ BLOOM_SSH_ENABLED=0
# BLOOM_SSH_MAX_LEASE_MINUTES=15
BLOOM_NFS_ENABLED=0
# BLOOM_NFS_KERNEL_CONFIG=./artifacts/nfs-kernel/vmlinux-6.1.155-nfsd.config
# Storage quota for workspace volumes in MiB (16–5120, default 512).
# BLOOM_STORAGE_QUOTA_MIB=512
# Comma-separated list of Bloom Petals to pre-install in every workspace.
# Default is empty (no auto-installed Petals). Users can still `bloom install`
# explicitly from the terminal (subject to the egress proxy allowlist).
# Max 32 entries, alphanumeric/dash/underscore only.
# BLOOM_PREINSTALLED_PETALS=gasless,enso
# Set controlled for the curated public package allowlist. Raw internet mode is
# rejected in public deployments.
# BLOOM_VM_EGRESS=controlled
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ artifacts/
*.log
.env
.DS_Store
__pycache__/
ops/guest-control/target/
73 changes: 63 additions & 10 deletions ops/bloom/guest-bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@
set -eu

# Provision Bloom inside a workspace using the authenticated login address as a
# watch-only wallet. This helper has no signer path by design: it accepts one
# EVM address, never a key/passphrase, and refuses to run beside signer state.
# watch-only wallet. This helper has no direct signer path: it accepts one EVM
# address, never a key/passphrase, and refuses to run beside signer state.
#
# Transaction signing uses Bloom's Sealed Approval ceremony: when a workspace
# process stages a transaction, Bloom writes plan.md and approval_challenge.json
# to the VFS outbox. The ceremony URL (http://localhost:18734/ceremony/<token>)
# is surfaced to the user. The user must connect via SSH with port forwarding
# (-L 18734:localhost:18734) and open the ceremony URL in their local browser
# to complete the WebAuthn approval. Private keys never enter the workspace VM.

BLOOM_BIN=${BLOOM_BIN:-/usr/local/bin/bloom}
BLOOM_WORKSPACE_ROOT=/workspace
Expand Down Expand Up @@ -78,6 +85,30 @@ verify_watch_only_keystore() {
[ "$stored" = "$expected" ] || die 'stored watch address does not match the authenticated login address'
}

validate_preinstalled_petals() {
config=$1
approved=$2
# Extract the preinstalled array contents from config.toml.
# Handles both single-line (preinstalled = ["a", "b"]) and multi-line forms.
entries=$(sed -n '/^preinstalled = \[/,/^\]/p' "$config" | tr -d '\n' | sed 's/.*\[//; s/\].*//')
# If empty array, nothing to check.
[ -n "$(printf '%s' "$entries" | tr -d ' \t')" ] || return 0
# Extract quoted values.
configured=$(printf '%s' "$entries" | tr ',' '\n' | sed 's/^[ \t]*"//; s/"[ \t]*$//' | grep -v '^$' || true)
# Build approved set from comma-separated list.
approved_set=$(printf '%s' "$approved" | tr ',' '\n' | grep -v '^$' || true)
# Check each configured petal is in the approved set.
while IFS= read -r petal; do
[ -n "$petal" ] || continue
case "$approved_set" in
*"$petal"*) ;;
*) die "preinstalled Petal '$petal' is not in the operator-approved list (BLOOM_PREINSTALLED_PETALS)" ;;
esac
done <<EOF
$configured
EOF
}

initialize_watch_wallet() {
address=$1
command -v "$BLOOM_BIN" >/dev/null 2>&1 || die "Bloom binary is unavailable: $BLOOM_BIN"
Expand All @@ -89,37 +120,59 @@ initialize_watch_wallet() {

# `bloom init` provisions network-fetched Petals by default in v0.1.3.
# First let a non-provisioning read command create Bloom's complete default
# config, then atomically persist the explicit empty-list opt-out. This keeps
# bootstrap offline and prevents remote executable content from entering the
# curated guest implicitly.
# config, then atomically persist the operator-approved Petal list. This keeps
# bootstrap deterministic: only operator-curated Petals enter the guest, not
# arbitrary remote executable content. Users can still `bloom install` explicit
# additions from the terminal (subject to the egress proxy allowlist).
config=${BLOOM_HOME}/config.toml
operator_petals=${BLOOM_PREINSTALLED_PETALS:-}
if [ ! -e "$config" ]; then
"$BLOOM_BIN" --home "$BLOOM_HOME" --quiet status >/dev/null
[ -f "$config" ] && [ ! -L "$config" ] || die 'Bloom did not create a regular config file'
[ "$(grep -c '^preinstalled = ' "$config")" -eq 1 ] || \
die 'Bloom config does not contain exactly one preinstalled Petals setting'
config_staging=${config}.workspace-bootstrap
awk '
# Build the TOML array value from the operator-approved comma-separated list.
toml_array='[]'
if [ -n "$operator_petals" ]; then
toml_array=''
remainder=$operator_petals
while [ -n "$remainder" ]; do
petal=${remainder%%,*}
[ -n "$petal" ] || { remainder=${remainder#*,}; continue; }
case "$petal" in
*[!a-zA-Z0-9_-]*) die "invalid petal name in BLOOM_PREINSTALLED_PETALS: $petal" ;;
esac
toml_array="${toml_array}\"$(printf '%s' "$petal" | sed 's/\\/\\\\/g; s/"/\\"/g')\", "
remainder=${remainder#$petal}
remainder=${remainder#,}
done
toml_array="[${toml_array%, }]"
fi
awk -v replacement="preinstalled = $toml_array" '
BEGIN { in_preinstalled = 0; seen = 0 }
in_preinstalled == 1 {
if ($0 ~ /^]$/) { in_preinstalled = 0 }
next
}
/^preinstalled = \[/ {
print "preinstalled = []"
print replacement
seen++
if ($0 !~ /]$/) { in_preinstalled = 1 }
next
}
{ print }
END { if (seen != 1 || in_preinstalled != 0) exit 42 }
' "$config" > "$config_staging" || die 'could not safely disable preinstalled Petals'
' "$config" > "$config_staging" || die 'could not safely set preinstalled Petals'
chmod 0600 "$config_staging"
mv -- "$config_staging" "$config"
else
[ -f "$config" ] && [ ! -L "$config" ] || die 'Bloom config must be a regular file'
grep -q '^preinstalled = \[\]$' "$config" || \
die 'preinstalled Petals must remain disabled in the watch-only workspace'
# On re-bootstrap (persistent workspace), validate that the preinstalled
# list contains only operator-approved entries. This allows user-initiated
# `bloom install` additions that match the approved list while catching
# unexpected entries that may have entered through compromise.
validate_preinstalled_petals "$config" "$operator_petals"
fi
"$BLOOM_BIN" --home "$BLOOM_HOME" --quiet init >/dev/null

Expand Down
167 changes: 167 additions & 0 deletions ops/guest-control/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions ops/guest-control/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "bloom-guest-control"
version = "1.0.0"
edition = "2021"
description = "Bounded guest-side file, job, and Bloom status service for Bloom Workspaces"
license = "MIT"

[[bin]]
name = "bloom-guest-control"
path = "src/main.rs"

[profile.release]
opt-level = 3
lto = true
strip = true
panic = "abort"
codegen-units = 1

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
base64 = "0.22"
regex = "1"
libc = "0.2"
once_cell = "1"
Loading
Loading