diff --git a/scripts/start-container b/scripts/start-container index b00870f..a921e93 100644 --- a/scripts/start-container +++ b/scripts/start-container @@ -4,6 +4,12 @@ set -eu echo "==========================================" echo " docker-bastion starting" echo "==========================================" + +# Trust every git repo regardless of UID — the bastion is a single-tenant +# isolated container, and ownership-mismatch on a bind-mounted repo +# (host uid != bastion uid) is the normal case here, not an attack vector. +# Without this, git refuses with "fatal: detected dubious ownership". +git config --system --add safe.directory '*' 2>/dev/null || true echo "Date: $(date)" echo "Hostname: $(hostname)" echo "OpenSSH: $(/usr/sbin/sshd -V 2>&1 | head -1 || echo n/a)" @@ -87,7 +93,12 @@ if [ "$file_keys" -eq 0 ] && [ "$dir_keys" -eq 0 ]; then echo " until then every SSH attempt will fail with 'publickey denied'." fi -chown -R "${SSH_USER}:${SSH_USER}" "$(dirname "$AUTH_FILE")" +# chown -R is best-effort: if the caller bind-mounts read-only files into +# /home/agent/.ssh/ (e.g. an id_rsa for git push from FORCE_COMMAND), chown +# on those will fail with "Read-only file system" — which under `set -e` +# previously killed the boot script. The dir itself + the file we just +# wrote are what matter; everything else is the caller's business. +chown -R "${SSH_USER}:${SSH_USER}" "$(dirname "$AUTH_FILE")" 2>/dev/null || true chmod 700 "$(dirname "$AUTH_FILE")" chmod 600 "$AUTH_FILE" @@ -117,6 +128,15 @@ cat > /etc/bastion/force-command <<'WRAPPER' # right place. sshd sets this already; busybox httpd's CGI doesn't, so # without this fix `git push` complains about /root/.config/git/* perms. export HOME=/home/agent + +# If a deploy SSH key is mounted at the conventional location, point git +# at it explicitly. Setting HOME alone isn't enough when the CGI runs as +# root (HTTP_AS_ROOT=1) — ssh ignores HOME-based ~/.ssh/ lookup in that +# path and silently uses /root/.ssh/, which is empty. +if [ -f /home/agent/.ssh/id_rsa ]; then + export GIT_SSH_COMMAND="ssh -o IdentityFile=/home/agent/.ssh/id_rsa -o UserKnownHostsFile=/home/agent/.ssh/known_hosts -o StrictHostKeyChecking=accept-new" +fi + exec sh -c "$(cat /etc/bastion/force-command.cmd)" WRAPPER chmod 0755 /etc/bastion/force-command @@ -196,7 +216,21 @@ CGI chmod 0755 /var/www/cgi-bin/run # -c CONFFILE = auth + content-type rules; httpd reads it as root before # dropping to -u USER. CGI scripts then run as USER. - httpd -f -p "${HTTP_PORT}" -h /var/www -u "${SSH_USER}" -c /etc/bastion/httpd.conf & + # + # Set HTTP_AS_ROOT=1 to skip the -u drop, so httpd (and the CGI it + # spawns) run as root. Use this when FORCE_COMMAND is a deploy-style + # script that needs full filesystem write + docker-socket access + + # arbitrary chown — busybox httpd's `-u USER` drop does setuid/setgid + # but not setgroups, so supplementary groups (e.g. dockerhost for the + # mounted /var/run/docker.sock) don't reach the CGI even when the user + # is a member on paper. Bastion already has socket = host root, so + # this doesn't enlarge the trust envelope. + if [ "${HTTP_AS_ROOT:-0}" = "1" ]; then + echo " HTTP_AS_ROOT=1 — httpd + CGI run as root" + httpd -f -p "${HTTP_PORT}" -h /var/www -c /etc/bastion/httpd.conf & + else + httpd -f -p "${HTTP_PORT}" -h /var/www -u "${SSH_USER}" -c /etc/bastion/httpd.conf & + fi HTTP_PID=$! echo " httpd PID ${HTTP_PID}, endpoint: /cgi-bin/run (basic auth)" @@ -217,7 +251,12 @@ printf 'Content-Type: text/plain\r\nCache-Control: no-cache\r\nX-Accel-Buffering exec /etc/bastion/force-command 2>&1 CGI chmod 0755 /var/www/cgi-bin/run - httpd -f -p "${HTTP_PORT}" -h /var/www -u "${SSH_USER}" & + if [ "${HTTP_AS_ROOT:-0}" = "1" ]; then + echo " HTTP_AS_ROOT=1 — httpd + CGI run as root" + httpd -f -p "${HTTP_PORT}" -h /var/www & + else + httpd -f -p "${HTTP_PORT}" -h /var/www -u "${SSH_USER}" & + fi HTTP_PID=$! echo " httpd PID ${HTTP_PID}, endpoint: /cgi-bin/run (bearer token)"