Compare commits

...

20 Commits

Author SHA1 Message Date
John Lancaster
42b7ec401a readme update 2026-01-04 19:50:29 -06:00
John Lancaster
d248af25a0 error message for cert renewal 2026-01-04 19:17:10 -06:00
John Lancaster
fd8ab31779 started ssh-client script 2026-01-04 18:33:17 -06:00
John Lancaster
c020c481cf polish 2026-01-04 16:50:46 -06:00
John Lancaster
fe0c66d57a pruned 2026-01-04 16:45:09 -06:00
John Lancaster
b08ef16aec added NEEDS_RESTART 2026-01-04 16:44:12 -06:00
John Lancaster
b6fd58a7ca more polish 2026-01-04 13:30:22 -06:00
John Lancaster
d30123dc4a polishing 2026-01-04 13:20:29 -06:00
John Lancaster
cff9d8d3a9 broke out create_files 2026-01-04 13:12:31 -06:00
John Lancaster
fc7af4a1f1 much better prompts 2026-01-04 13:01:06 -06:00
John Lancaster
623c8ef437 added quickstart script to readme 2026-01-04 12:00:35 -06:00
John Lancaster
b18c0f3400 signing host cert 2026-01-04 11:57:06 -06:00
John Lancaster
7f634bf0ba more prompts 2026-01-04 11:48:01 -06:00
John Lancaster
fce8a539d4 prompts wip 2026-01-04 11:33:36 -06:00
John Lancaster
04d52d0017 updates 2026-01-04 11:22:01 -06:00
John Lancaster
20ca89d452 updating prompt 2026-01-04 11:21:22 -06:00
John Lancaster
6620d6c0a9 variables 2026-01-04 11:03:58 -06:00
John Lancaster
7b7fba3f39 check_cert_config 2026-01-04 10:52:53 -06:00
John Lancaster
6886b6ca69 more case structure 2026-01-04 10:40:01 -06:00
John Lancaster
42b3506b1c row_process 2026-01-04 10:24:46 -06:00
3 changed files with 274 additions and 67 deletions

View File

@@ -18,6 +18,12 @@ step ca init --ssh --acme
## SSH Certificates ## SSH Certificates
Install script:
```bash
bash <(curl -sL https://gitea.john-stream.com/john/janus/raw/branch/main/scripts/ssh-server-check.sh)
```
### Server ### Server
Use step-ca to sign an existing public key to produce a signed certificate with some principals on it. Use step-ca to sign an existing public key to produce a signed certificate with some principals on it.

121
scripts/ssh-client.sh Executable file
View File

@@ -0,0 +1,121 @@
#!/usr/bin/env bash
#
# Env vars
#
SSH_CFG_PATH="$(readlink -f ~/.ssh)"
SSH_USER_KEY="$SSH_CFG_PATH/id_ed25519"
SSH_USER_PUBLIC_KEY="$SSH_USER_KEY.pub"
SSH_USER_CERT="$SSH_USER_KEY-cert.pub"
GREEN_CHECK="\e[32m✔\e[0m"
RED_X="\e[31m✗\e[0m"
YELLOW_BANG="\e[33m!\e[0m"
MAGENTA_QUESTION="\e[35m?\e[0m"
UP_ONE_LINE="\e[1A"
#
# Function Definitions
#
reset_line() {
echo -en "\r\e[K"
}
title_msg() {
local title="\e[1m${1:-Title}:\e[0m"
local prompt="${2:-Prompt for the user}"
# printf "%b %b" "$title" "$prompt"
echo -e "$title $prompt"
}
prompt_user() {
full_prompt_msg="$(title_msg "${1}" "${2}")"
echo -n -e "$MAGENTA_QUESTION $full_prompt_msg"
read -p " (y/n) " -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
reset_line
echo -e "$icon $msg"
}
reupdate_prompt() {
# echo -en "$UP_ONE_LINE"
echo -en "$UP_ONE_LINE"
update_prompt "$@"
}
icon_msg() {
local icon="$1"
echo -en "$icon "
title_msg "${@:2}"
}
success_msg() {
icon_msg "${GREEN_CHECK}" "$@"
}
warn_msg() {
icon_msg "${YELLOW_BANG}" "$@"
}
check_user_private_key() {
if [[ -e "$SSH_USER_KEY" ]]; then
success_msg "SSH User" "Private key: $SSH_USER_KEY"
else
prompt_user "SSH User" "Private key missing: ${SSH_USER_KEY}. Create?"
if [[ $REPLY =~ ^[Yy]$ ]]; then
reupdate_prompt $YELLOW_BANG "SSH User" "Creating private key"
ERROR_MSG=$(ssh-keygen -t ed25519 -f "$SSH_USER_KEY" -N "" 2>&1)
if [[ $? -eq 0 ]]; then
reupdate_prompt $GREEN_CHECK "SSH User" "Created private key: ${SSH_USER_KEY}"
else
reupdate_prompt $RED_X "SSH User" "Failed to create key: ${SSH_USER_KEY}"
echo -e "Error: $ERROR_MSG"
fi
elif [[ $REPLY =~ ^[Nn]$ ]]; then
reupdate_prompt $YELLOW_BANG "SSH User" "Continuing without private key"
fi
fi
}
check_user_cert() {
if [[ -e "$SSH_USER_CERT" ]]; then
success_msg "SSH User" "Certificate: $SSH_USER_CERT"
else
prompt_user "SSH User" "Cert missing. Renew cert?"
if [[ $REPLY =~ ^[Yy]$ ]]; then
if renew_user_cert; then
update_prompt $GREEN_CHECK "SSH User" "Renewed cert"
else
update_prompt $RED_X "SSH User" "Failed to renew cert"
fi
elif [[ $REPLY =~ ^[Nn]$ ]]; then
reupdate_prompt $RED_X "SSH User" "Declined to renew cert"
fi
fi
}
renew_user_cert() {
step ssh certificate --sign \
--principal root --principal john \
--provisioner admin \
john@john-pc-ubuntu ~/.ssh/id_ed25519.pub < /dev/tty
}
#
# Run Process
#
check_user_private_key
check_user_cert

View File

@@ -1,14 +1,30 @@
#!/usr/bin/env bash #!/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" GREEN_CHECK="\e[32m✔\e[0m"
RED_X="\e[31m✗\e[0m" RED_X="\e[31m✗\e[0m"
YELLOW_BANG="\e[33m!\e[0m" YELLOW_BANG="\e[33m!\e[0m"
# CREATE_USER_CA=0
# Function Definition CREATE_HOST_CERT=0
# NEEDS_RESTART=0
#
# Function Definitions
#
# This test loads the sshd config to see what values actually get parsed.
ssh_config_val() { ssh_config_val() {
local field="$1" local field="$1"
local val local val
@@ -21,11 +37,46 @@ ssh_config_val() {
echo $(sshd -T 2>/dev/null | grep -i "^$field " | head -1 | awk '{print $2}') echo $(sshd -T 2>/dev/null | grep -i "^$field " | head -1 | awk '{print $2}')
} }
green_checkmark() { title_msg() {
printf "\e[32m✔\e[0m" local title="\e[1m${1:-Title}:\e[0m"
local prompt="${2:-Prompt for the user}"
# printf "%b %b" "$title" "$prompt"
echo -e "$title $prompt"
} }
check_ssh_files() { 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"
}
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() { row_success() {
local key="$1" local key="$1"
local path="$2" local path="$2"
@@ -33,7 +84,7 @@ check_ssh_files() {
printf "%-17b %-20s %-6s %s\n" " $GREEN_CHECK" "$key" "$perms" "$path" printf "%-17b %-20s %-6s %s\n" " $GREEN_CHECK" "$key" "$perms" "$path"
} }
row_fail() { row_missing() {
local key="$1" local key="$1"
local path="$2" local path="$2"
printf "%-15b %-20s %-6s %s\n" " $YELLOW_BANG" "$key" "-" "$path (missing)" printf "%-15b %-20s %-6s %s\n" " $YELLOW_BANG" "$key" "-" "$path (missing)"
@@ -44,44 +95,51 @@ check_ssh_files() {
printf "%-17b %-20s %-6s %s\n" " $RED_X" "$key" "-" "(not configured)" printf "%-17b %-20s %-6s %s\n" " $RED_X" "$key" "-" "(not configured)"
} }
row_process() { get_key_status() {
local key="$1" local path="$1"
if [[ -z "$key" ]]; then
echo "usage: row_process <key>" >&2
return 2
fi
path=$(ssh_config_val "$key")
if [[ -z "$path" ]]; then if [[ -z "$path" ]]; then
row_unconfigured $key echo "unconfigured"
continue elif [[ ! -e "$path" ]]; then
fi echo "missing"
if [[ -e "$path" ]]; then
row_success $key $path
else else
row_fail $key $path echo "success"
fi 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" printf "%-6s %-20s %-6s %s\n" "STATUS" "KEY" "PERMS" "PATH"
row_process "hostkey" row_process "hostkey"
row_process "hostcertificate" row_process "hostcertificate"
case "$status" in
missing) CREATE_HOST_CERT=1;;
esac
row_process "trustedusercakeys" row_process "trustedusercakeys"
case "$status" in
missing) CREATE_USER_CA=1;;
esac
echo
} }
ssh_fingerprint() { ssh_fingerprint() {
local field="$1" local field="$1"
local ca_path
if [[ -z "$field" ]]; then if [[ -z "$field" ]]; then
echo "usage: ssh_fingerprint <trusteduserca|hostcertificate|...>" >&2 echo "usage: ssh_fingerprint <field>" >&2
return 2 return 2
fi fi
cfg_path=$(ssh_config_val $field) local cfg_path=$(ssh_config_val $field)
if [[ -z "$cfg_path" ]]; then if [[ -z "$cfg_path" ]]; then
echo "error: sshd field '$field' not found or empty" >&2 echo "error: sshd field '$field' not found or empty" >&2
@@ -96,55 +154,77 @@ ssh_fingerprint() {
ssh-keygen -lf "$cfg_path" | awk '{ print $2 }' ssh-keygen -lf "$cfg_path" | awk '{ print $2 }'
} }
install_cert_config() { check_cert_config() {
local base_dir="/etc/ssh/sshd_config.d" local base_dir="/etc/ssh/sshd_config.d"
local cfg_path="${1:-$base_dir/certs.conf}" local cfg_path="$base_dir/${1:-certs.conf}"
install_cert_config() {
mkdir -p $(dirname $cfg_path) mkdir -p $(dirname $cfg_path)
cat <<EOF > $cfg_path cat <<EOF > $cfg_path
TrustedUserCAKeys /etc/ssh/ssh_user_ca.pub TrustedUserCAKeys $SSH_USER_CA
HostKey /etc/ssh/ssh_host_ed25519_key HostKey $SSH_HOST_KEY
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub HostCertificate $SSH_HOST_CERT
EOF EOF
}
echo -e "$GREEN_CHECK Configured sshd to use and accept SSH certs." if [[ ! -e $cfg_path ]]; then
} prompt_user "sshd" "Currently unconfigured for certs. Do you want to configure?"
restart_sshd() {
if systemctl is-active --quiet sshd; then
local sshd_pid=$(systemctl show --property MainPID --value sshd)
echo "Restarting sshd service..."
systemctl restart sshd
echo -e "$GREEN_CHECK Restarted sshd service on PID: $sshd_pid"
else
echo -e "$YELLOW_BANG Not running sshd service"
read -p "Do you want to start sshd? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
systemctl start sshd
echo -e "$GREEN_CHECK Started sshd"
fi
fi
}
#
# Run Process
#
if [[ ! -e "/etc/ssh/sshd_config.d/certs.conf" ]]; then
echo -e "$YELLOW_BANG sshd not configured to use SSH certs"
read -p "Do you want to configure sshd? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then if [[ $REPLY =~ ^[Yy]$ ]]; then
install_cert_config install_cert_config
restart_sshd update_prompt $GREEN_CHECK "sshd" "Configured to use and accept certs"
NEEDS_RESTART=1
fi fi
fi fi
}
check_ssh_files restart_sshd() {
if [[ $NEEDS_RESTART -eq 0 ]]; then return; fi
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
echo
}
echo "" create_files() {
echo "Host key fingerprint" if [[ $CREATE_HOST_CERT -eq 1 ]]; then
ssh_fingerprint hostkey 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
NEEDS_RESTART=1
else
update_prompt $RED_X
fi
fi
if [[ $CREATE_USER_CA -eq 1 ]]; then
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."
NEEDS_RESTART=1
else
update_prompt $RED_X
fi
fi
}
# Run Process
check_cert_config "certs.conf"
check_ssh_config_files
create_files
restart_sshd
title_msg "SSH Host Cert" "$SSH_HOST_CERT"
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"