239 lines
5.7 KiB
Bash
Executable File
239 lines
5.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
#
|
|
# Env vars
|
|
#
|
|
|
|
# SSH config paths
|
|
SSH_CFG_DIR="/etc/ssh"
|
|
SSH_USER_CA="$SSH_CFG_DIR/ssh_user_ca.pub"
|
|
SSH_HOST_KEY="$SSH_CFG_DIR/ssh_host_ed25519_key"
|
|
SSH_HOST_PUBLIC_KEY="$SSH_HOST_KEY.pub"
|
|
SSH_HOST_CERT="$SSH_HOST_KEY-cert.pub"
|
|
|
|
GREEN_CHECK="\e[32m✔\e[0m"
|
|
RED_X="\e[31m✗\e[0m"
|
|
YELLOW_BANG="\e[33m!\e[0m"
|
|
|
|
CREATE_USER_CA=0
|
|
CREATE_HOST_CERT=0
|
|
|
|
#
|
|
# Function Definition
|
|
#
|
|
|
|
# This test loads the sshd config to see what values actually get parsed.
|
|
ssh_config_val() {
|
|
local field="$1"
|
|
local val
|
|
|
|
if [[ -z "$field" ]]; then
|
|
echo "usage: ssh_config_val <config name>" >&2
|
|
return 2
|
|
fi
|
|
|
|
echo $(sshd -T 2>/dev/null | grep -i "^$field " | head -1 | awk '{print $2}')
|
|
}
|
|
|
|
title_msg() {
|
|
local title="\e[1m${1:-Title}:\e[0m"
|
|
local prompt="${2:-Prompt for the user}"
|
|
printf "%b %b" "$title" "$prompt"
|
|
}
|
|
|
|
prompt_user() {
|
|
full_prompt_msg="$(title_msg "${1}" "${2}")"
|
|
echo -n -e "$YELLOW_BANG $full_prompt_msg"
|
|
read -p " (y/n) " -n 1 -r
|
|
}
|
|
|
|
update_prompt() {
|
|
local icon="$1"
|
|
case $# in
|
|
1) msg="$full_prompt_msg $REPLY";;
|
|
2) msg="$2";;
|
|
3) msg="$(title_msg "${2}" "${3}")";;
|
|
*) msg="Too many arguments";;
|
|
esac
|
|
|
|
echo -en "\r\e[K"
|
|
echo -e "$icon $msg"
|
|
}
|
|
|
|
auto_update_prompt() {
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
update_prompt $GREEN_CHECK
|
|
elif [[ $REPLY =~ ^[Nn]$ ]]; then
|
|
update_prompt $RED_X
|
|
fi
|
|
}
|
|
|
|
sign_host_cert() {
|
|
local if="eth0"
|
|
local IP_ADDRESS=$(ip -4 addr show dev $if | awk '/inet /{print $2}' | cut -d/ -f1) && \
|
|
local hostname=$(hostname -s) && \
|
|
step ssh certificate --host --sign \
|
|
--principal "$hostname" \
|
|
--principal "$hostname.john-stream.com" \
|
|
--principal "$IP_ADDRESS" \
|
|
--provisioner admin \
|
|
"$hostname" "$SSH_HOST_PUBLIC_KEY"
|
|
}
|
|
|
|
|
|
check_ssh_config_files() {
|
|
row_success() {
|
|
local key="$1"
|
|
local path="$2"
|
|
local perms=$(stat -c '%a' "$path")
|
|
printf "%-17b %-20s %-6s %s\n" " $GREEN_CHECK" "$key" "$perms" "$path"
|
|
}
|
|
|
|
row_missing() {
|
|
local key="$1"
|
|
local path="$2"
|
|
printf "%-15b %-20s %-6s %s\n" " $YELLOW_BANG" "$key" "-" "$path (missing)"
|
|
}
|
|
|
|
row_unconfigured() {
|
|
local key="$1"
|
|
printf "%-17b %-20s %-6s %s\n" " $RED_X" "$key" "-" "(not configured)"
|
|
}
|
|
|
|
get_key_status() {
|
|
local path="$1"
|
|
if [[ -z "$path" ]]; then
|
|
echo "unconfigured"
|
|
elif [[ ! -e "$path" ]]; then
|
|
echo "missing"
|
|
else
|
|
echo "success"
|
|
fi
|
|
}
|
|
|
|
row_process() {
|
|
local key="$1"
|
|
path=$(ssh_config_val "$key")
|
|
status=$(get_key_status "$path")
|
|
case "$status" in
|
|
success) row_success "$key" "$path" ;;
|
|
missing) row_missing "$key" "$path" ;;
|
|
unconfigured) row_unconfigured "$key" ;;
|
|
esac
|
|
}
|
|
|
|
printf "%-6s %-20s %-6s %s\n" "STATUS" "KEY" "PERMS" "PATH"
|
|
|
|
row_process "hostkey"
|
|
row_process "hostcertificate"
|
|
case "$status" in
|
|
missing) CREATE_HOST_CERT=1;;
|
|
esac
|
|
row_process "trustedusercakeys"
|
|
case "$status" in
|
|
missing) CREATE_USER_CA=1;;
|
|
esac
|
|
echo
|
|
}
|
|
|
|
ssh_fingerprint() {
|
|
local field="$1"
|
|
|
|
if [[ -z "$field" ]]; then
|
|
echo "usage: ssh_fingerprint <field>" >&2
|
|
return 2
|
|
fi
|
|
|
|
local cfg_path=$(ssh_config_val $field)
|
|
|
|
if [[ -z "$cfg_path" ]]; then
|
|
echo "error: sshd field '$field' not found or empty" >&2
|
|
return 1
|
|
fi
|
|
|
|
if [[ ! -r "$cfg_path" ]]; then
|
|
echo "error: file not readable: $cfg_path" >&2
|
|
return 1
|
|
fi
|
|
|
|
ssh-keygen -lf "$cfg_path" | awk '{ print $2 }'
|
|
}
|
|
|
|
check_cert_config() {
|
|
local base_dir="/etc/ssh/sshd_config.d"
|
|
local cfg_path="$base_dir/${1:-certs.conf}"
|
|
|
|
install_cert_config() {
|
|
mkdir -p $(dirname $cfg_path)
|
|
cat <<EOF > $cfg_path
|
|
TrustedUserCAKeys $SSH_USER_CA
|
|
HostKey $SSH_HOST_KEY
|
|
HostCertificate $SSH_HOST_CERT
|
|
EOF
|
|
}
|
|
|
|
if [[ ! -e $cfg_path ]]; then
|
|
prompt_user "sshd" "Currently unconfigured for certs. Do you want to configure?"
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
install_cert_config
|
|
update_prompt $GREEN_CHECK "sshd" "Configured to use and accept certs"
|
|
fi
|
|
fi
|
|
|
|
restart_sshd
|
|
echo
|
|
}
|
|
|
|
restart_sshd() {
|
|
echo -en "$YELLOW_BANG Restarting sshd..."
|
|
systemctl restart sshd
|
|
if [[ $? -eq 0 ]]; then
|
|
local sshd_pid=$(systemctl show --property MainPID --value sshd)
|
|
update_prompt $GREEN_CHECK "sshd" "Restarted sshd.service on PID: $sshd_pid"
|
|
else
|
|
update_prompt $RED_X "sshd" "Failed to restart sshd.service"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
create_files() {
|
|
local wrote_lines=0
|
|
|
|
if [[ $CREATE_HOST_CERT -eq 1 ]]; then
|
|
wrote_lines=1
|
|
prompt_user "SSH Host" "Cert missing. Sign the ssh host cert?"
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
update_prompt $YELLOW_BANG "Signing ssh host cert"
|
|
sign_host_cert
|
|
else
|
|
update_prompt $RED_X
|
|
fi
|
|
fi
|
|
|
|
if [[ $CREATE_USER_CA -eq 1 ]]; then
|
|
wrote_lines=1
|
|
prompt_user "SSH Host" "Create the trusted keys file?"
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
(step ssh config --roots > "$path")
|
|
update_prompt $GREEN_CHECK "SSH Host" "Created the trusted keys file for the SSH host."
|
|
else
|
|
update_prompt $RED_X
|
|
fi
|
|
fi
|
|
|
|
if [[ $wrote_lines -eq 1 ]]; then echo; fi
|
|
}
|
|
|
|
|
|
# Run Process
|
|
|
|
check_cert_config "certs.conf"
|
|
check_ssh_config_files
|
|
create_files
|
|
|
|
title_msg "SSH Host Cert" "$SSH_HOST_CERT\n"
|
|
CERT_INFO=$(ssh-keygen -Lf "$SSH_HOST_CERT")
|
|
echo -e "$CERT_INFO" | grep "Public key"
|
|
echo -e "$CERT_INFO" | grep "Valid"
|
|
echo -e "$CERT_INFO" | grep -A3 "Principals"
|