Skip to content
Draft
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
1 change: 1 addition & 0 deletions bob-common/mkosi.conf
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Packages=podman
openssh-sftp-server
udev
libsnappy1v5
pv

BuildPackages=build-essential
git
Expand Down
2 changes: 1 addition & 1 deletion bob-common/mkosi.extra/etc/sudoers.d/99-searcher
Original file line number Diff line number Diff line change
@@ -1 +1 @@
searcher ALL=(root) NOPASSWD: /usr/bin/toggle, /usr/bin/tdx-init set-passphrase, /usr/bin/systemctl restart lighthouse, /usr/local/bin/reboot
searcher ALL=(root) NOPASSWD: /usr/bin/toggle, /usr/bin/tdx-init set-passphrase, /usr/bin/systemctl restart lighthouse, /usr/local/bin/reboot, /usr/bin/feed-data-helper
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[Unit]
Description=Searcher Input FIFO Setup
After=persistent-mount.service
Requires=persistent-mount.service
Before=searcher-container.service

[Service]
Type=oneshot
ExecStart=/usr/bin/setup-input-fifo.sh
RemainAfterExit=yes
User=root
Group=root

[Install]
WantedBy=minimal.target
78 changes: 78 additions & 0 deletions bob-common/mkosi.extra/usr/bin/feed-data-helper
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/bin/bash
# Safely pipes authenticated data from searcher into container
# Security-hardened version with strict FIFO validation
# Supports continuous streaming without timeout
set -eu -o pipefail

FIFO_PATH="/persistent/input/data.fifo"
LOG_DIR="/persistent/delayed_logs"
LOG_FILE="$LOG_DIR/output.log"
MAX_RATE="100M" # 100MB/s rate limit to prevent accidental DoS

# Create log directory if it doesn't exist
mkdir -p "$LOG_DIR"

log_error() {
echo "[$(date -Iseconds)] feed-data ERROR: $1" >> "$LOG_FILE"
echo "Error: $1" >&2
}

log_info() {
echo "[$(date -Iseconds)] feed-data INFO: $1" >> "$LOG_FILE"
}

# Check system is initialized
if [ ! -f /etc/searcher-network.state ]; then
log_error "System not initialized. Run 'initialize' first."
exit 1
fi

# CRITICAL SECURITY VALIDATION: Verify FIFO exists and is not a symlink
if [ -L "$FIFO_PATH" ]; then
log_error "SECURITY VIOLATION: $FIFO_PATH is a symlink!"
exit 1
fi

if [ ! -e "$FIFO_PATH" ]; then
log_error "Input FIFO does not exist at $FIFO_PATH."
exit 1
fi

if [ ! -p "$FIFO_PATH" ]; then
log_error "SECURITY VIOLATION: $FIFO_PATH is not a FIFO!"
exit 1
fi

# Security: Verify ownership MUST be root (no exceptions in production)
FIFO_OWNER=$(stat -c %u "$FIFO_PATH")
if [ "$FIFO_OWNER" -ne 0 ]; then
log_error "SECURITY VIOLATION: FIFO not owned by root!"
exit 1
fi

# Verify we can write to the FIFO
if [ ! -w "$FIFO_PATH" ]; then
log_error "Cannot write to FIFO at $FIFO_PATH."
exit 1
fi

# Log start of data feed
log_info "Starting secure data feed to container (continuous stream)"

# Rate limit and pipe stdin to FIFO
# No timeout - this is a continuous stream that runs until:
# - The sender closes the connection (EOF)
# - The container stops reading (SIGPIPE)
Copy link
Contributor

@Ruteri Ruteri Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the searcher influence this, leaking data through how they read the data? Might be fine, but I would also worry about the rate limit

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no they can't because this is set by the host-os and not the searcher.
I added the rate-limit just to avoid self DoSing but ofc we can relax it even more or remove it fully while documenting/communicating it with the searcher to keep an eye on the ingress

# - The SSH connection drops
if pv -q -L "$MAX_RATE" > "$FIFO_PATH" 2>/dev/null; then
log_info "Data feed completed successfully"
exit 0
else
EXIT_CODE=$?
if [ $EXIT_CODE -eq 141 ]; then
log_info "Container disconnected (SIGPIPE)"
else
log_error "Feed terminated with exit code $EXIT_CODE"
fi
exit $EXIT_CODE
fi
1 change: 1 addition & 0 deletions bob-common/mkosi.extra/usr/bin/init-container.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ su -s /bin/sh searcher -c "cd ~ && podman run -d \
-v /persistent/searcher:/persistent:rw \
-v /etc/searcher/ssh_hostkey:/etc/searcher/ssh_hostkey:rw \
-v /persistent/searcher_logs:/var/log/searcher:rw \
-v /persistent/input:/persistent/input:ro \
-v /etc/searcher-logrotate.conf:/tmp/searcher.conf:ro \
$BOB_SEARCHER_EXTRA_PODMAN_FLAGS \
docker.io/library/ubuntu:24.04 \
Expand Down
62 changes: 62 additions & 0 deletions bob-common/mkosi.extra/usr/bin/setup-input-fifo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/bin/bash
# Sets up the input FIFO for searcher data feed with hardened security
# MUST run as root to ensure proper ownership and prevent tampering
set -eu -o pipefail

# Use /persistent/input to match existing /persistent/searcher pattern
INPUT_DIR="/persistent/input"
FIFO_PATH="$INPUT_DIR/data.fifo"

echo "Setting up hardened searcher input FIFO..."

# Ensure we're running as root for security
if [ "$EUID" -ne 0 ]; then
echo "ERROR: This script must be run as root for security reasons" >&2
exit 1
fi

# Create directory on persistent storage with secure ownership
mkdir -p "$INPUT_DIR"

# Set directory ownership to root with read/execute for searcher group
# Directory must be root-owned to prevent tampering
chown root:root "$INPUT_DIR"
chmod 755 "$INPUT_DIR" # rwxr-xr-x - searcher can traverse but not modify

# Remove any existing FIFO/symlink (security check)
if [ -e "$FIFO_PATH" ] || [ -L "$FIFO_PATH" ]; then
echo "Removing existing file at $FIFO_PATH for security..."
rm -f "$FIFO_PATH"
fi

# Create FIFO with secure permissions
mkfifo "$FIFO_PATH"
echo "Created FIFO at $FIFO_PATH"

# Set FIFO ownership: root owns it, searcher group can read
# This prevents the container from modifying/replacing the FIFO
chown root:1000 "$FIFO_PATH" # root:searcher
chmod 640 "$FIFO_PATH" # rw-r----- (root write, group read, others none)

# Verify the FIFO was created correctly (security validation)
if [ ! -p "$FIFO_PATH" ]; then
echo "ERROR: Failed to create FIFO at $FIFO_PATH" >&2
exit 1
fi

# Verify no symlinks (extra security check)
if [ -L "$FIFO_PATH" ]; then
echo "ERROR: Security violation - FIFO is a symlink!" >&2
exit 1
fi

echo "Hardened searcher input FIFO ready at $FIFO_PATH"
echo "Security features enabled:"
echo " - Root-owned directory (prevents tampering)"
echo " - Root-owned FIFO (prevents replacement)"
echo " - Read-only mount in container (prevents modification)"
echo " - Group read permission only (searcher UID 1000)"
echo ""
echo "Container will access it READ-ONLY at /persistent/input/data.fifo"
echo "Usage: cat data.json | ssh searcher@host feed-data"
exit 0
1 change: 1 addition & 0 deletions bob-common/mkosi.postinst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ for service in \
wait-for-key.service \
searcher-firewall.service \
dropbear.service \
searcher-input-fifo.service \
searcher-container.service \
ssh-pubkey-server.service \
cvm-reverse-proxy.service
Expand Down
16 changes: 14 additions & 2 deletions bob-common/searchersh.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ int main(int argc, char *argv[]) {
if (command == NULL) {
// If there's no token at all (e.g., empty or whitespace-only string),
// we print an error and quit.
fprintf(stderr, "No command provided. Valid commands are: toggle, status, logs, tail-the-logs, restart-lighthouse, reboot [force], initialize\n");
fprintf(stderr, "No command provided. Valid commands are: toggle, status, logs, tail-the-logs, restart-lighthouse, feed-data, reboot [force], initialize\n");
free(arg_copy); // free the memory
return 1; // return error code 1
}
Expand Down Expand Up @@ -186,6 +186,18 @@ int main(int argc, char *argv[]) {
return 1;
}

// If command == "feed-data", pipe stdin to container's input FIFO
else if (strcmp(command, "feed-data") == 0) {
// Pipes authenticated data from searcher into container
// Security: SSH authenticates searcher, sudo escalates to write to root-owned FIFO
// FIFO is root-owned (only root can write) and mounted read-only in container
execl("/usr/bin/sudo", "sudo", "/usr/bin/feed-data-helper", "feed-data-helper", NULL);

perror("execl failed (feed-data)");
free(arg_copy);
return 1;
}

// If command == "reboot", reboot the host machine using graceful shutdown wrapper
else if (strcmp(command, "reboot") == 0) {
// Check if force flag is provided
Expand All @@ -201,7 +213,7 @@ int main(int argc, char *argv[]) {
}

// If we reach here, the command didn't match any of the valid commands
fprintf(stderr, "Invalid command. Valid commands are: toggle, status, logs, tail-the-logs, restart-lighthouse, reboot [force], initialize\n");
fprintf(stderr, "Invalid command. Valid commands are: toggle, status, logs, tail-the-logs, restart-lighthouse, feed-data, reboot [force], initialize\n");
free(arg_copy); // Clean up allocated memory
return 1; // Return error code 1
}