212 lines
5.5 KiB
Bash
212 lines
5.5 KiB
Bash
#!/bin/sh
|
|
set -eu
|
|
|
|
# Create a deploy user, generate an ed25519 keypair, copy keys to /root,
|
|
# update the user's authorized_keys, and (if supported) add an sshd per-user config.
|
|
#
|
|
# Usage:
|
|
# DEPLOY_USER=deploy ./setup_deploy_user.sh
|
|
# ./setup_deploy_user.sh mydeploy
|
|
#
|
|
# Defaults to user "deploy" if not provided via env or argument.
|
|
|
|
info() {
|
|
printf "[INFO] %s\n" "$*"
|
|
}
|
|
|
|
error() {
|
|
printf "[ERROR] %s\n" "$*" 1>&2
|
|
}
|
|
|
|
command_exists() {
|
|
command -v "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
require_root() {
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
error "This script must be run as root"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
sanitize_username() {
|
|
# POSIX-ish username rules (simplified): letters, digits, _ and -
|
|
# must start with a letter or underscore
|
|
case "$1" in
|
|
'' ) return 1 ;;
|
|
*[!A-Za-z0-9_-]* ) return 1 ;;
|
|
[!A-Za-z_]* ) return 1 ;;
|
|
* ) return 0 ;;
|
|
esac
|
|
}
|
|
|
|
get_home_dir() {
|
|
user="$1"
|
|
home="$(getent passwd "$user" 2>/dev/null | awk -F: 'NR==1 {print $6}')"
|
|
if [ -z "$home" ]; then
|
|
home="/home/$user"
|
|
fi
|
|
printf '%s' "$home"
|
|
}
|
|
|
|
create_user_if_needed() {
|
|
user="$1"
|
|
if id "$user" >/dev/null 2>&1; then
|
|
info "User '$user' already exists"
|
|
return 0
|
|
fi
|
|
|
|
if command_exists useradd; then
|
|
info "Creating user '$user' with useradd"
|
|
# -m create home, -U create group, -s shell
|
|
useradd -m -U -s /bin/bash -c "Deployment User" "$user"
|
|
elif command_exists adduser; then
|
|
info "Creating user '$user' with adduser"
|
|
# Non-interactive, no password
|
|
adduser --disabled-password --gecos "Deployment User" "$user"
|
|
else
|
|
error "Neither useradd nor adduser is available"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
backup_if_exists() {
|
|
p="$1"
|
|
if [ -e "$p" ]; then
|
|
ts="$(date +%s)"
|
|
mv "$p" "$p.bak.$ts"
|
|
info "Backed up existing $(basename "$p") to $(basename "$p.bak.$ts")"
|
|
fi
|
|
}
|
|
|
|
write_user_ssh_client_config() {
|
|
user="$1"
|
|
home_dir="$2"
|
|
ssh_dir="$home_dir/.ssh"
|
|
cfg="$ssh_dir/config"
|
|
mkdir -p "$ssh_dir"
|
|
chmod 700 "$ssh_dir"
|
|
# Minimal, safe client config
|
|
if [ ! -f "$cfg" ]; then
|
|
{
|
|
echo "Host *"
|
|
echo " IdentitiesOnly yes"
|
|
echo " PubkeyAuthentication yes"
|
|
} > "$cfg"
|
|
chown "$user":"$user" "$cfg"
|
|
chmod 600 "$cfg"
|
|
info "Wrote SSH client config at $cfg"
|
|
else
|
|
info "SSH client config already present at $cfg"
|
|
fi
|
|
chown "$user":"$user" "$ssh_dir"
|
|
}
|
|
|
|
configure_sshd_dropin_if_supported() {
|
|
user="$1"
|
|
dropin_dir="/etc/ssh/sshd_config.d"
|
|
if [ -d "$dropin_dir" ]; then
|
|
conf="$dropin_dir/99-deploy-user-$user.conf"
|
|
if [ ! -f "$conf" ]; then
|
|
{
|
|
echo "Match User $user"
|
|
echo " PubkeyAuthentication yes"
|
|
echo " PasswordAuthentication no"
|
|
echo " AuthorizedKeysFile %h/.ssh/authorized_keys"
|
|
} > "$conf"
|
|
chmod 644 "$conf"
|
|
info "Created sshd drop-in: $conf"
|
|
else
|
|
info "sshd drop-in already exists: $conf"
|
|
fi
|
|
|
|
if command_exists sshd; then
|
|
if sshd -t >/dev/null 2>&1; then
|
|
if command_exists systemctl; then
|
|
systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true
|
|
else
|
|
service ssh reload 2>/dev/null || service sshd reload 2>/dev/null || true
|
|
fi
|
|
info "Reloaded sshd"
|
|
else
|
|
error "sshd configuration test failed; not reloading"
|
|
fi
|
|
fi
|
|
else
|
|
info "sshd drop-in directory not present; skipping server config"
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
require_root
|
|
|
|
DEPLOY_USER_NAME="${DEPLOY_USER:-${1:-deploy}}"
|
|
|
|
if ! sanitize_username "$DEPLOY_USER_NAME"; then
|
|
error "Invalid username '$DEPLOY_USER_NAME'. Use lowercase letters, digits, '_' or '-', starting with a letter or '_'"
|
|
exit 1
|
|
fi
|
|
|
|
info "Using deploy user: $DEPLOY_USER_NAME"
|
|
create_user_if_needed "$DEPLOY_USER_NAME"
|
|
|
|
HOME_DIR="$(get_home_dir "$DEPLOY_USER_NAME")"
|
|
SSH_DIR="$HOME_DIR/.ssh"
|
|
AUTH_KEYS="$SSH_DIR/authorized_keys"
|
|
|
|
mkdir -p "$SSH_DIR"
|
|
chmod 700 "$SSH_DIR"
|
|
touch "$AUTH_KEYS"
|
|
chmod 600 "$AUTH_KEYS"
|
|
chown -R "$DEPLOY_USER_NAME":"$DEPLOY_USER_NAME" "$SSH_DIR"
|
|
|
|
# Generate keypair in a temp dir
|
|
TMPDIR="$(mktemp -d)"
|
|
trap 'rm -rf "$TMPDIR"' EXIT INT HUP TERM
|
|
KEYBASE="$TMPDIR/id_ed25519"
|
|
COMMENT="deploy key for $DEPLOY_USER_NAME@$(hostname -f 2>/dev/null || hostname)"
|
|
|
|
info "Generating ed25519 keypair"
|
|
if ! command_exists ssh-keygen; then
|
|
error "ssh-keygen not found. Please install OpenSSH client tools."
|
|
exit 1
|
|
fi
|
|
ssh-keygen -t ed25519 -N "" -C "$COMMENT" -f "$KEYBASE" >/dev/null 2>&1
|
|
|
|
# Copy keys to /root as deploy.priv and deploy.pub
|
|
DEST_PRIV="/root/deploy.priv"
|
|
DEST_PUB="/root/deploy.pub"
|
|
backup_if_exists "$DEST_PRIV"
|
|
backup_if_exists "$DEST_PUB"
|
|
cp "$KEYBASE" "$DEST_PRIV"
|
|
cp "$KEYBASE.pub" "$DEST_PUB"
|
|
chown root:root "$DEST_PRIV" "$DEST_PUB"
|
|
chmod 600 "$DEST_PRIV"
|
|
chmod 644 "$DEST_PUB"
|
|
info "Installed private key: $DEST_PRIV"
|
|
info "Installed public key: $DEST_PUB"
|
|
|
|
# Add public key to authorized_keys if not present
|
|
PUB_LINE="$(cat "$KEYBASE.pub")"
|
|
if ! grep -qxF "$PUB_LINE" "$AUTH_KEYS" 2>/dev/null; then
|
|
printf '%s\n' "$PUB_LINE" >> "$AUTH_KEYS"
|
|
chown "$DEPLOY_USER_NAME":"$DEPLOY_USER_NAME" "$AUTH_KEYS"
|
|
chmod 600 "$AUTH_KEYS"
|
|
info "Added public key to $AUTH_KEYS"
|
|
else
|
|
info "Public key already present in $AUTH_KEYS"
|
|
fi
|
|
|
|
# Minimal client-side SSH config for the deploy user
|
|
write_user_ssh_client_config "$DEPLOY_USER_NAME" "$HOME_DIR"
|
|
|
|
# Optional: server-side sshd drop-in configuration
|
|
configure_sshd_dropin_if_supported "$DEPLOY_USER_NAME"
|
|
|
|
printf "\nDone. You can distribute the public key at %s.\n" "$DEST_PUB"
|
|
}
|
|
|
|
main "$@"
|
|
|
|
|