From 0ea58b195d2c740dfbe66cb4c8289c7d7758cb99 Mon Sep 17 00:00:00 2001 From: klein panic Date: Mon, 3 Feb 2025 19:46:21 -0500 Subject: [PATCH] initial commit --- .gitignore | 2 + chroot_manager.1 | 50 ++++ chroot_manager.bash_completion | 21 ++ chroot_manager.sh | 494 +++++++++++++++++++++++++++++++++ 4 files changed, 567 insertions(+) create mode 100644 .gitignore create mode 100644 chroot_manager.1 create mode 100644 chroot_manager.bash_completion create mode 100755 chroot_manager.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df4858d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +backups/ +chroot_daemon_logs/ diff --git a/chroot_manager.1 b/chroot_manager.1 new file mode 100644 index 0000000..ba7c920 --- /dev/null +++ b/chroot_manager.1 @@ -0,0 +1,50 @@ +.\" Manpage for chroot_manager +.TH CHROOT_MANAGER 1 "February 2025" "chroot_manager 1.0" "User Commands" +.SH NAME +chroot_manager \- chroot management tool with daemon logging support +.SH SYNOPSIS +.B chroot_manager +[--verbose] [--daemon] +.SH DESCRIPTION +The +.B chroot_manager +script provides commands to create, connect to, disconnect from, and manage a chroot jail. +.PP +It also supports a daemon mode (using --daemon) where the chroot session is run under +strace to log all system calls from the chroot environment and its child processes. +.PP +Available commands: +.TP +.B create +Creates a chroot jail using debootstrap. +.TP +.B connect +Mounts required filesystems, sets up X access, and enters the chroot. +With --daemon, the session is traced via strace. +.TP +.B disconnect +Unmounts the filesystems from the chroot. +.TP +.B status +Shows the mount status for the chroot jail. +.TP +.B install +Installs chroot_manager to /usr/local/bin along with its man page and shell completions. +.TP +.B uninstall +Removes the installed chroot_manager, its man page, and shell completions. +.TP +.B help +Displays this help message. +.SH OPTIONS +.TP +.B --verbose +Enables verbose output. +.TP +.B --daemon +Enables daemon mode for the connect command. +.SH AUTHOR +Written by Your Name. +.SH COPYRIGHT +This is free software; see the source for copying conditions. There is NO warranty. + diff --git a/chroot_manager.bash_completion b/chroot_manager.bash_completion new file mode 100644 index 0000000..3e83f3a --- /dev/null +++ b/chroot_manager.bash_completion @@ -0,0 +1,21 @@ +#!/bin/bash +# Bash completion for chroot_manager + +_chroot_manager_completion() { + local cur prev opts cmds + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + cmds="create connect disconnect status install uninstall help" + opts="--verbose --daemon --help -h" + + if [[ ${COMP_CWORD} == 1 ]]; then + COMPREPLY=( $(compgen -W "${cmds} ${opts}" -- ${cur}) ) + else + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + fi +} + +complete -F _chroot_manager_completion chroot_manager + diff --git a/chroot_manager.sh b/chroot_manager.sh new file mode 100755 index 0000000..86cea0e --- /dev/null +++ b/chroot_manager.sh @@ -0,0 +1,494 @@ +#!/bin/bash +# chroot_manager.sh +# +# A fully featured chroot management tool with: +# - A command-line interface (create, connect, disconnect, status, help, install, uninstall) +# - Improved error handling and logging +# - A verbose/debug mode (--verbose) +# - A daemon mode (--daemon) which, when used with connect, will run +# the chroot session under strace to log all activity (system calls) in separate files. +# +# Requirements: +# - Some commands (create, connect, disconnect, install, uninstall) must be run as root. +# +# Usage: +# sudo ./chroot_manager.sh [--verbose] [--daemon] +# +# Commands: +# create Create the chroot jail using debootstrap. +# connect Mount necessary filesystems, set up X access, and enter the chroot. +# With --daemon, the session is run under strace to log all activity. +# disconnect Unmount the filesystems from the chroot (if needed). +# status Display the current mount status for the chroot jail. +# help Display a detailed help message. +# install Install chroot_manager to /usr/local/bin (and install man page and shell completions). +# uninstall Uninstall chroot_manager and remove its man page and shell completions. +# +# Note: +# By default, the script assumes the chroot jail is located at /var/chroot. +# When using --daemon, the log files will be stored in a directory created in the +# current working directory (default: "chroot_daemon_logs"). After the chroot session ends, +# the script will attempt to rename each log file based on the traced program’s name (if available) +# and ignore logs for trivial commands. Finally, the script will adjust ownership and permissions +# on that directory and its contents so they are accessible to the invoking user. +# + +# --- Global Variables and Defaults --- +CHROOT_DIR="/var/chroot" +DEBIAN_MIRROR="http://deb.debian.org/debian" +LOGFILE="/var/log/chroot_manager.log" +VERBOSE=0 +DAEMON=0 +# Default directory to store daemon logs +DAEMON_LOG_DIR="$(pwd)/chroot_daemon_logs" + +# List of trivial commands to ignore in daemon logs (basename only) +IGNORE_LIST=(bash sh ls cat echo grep mount umount) + +# --- Logging Functions --- +function log_info() { + echo "[INFO] $(date +'%F %T') $*" | tee -a "$LOGFILE" +} + +function log_debug() { + if [ "$VERBOSE" -eq 1 ]; then + echo "[DEBUG] $(date +'%F %T') $*" | tee -a "$LOGFILE" + else + echo "[DEBUG] $(date +'%F %T') $*" >> "$LOGFILE" + fi +} + +function log_error() { + echo "[ERROR] $(date +'%F %T') $*" | tee -a "$LOGFILE" >&2 +} + +# --- Extended Help Message --- +function help_message() { + cat < + +Commands: + create + Create the chroot jail using debootstrap. + Sets up a minimal Debian environment in \$CHROOT_DIR. + + connect + Mount necessary filesystems (dev, proc, sys, tmp) into the chroot, + set up X access, and then enter the chroot environment. + When the chroot session ends, the bind mounts are automatically cleaned up. + With the --daemon flag, the session is launched under strace so that every system call + from the chroot process and its children is logged. The logs are stored in a directory: + \$DAEMON_LOG_DIR + Each log file is post-processed to try to include the traced program's name (if found) + and trivial commands are ignored. Finally, the log directory and its files will have their + ownership and permissions changed so the invoking user (from \$SUDO_USER) can read and write them. + + disconnect + Unmount the filesystems from the chroot. + Checks if bind mounts exist for \$CHROOT_DIR and, if so, unmounts them. + + status + Displays the current mount status for the chroot jail (\$CHROOT_DIR). + + install + Installs chroot_manager to /usr/local/bin, and copies the man page and bash completion file + (if found in the current directory) to the appropriate locations. + + uninstall + Uninstalls chroot_manager from /usr/local/bin and removes the installed man page and bash completion file. + + help + Display this detailed help message. + +Options: + --verbose + Enable verbose/debug mode (more detailed logging to console and log file). + + --daemon + When used with the 'connect' command, run the chroot session under strace in daemon mode, + logging all system call activity in separate log files. + +Notes: + - Commands that modify the chroot environment (create, connect, disconnect, install, uninstall) + must be run as root. + - By default, the chroot jail is located at: \$CHROOT_DIR. +EOF +} + +# --- Usage (Short) --- +function usage() { + cat < +Try '$0 help' for more information. +EOF +} + +# --- Dependency Check --- +REQUIRED_CMDS=(debootstrap chroot mount xhost xauth sudo strace) +function check_dependencies() { + local missing=0 + for cmd in "${REQUIRED_CMDS[@]}"; do + if ! command -v "$cmd" &>/dev/null; then + log_error "Required command '$cmd' is not installed." + missing=1 + fi + done + if [ $missing -ne 0 ]; then + exit 1 + fi +} + +# --- Check for Chroot Jail Existence --- +function check_jail() { + if [ ! -d "$CHROOT_DIR" ]; then + log_error "Chroot jail '$CHROOT_DIR' does not exist." + echo "Please create the chroot environment first using the 'create' command." + exit 1 + fi +} + +# --- Mount / Unmount Filesystems --- +function mount_filesystems() { + local dirs=(dev proc sys tmp) + for d in "${dirs[@]}"; do + log_info "Mounting /$d to ${CHROOT_DIR}/$d..." + if ! mountpoint -q "${CHROOT_DIR}/$d"; then + if ! sudo mount --bind "/$d" "${CHROOT_DIR}/$d"; then + log_error "Error mounting /$d" + exit 1 + fi + else + log_debug "${CHROOT_DIR}/$d is already mounted." + fi + done +} + +function unmount_filesystems() { + local dirs=(dev proc sys tmp) + local any_mounted=0 + for d in "${dirs[@]}"; do + if mountpoint -q "${CHROOT_DIR}/$d"; then + any_mounted=1 + log_info "Unmounting ${CHROOT_DIR}/$d..." + if ! sudo umount "${CHROOT_DIR}/$d"; then + log_error "Error unmounting ${CHROOT_DIR}/$d" + fi + else + log_debug "${CHROOT_DIR}/$d is not mounted." + fi + done + return $any_mounted +} + +# --- Post-Process Daemon Logs --- +function post_process_daemon_logs() { + log_info "Post-processing daemon logs in directory: $DAEMON_LOG_DIR" + # Iterate over log files matching the pattern (they are created with suffix .) + for logfile in "$DAEMON_LOG_DIR"/chroot_daemon.log.*; do + [ -e "$logfile" ] || continue + # Extract the PID from the filename: assume filename ends with . + pid="${logfile##*.}" + # Try to extract the first execve call to determine the program name. + prog_full=$(grep -m 1 'execve(' "$logfile" | sed -E 's/.*execve\("([^"]+)".*/\1/') + if [ -n "$prog_full" ]; then + prog_name=$(basename "$prog_full") + else + prog_name="pid${pid}" + fi + + # Check if the program is in the ignore list (compare only the basename) + ignore=0 + for trivial in "${IGNORE_LIST[@]}"; do + if [ "$prog_name" == "$trivial" ]; then + ignore=1 + break + fi + done + + if [ "$ignore" -eq 1 ]; then + log_debug "Ignoring trivial log for program '$prog_name' (file: $logfile). Removing." + rm -f "$logfile" + else + newname="$DAEMON_LOG_DIR/${prog_name}_${pid}.log" + log_info "Renaming log file '$logfile' to '$newname'" + mv "$logfile" "$newname" + fi + done + + # Adjust ownership and permissions of the daemon log directory and its contents. + if [ -n "$SUDO_USER" ]; then + log_info "Changing ownership of $DAEMON_LOG_DIR to user $SUDO_USER" + sudo chown -R "$SUDO_USER":"$SUDO_USER" "$DAEMON_LOG_DIR" + fi + log_info "Setting permissions on $DAEMON_LOG_DIR and its files." + # Directories: rwxr-xr-x; Files: rw-r--r-- + find "$DAEMON_LOG_DIR" -type d -exec chmod 0755 {} \; + find "$DAEMON_LOG_DIR" -type f -exec chmod 0644 {} \; +} + +# --- Cleanup Routine (for connect) --- +function cleanup() { + log_info "Cleaning up: Unmounting filesystems..." + unmount_filesystems + log_info "Cleanup complete." +} + +# --- Command: create --- +function create() { + # Require root + if [ "$EUID" -ne 0 ]; then + echo "Error: The 'create' command must be run as root (use sudo)." >&2 + exit 1 + fi + + check_dependencies + if [ -d "$CHROOT_DIR" ]; then + log_error "Chroot jail already exists at $CHROOT_DIR." + echo "Use 'connect' to enter it, or 'disconnect' if mounts remain." + exit 1 + fi + + log_info "Creating chroot jail at $CHROOT_DIR using debootstrap..." + if ! sudo mkdir -p "$CHROOT_DIR"; then + log_error "Failed to create directory $CHROOT_DIR." + exit 1 + fi + + if sudo debootstrap stable "$CHROOT_DIR" "$DEBIAN_MIRROR"; then + log_info "Chroot jail successfully created." + else + log_error "debootstrap failed. Check your network and settings." + exit 1 + fi +} + +# --- Command: connect --- +function connect() { + # Require root + if [ "$EUID" -ne 0 ]; then + echo "Error: The 'connect' command must be run as root (use sudo)." >&2 + exit 1 + fi + + check_dependencies + check_jail + + # Set trap for cleanup when connect ends. + trap cleanup EXIT SIGINT SIGTERM SIGHUP + + # Mount the required filesystems + mount_filesystems + + # Allow all connections to the X server + log_info "Running 'xhost +' to allow X connections..." + if ! xhost +; then + log_error "Error running 'xhost +'." + exit 1 + fi + + # Get the current X authentication keys + log_info "Retrieving X authentication keys with 'xauth list'..." + local xauth_keys + xauth_keys=$(xauth list) + if [ -z "$xauth_keys" ]; then + log_error "Warning: 'xauth list' returned no output." + else + if command -v xclip &>/dev/null; then + echo "$xauth_keys" | xclip -selection clipboard + log_info "X authentication keys have been copied to your clipboard." + else + log_info "xclip not found. Here are your X authentication keys:" + echo "$xauth_keys" + fi + fi + + echo "" + echo "------------------------------" + echo "Now entering the chroot environment." + echo "Inside the chroot, add the X authentication key by running:" + echo " xauth add " + echo "For example, if your clipboard contains:" + echo " $(echo "$xauth_keys" | head -n 1)" + echo "then run:" + echo " xauth add $(echo "$xauth_keys" | head -n 1)" + echo "------------------------------" + echo "Press Enter to continue..." + read -r + + log_info "Entering chroot at $CHROOT_DIR..." + if [ "$DAEMON" -eq 1 ]; then + # Create the daemon log directory if it doesn't exist. + if [ ! -d "$DAEMON_LOG_DIR" ]; then + mkdir -p "$DAEMON_LOG_DIR" || { + log_error "Failed to create daemon log directory: $DAEMON_LOG_DIR" + exit 1 + } + fi + log_info "Daemon mode enabled. Monitoring chroot session with strace." + # Run chroot under strace so that all system calls (and those of forked children) + # are logged to files with prefix "$DAEMON_LOG_DIR/chroot_daemon.log" + strace -ff -tt -o "$DAEMON_LOG_DIR/chroot_daemon.log" sudo chroot "$CHROOT_DIR" + # Post-process the generated logs + post_process_daemon_logs + else + sudo chroot "$CHROOT_DIR" + fi + log_info "Chroot session ended." + # When the chroot session ends, cleanup is automatically triggered by trap. +} + +# --- Command: disconnect --- +function disconnect() { + # Require root + if [ "$EUID" -ne 0 ]; then + echo "Error: The 'disconnect' command must be run as root (use sudo)." >&2 + exit 1 + fi + + check_jail + + # Check if any bind mounts exist. + if ! mount | grep -q "$CHROOT_DIR"; then + log_info "Chroot environment appears to be clean; no mounts found at $CHROOT_DIR." + exit 0 + fi + + log_info "Running disconnect: unmounting chroot filesystems..." + unmount_filesystems + + # Revoke X server permissions. + if xhost -; then + log_info "X server access has been revoked." + else + log_error "Warning: failed to revoke X server permissions with 'xhost -'." + fi +} + +# --- Command: status --- +function status() { + echo "Mount status for chroot jail ($CHROOT_DIR):" + mount | grep "$CHROOT_DIR" || echo "No mounts found for $CHROOT_DIR." +} + +# --- Command: install --- +function install_program() { + # Require root. + if [ "$EUID" -ne 0 ]; then + echo "Error: The 'install' command must be run as root (use sudo)." >&2 + exit 1 + fi + + # Resolve the full path of the current script. + script_path=$(readlink -f "$0") + log_info "Installing chroot_manager from $script_path to /usr/local/bin/chroot_manager..." + cp "$script_path" /usr/local/bin/chroot_manager || { log_error "Failed to copy script."; exit 1; } + chmod 0755 /usr/local/bin/chroot_manager + + # Install the man page if available. + if [ -e "chroot_manager.1" ]; then + log_info "Installing man page..." + mkdir -p /usr/local/share/man/man1 + cp chroot_manager.1 /usr/local/share/man/man1/chroot_manager.1 || { log_error "Failed to copy man page."; } + gzip -f /usr/local/share/man/man1/chroot_manager.1 + else + log_debug "No man page (chroot_manager.1) found in the current directory." + fi + + # Install bash completions if available. + if [ -e "chroot_manager.bash_completion" ]; then + log_info "Installing bash completion..." + cp chroot_manager.bash_completion /etc/bash_completion.d/chroot_manager || { log_error "Failed to install bash completion."; } + else + log_debug "No bash completion file found in the current directory." + fi + + log_info "Installation complete." +} + +# --- Command: uninstall --- +function uninstall_program() { + # Require root. + if [ "$EUID" -ne 0 ]; then + echo "Error: The 'uninstall' command must be run as root (use sudo)." >&2 + exit 1 + fi + + log_info "Uninstalling chroot_manager from /usr/local/bin..." + rm -f /usr/local/bin/chroot_manager + + log_info "Removing man page..." + rm -f /usr/local/share/man/man1/chroot_manager.1.gz + + log_info "Removing bash completion..." + rm -f /etc/bash_completion.d/chroot_manager + + log_info "Uninstallation complete." +} + +# --- Main CLI Processing --- +if [ $# -eq 0 ]; then + usage + exit 1 +fi + +# Process options. +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) + help_message + exit 0 + ;; + --verbose) + VERBOSE=1 + shift + ;; + --daemon) + DAEMON=1 + shift + ;; + create|connect|disconnect|status|install|uninstall|help) + COMMAND="$1" + shift + break + ;; + *) + echo "Unknown option or command: $1" + usage + exit 1 + ;; + esac +done + +# Dispatch command. +case "$COMMAND" in + create) + create + ;; + connect) + connect + ;; + disconnect) + disconnect + ;; + status) + status + ;; + install) + install_program + ;; + uninstall) + uninstall_program + ;; + help) + help_message + ;; + *) + echo "Invalid command." + usage + exit 1 + ;; +esac +