Files
gniza4cp/lib/ssh.sh
shuki 1f68ea1058 Security hardening, static analysis fixes, and expanded test coverage
- Fix CRITICAL: safe config parser replacing shell source, sshpass -e,
  CSRF with /dev/urandom, symlink-safe file I/O
- Fix HIGH: input validation for timestamps/accounts, path traversal
  prevention in Runner.pm, AJAX CSRF on all endpoints
- Fix MEDIUM: umask 077, chmod 700 on config dirs, Config.pm TOCTOU lock,
  rsync exit code capture bug, RSYNC_EXTRA_OPTS character validation
- ShellCheck: fix word-splitting in notify.sh, safe rm in pkgacct.sh,
  suppress cross-file SC2034 false positives
- Perl::Critic: return undef→bare return, return (sort), unpack @_,
  explicit return on void subs, rename Config::write→save
- Remove dead code: enforce_retention_all(), rsync_dry_run()
- Add require_cmd checks for rsync/ssh/hostname/gzip at startup
- Escape $hint/$tip in CGI helper functions for defense-in-depth
- Expand tests from 17→40: validate_timestamp, validate_account_name,
  _safe_source_config (including malicious input), numeric validation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:57:26 +02:00

78 lines
2.1 KiB
Bash

#!/usr/bin/env bash
# gniza/lib/ssh.sh — SSH connectivity, remote exec, ssh_opts builder
_is_password_mode() {
[[ "${REMOTE_AUTH_METHOD:-key}" == "password" ]]
}
build_ssh_opts() {
local opts=()
opts+=(-n)
if _is_password_mode; then
opts+=(-o "StrictHostKeyChecking=yes")
else
opts+=(-i "$REMOTE_KEY")
opts+=(-o "StrictHostKeyChecking=yes")
opts+=(-o "BatchMode=yes")
fi
opts+=(-p "$REMOTE_PORT")
opts+=(-o "ConnectTimeout=$SSH_TIMEOUT")
opts+=(-o "ServerAliveInterval=60")
opts+=(-o "ServerAliveCountMax=3")
echo "${opts[*]}"
}
build_ssh_cmd() {
if _is_password_mode; then
echo "sshpass -e ssh $(build_ssh_opts)"
else
echo "ssh $(build_ssh_opts)"
fi
}
remote_exec() {
local cmd="$1"
local ssh_opts; ssh_opts=$(build_ssh_opts)
if _is_password_mode; then
log_debug "CMD: sshpass -e ssh $ssh_opts ${REMOTE_USER}@${REMOTE_HOST} '<cmd>'"
export SSHPASS="$REMOTE_PASSWORD"
# shellcheck disable=SC2086
sshpass -e ssh $ssh_opts "${REMOTE_USER}@${REMOTE_HOST}" "$cmd"
else
log_debug "CMD: ssh $ssh_opts ${REMOTE_USER}@${REMOTE_HOST} '$cmd'"
# shellcheck disable=SC2086
ssh $ssh_opts "${REMOTE_USER}@${REMOTE_HOST}" "$cmd"
fi
}
remote_exec_quiet() {
remote_exec "$1" 2>/dev/null
}
test_ssh_connection() {
log_info "Testing SSH connection to ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PORT}..."
if remote_exec "echo ok" &>/dev/null; then
log_info "SSH connection successful"
return 0
else
log_error "SSH connection failed to ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PORT}"
return 1
fi
}
ensure_remote_dir() {
local dir="$1"
remote_exec "mkdir -p '$dir'" || {
log_error "Failed to create remote directory: $dir"
return 1
}
}
build_rsync_ssh_cmd() {
if _is_password_mode; then
echo "ssh -p $REMOTE_PORT -o StrictHostKeyChecking=yes -o ConnectTimeout=$SSH_TIMEOUT"
else
echo "ssh -i $REMOTE_KEY -p $REMOTE_PORT -o StrictHostKeyChecking=yes -o BatchMode=yes -o ConnectTimeout=$SSH_TIMEOUT"
fi
}