diff --git a/AGENTS.md b/AGENTS.md index 850b81e..cf5ab6f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,13 +27,12 @@ Instead, guide the operator with exact commands to run on the FreeBSD system. ## System Configuration -**Privilege escalation:** Unified on `sudo` (not doas), with `mac_do` adoption planned +**Privilege model:** operator commands use `sudo`; agent runtime privilege goes through hostd. -- All documentation uses sudo -- Admin panel uses sudo for privileged operations -- Agents should use sudo in examples and commands -- mac_do (kernel MAC module) will be added for per-jail privilege isolation -- See `Clawdie-AI/docs/internal/SUDO_REPLACEMENT.md` for the adoption plan +- All operator-facing documentation uses sudo for interactive host administration +- Agent runtime code must not shell out to sudo for privileged host changes +- Privileged agent operations go through the Clawdie-AI hostd RPC layer +- The ISO enables FreeBSD `mac_do` with an empty rule set so future in-jail UID transitions can be added narrowly when there is a concrete consumer **Unified ISO:** Single image works on VPS, baremetal, and cloud diff --git a/firstboot/shell-system.sh b/firstboot/shell-system.sh index 5ff092e..d20c0b7 100755 --- a/firstboot/shell-system.sh +++ b/firstboot/shell-system.sh @@ -9,6 +9,8 @@ set -eu # Configuration (can be overridden for testing) RC_CONF="${RC_CONF:-/etc/rc.conf}" +LOADER_CONF="${LOADER_CONF:-/boot/loader.conf}" +SYSCTL_CONF="${SYSCTL_CONF:-/etc/sysctl.conf}" HOSTNAME_FILE="${HOSTNAME_FILE:-/etc/hostname}" PROFILE_DIR="${PROFILE_DIR:-/etc/profile.d}" LOG_FILE="${LOG_FILE:-/var/log/clawdie-firstboot.log}" @@ -59,6 +61,10 @@ clawdie_shell_system_config() { clawdie_shell_system_setup_locale log_msg "[system] Setup locale" + # Enable FreeBSD mac_do framework with no credential grants yet + clawdie_shell_system_setup_mac_do + log_msg "[system] Setup mac_do framework" + # Enable services clawdie_shell_system_enable_services log_msg "[system] Enabled services" @@ -100,19 +106,54 @@ clawdie_shell_system_sysrc() { # Add or update a variable in rc.conf # Input: VAR=VALUE - local var_assignment="$1" - local var_name var_value + clawdie_shell_system_set_config_line "$RC_CONF" "$1" +} + +clawdie_shell_system_set_config_line() { + # Add or update KEY=VALUE in a config file. + # Input: FILE KEY=VALUE + + local config_file="$1" + local var_assignment="$2" + local var_name var_name=$(echo "$var_assignment" | cut -d= -f1) - var_value=$(echo "$var_assignment" | cut -d= -f2-) - # Check if var already set (idempotence) - if grep -q "^${var_name}=" "$RC_CONF" 2>/dev/null; then - # Update existing (use | as delimiter to avoid issues with / in values) - sed -i '' "s|^${var_name}=.*|${var_assignment}|" "$RC_CONF" + if [ ! -f "$config_file" ]; then + mkdir -p "$(dirname "$config_file")" + touch "$config_file" + fi + + if grep -q "^${var_name}=" "$config_file" 2>/dev/null; then + sed -i '' "s|^${var_name}=.*|${var_assignment}|" "$config_file" else - # Append new - echo "$var_assignment" >> "$RC_CONF" + echo "$var_assignment" >> "$config_file" + fi +} + +# ============================================================================ +# MAC_DO FRAMEWORK +# ============================================================================ + +clawdie_shell_system_setup_mac_do() { + # Load FreeBSD mac_do at boot, but grant no credential transitions yet. + # FreeBSD 15 accepts an empty rule list as no-op policy: security.mac.do.rules= + + clawdie_shell_system_set_config_line "$LOADER_CONF" 'mac_do_load="YES"' + clawdie_shell_system_set_config_line "$SYSCTL_CONF" 'security.mac.do.rules=' + + # Best-effort live activation for the firstboot session. The persistent + # loader/sysctl files above are the source of truth after reboot. + if command -v kldload >/dev/null 2>&1; then + kldload mac_do 2>/dev/null || true + elif [ -x /sbin/kldload ]; then + /sbin/kldload mac_do 2>/dev/null || true + fi + + if command -v sysctl >/dev/null 2>&1; then + sysctl security.mac.do.rules= >/dev/null 2>&1 || true + elif [ -x /sbin/sysctl ]; then + /sbin/sysctl security.mac.do.rules= >/dev/null 2>&1 || true fi } @@ -249,6 +290,17 @@ clawdie_shell_system_validate() { return 1 fi + # Check mac_do framework config exists. Empty rules intentionally grant no + # credential transitions. + if ! grep -q '^mac_do_load="YES"' "$LOADER_CONF" 2>/dev/null; then + echo "ERROR: mac_do_load not set in $LOADER_CONF" >&2 + return 1 + fi + if ! grep -q '^security.mac.do.rules=' "$SYSCTL_CONF" 2>/dev/null; then + echo "ERROR: security.mac.do.rules not set in $SYSCTL_CONF" >&2 + return 1 + fi + # Check environment profile exists if [ ! -f "$PROFILE_DIR/clawdie.sh" ]; then echo "ERROR: $PROFILE_DIR/clawdie.sh not created" >&2 diff --git a/installerconfig b/installerconfig index 9292de6..293d34b 100644 --- a/installerconfig +++ b/installerconfig @@ -22,6 +22,19 @@ export ZFSBOOT_POOL_NAME="clawdie" set -e +set_config_line() { + _file="$1" + _assignment="$2" + _name=$(echo "$_assignment" | cut -d= -f1) + mkdir -p "$(dirname "$_file")" + touch "$_file" + if grep -q "^${_name}=" "$_file" 2>/dev/null; then + sed -i '' "s|^${_name}=.*|${_assignment}|" "$_file" + else + echo "$_assignment" >> "$_file" + fi +} + USB_SHARE="/usr/local/share/clawdie-iso" HDD_SHARE="/mnt/usr/local/share/clawdie-iso" HDD_RCD="/mnt/usr/local/etc/rc.d" @@ -49,6 +62,10 @@ mkdir -p "$HDD_RCD" cp "${USB_SHARE}/firstboot/rc.d/clawdie-firstboot" "${HDD_RCD}/clawdie-firstboot" chmod +x "${HDD_RCD}/clawdie-firstboot" +# Enable mac_do framework at first HDD boot with no credential grants yet. +set_config_line /mnt/boot/loader.conf 'mac_do_load="YES"' +set_config_line /mnt/etc/sysctl.conf 'security.mac.do.rules=' + # Enable service in rc.conf on HDD echo 'clawdie_firstboot_enable="YES"' >> /mnt/etc/rc.conf diff --git a/vps/firstboot-vps.sh b/vps/firstboot-vps.sh index a69d33e..0b12a34 100644 --- a/vps/firstboot-vps.sh +++ b/vps/firstboot-vps.sh @@ -167,6 +167,13 @@ EOF # loader.conf cat > /mnt/boot/loader.conf < /mnt/etc/sysctl.conf <