#!/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 " >&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 " >&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 < $cfg_path TrustedUserCAKeys $SSH_USER_CA HostKey $SSH_HOST_KEY HostCertificate $SSH_HOST_CERT EOF } if [[ ! -e $cfg_path ]]; then prompt_user "sshd config" "Do you want to configure sshd?" if [[ $REPLY =~ ^[Yy]$ ]]; then install_cert_config update_prompt $GREEN_CHECK "Configured sshd" fi fi restart_sshd echo } restart_sshd() { if ! systemctl is-active --quiet sshd; then prompt_user "sshd.service" "sshd.service is not active. Restart?" if [[ $REPLY =~ ^[Yy]$ ]]; then systemctl restart sshd local sshd_pid=$(systemctl show --property MainPID --value sshd) update_prompt $GREEN_CHECK "Restarted sshd.service on PID: $sshd_pid" fi else local sshd_pid=$(systemctl show --property MainPID --value sshd) echo -e "$GREEN_CHECK sshd.service is active on PID: $sshd_pid" 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"