From 3a574392f0e016ddb0b632efeb20fafbe8f58f1a Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 9 Jun 2023 23:38:50 +0000 Subject: [PATCH] feat: add sshd-prohibit-password --- ssh-harden/README.md | 97 ++++++++++ ssh-harden/ssh-harden.complex | 182 ++++++++++++++++++ ssh-prohibit-password/README.md | 11 ++ ssh-prohibit-password/install.sh | 13 ++ ssh-utils/README.md | 18 +- ssh-utils/install.sh | 6 +- sshd-prohibit-password/README.md | 53 +++++ sshd-prohibit-password/install.sh | 20 ++ sshd-prohibit-password/sshd-prohibit-password | 148 ++++++++++++++ 9 files changed, 546 insertions(+), 2 deletions(-) create mode 100644 ssh-harden/README.md create mode 100644 ssh-harden/ssh-harden.complex create mode 100644 ssh-prohibit-password/README.md create mode 100644 ssh-prohibit-password/install.sh create mode 100644 sshd-prohibit-password/README.md create mode 100644 sshd-prohibit-password/install.sh create mode 100644 sshd-prohibit-password/sshd-prohibit-password diff --git a/ssh-harden/README.md b/ssh-harden/README.md new file mode 100644 index 0000000..16ef259 --- /dev/null +++ b/ssh-harden/README.md @@ -0,0 +1,97 @@ +--- +title: SSH Prohibit Password +homepage: https://webinstall.dev/ssh-prohibit-password +tagline: | + SSH Prohibit Password: Because friends don't let friends ssh with passwords +linux: true +--- + +## Cheat Sheet + +> Will check if your system This will check if your Modern SSH deployments are +> key-only and don't allow root login. However, there's a lot of legacy systems +> out there. + +`ssh-harden` will + +1. Check that some `/home/*/.ssh/authorized_keys` is non-empty +2. Check that `/etc/sudoers.d` is not empty +3. Optionally create a `sudoer` for a given user and group +4. Disable `root` login +5. Disable Password and Challenge login + +```sh +USAGE + ssh-harden [username] [sudo-group] + +EXAMPLES + + sudo ssh-harden + sudo ssh-harden app + sudo ssh-harden "$(id -n -u)" wheel +``` + +### How to check for sudoers + +```sh +sudo sh -c 'grep "^\w\+ ALL=" /etc/sudoers.d/*' +``` + +### How to check for authorized ssh users + +**Quick 'n' Easy** + +```sh +sudo sh -c "grep -E '^(ssh|ec)' /home/*/.ssh/authorized_keys" | + cut -d' ' -f3 | + sort -u +``` + +**Detailed** + +```sh +my_authorized='' +for my_file in /home/*/.ssh/authorized_keys; do + # if no files match the glob becomes a literal string + if test "${my_file}" = '/home/*/.ssh/authorized_keys'; then + break + fi + + echo "${my_file} authorizes:" + if ! grep -q -E '^(ssh|ec)' "${my_file}"; then + echo " (none, empty file)" + continue + fi + + grep '^(ssh|ec)' "${my_file}" | cut -d' ' -f3 | while read -r my_comment; do + echo " ${my_comment}" + done + my_authorized='true' +done + +if test -z "${my_authorized}"; then + echo >&2 "" + echo >&2 "ERROR" + echo >&2 " No authorized remote users found." + echo >&2 "" + exit 1 +fi +``` + +### How to add passwordless sudoer + +```sh +echo "app ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/app +``` + +How to copy allowed keys from root to the new user: + +```sh +mkdir -p /home/app/.ssh/ +chmod 0700 /home/app/.ssh/ + +cat "$HOME/.ssh/authorized_keys" >> /home/app/.ssh/authorized_keys +chmod 0600 /home/app/.ssh/authorized_keys + +chown -R app:app /home/app/.ssh/ +``` diff --git a/ssh-harden/ssh-harden.complex b/ssh-harden/ssh-harden.complex new file mode 100644 index 0000000..2394fd0 --- /dev/null +++ b/ssh-harden/ssh-harden.complex @@ -0,0 +1,182 @@ +#!/bin/sh +set -e +set -u + +# There are two important use cases: +# 1. Run as 'root', for the 'app' (or specified) user +# 2. Run as a sudoer, for self + +fn_check_as_root() { ( + my_admin="${1:-}" + + if ! command -v sudo > /dev/null; then + echo "" + echo "WARNING" + echo " 'sudo' is not installed." + echo " '${my_admin}' MUST be able to login via the 'root' password." + echo "" + else + my_admin_is_sudoer='' + if grep -q "^${my_admin} ALL=" /etc/sudoers; then + my_admin_is_sudoer='true' + elif test -e /etc/sudoers.d; then + if grep -q "^${my_admin} ALL=" /etc/sudoers.d/*; then + my_admin_is_sudoer='true' + fi + fi + + echo "" + echo "ERROR" + echo " '${my_admin}' is NOT a sudoer" + echo "" + echo "SOLUTION" + echo " mkdir -p /etc/sudoers.d" + echo " chmod 0700 /etc/sudoers.d" + echo " echo '${my_admin} ALL=(ALL:ALL) NOPASSWD: ALL' | " + echo " tee '/etc/sudoers.d/${my_admin}'" + echo "" + fi +); } + +fn_check_as_sudoer() { ( + echo +); } + +main() { + echo "" + echo "IMPORTANT" + echo "" + echo "READ CAREFULLY (or get LOCKED OUT)" + echo "" + + my_user_id="$(id -u)" + if test "0" = "${my_user_id}"; then + fn_check_as_root ${1:-app} + else + fn_check_as_sudoer + fi + + my_username="$(id -u -n)" + my_sudo_user="${1:-${my_username}}" + + my_sudo_exists='' + my_sudo='' + if command -v sudo > /dev/null; then + my_sudo_exists='true' + my_sudo="sudo" + fi + + my_root='' + if test "$(id -u)" = "0"; then + my_root='true' + my_sudo='' + fi + + my_authorized_exists='' + echo '' + echo "All authorized users:" + if ! $my_sudo sh -c 'ls /home/*/.ssh/authorized_keys' > /devl/null 2>&1; then + echo " (none)" + else + $my_sudo sh -c "grep -E '^(ssh|ec)' /home/*/.ssh/authorized_keys" | + cut -d' ' -f3 | + sort -u | + while read -r my_comment; do + echo " ${my_comment}" + my_authorized_exists='true' + done + fi + + my_sudoer_exists='' + echo '' + echo "All sudoers:" + if test -z "${my_sudo_exists}"; then + echo ' (sudo not installed)' + else + { + $my_sudo sh -c "grep -E '(sudo|wheel):' /etc/gshadow" | + cut -d':' -f4 | + tr ',' '\n' + + #$my_sudo sh -c "grep '^%\?\w\+ ALL=' /etc/sudoers /etc/sudoers.d/*" | + $my_sudo sh -c "grep '^\w\+ ALL=' /etc/sudoers /etc/sudoers.d/*" | + cut -d':' -f2 | + cut -d' ' -f1 + } | + grep -v root | + sort -u | + while read -r my_sudoer; do + my_sudoer_exists='true' + echo " ${my_sudoer}" + done + fi + + #adduser "$my_new_user" sudo || adduser "$my_new_user" wheel + echo "$my_new_user ALL=(ALL:ALL) NOPASSWD: ALL" | tee "/etc/sudoers.d/$my_new_user" + + # allow users who can already login as 'root' to login as 'app' + mkdir -p "/home/$my_new_user/.ssh/" + chmod 0700 "/home/$my_new_user/.ssh/" + echo "${my_keys}" >> "/home/$my_new_user/.ssh/authorized_keys" + chmod 0600 "/home/$my_new_user/.ssh/authorized_keys" + touch "/home/$my_new_user/.ssh/config" + chmod 0644 "/home/$my_new_user/.ssh/config" + chown -R "$my_new_user":"$my_new_user" "/home/$my_new_user/.ssh/" + + # ensure that 'app' has an SSH Keypair + sudo -i -u "$my_new_user" sh -c "ssh-keygen -b 2048 -t rsa -f '/home/$my_new_user/.ssh/id_rsa' -q -N ''" + chown -R "$my_new_user":"$my_new_user" "/home/$my_new_user/.ssh/" + + # Install webi for the new 'app' user + WEBI_HOST=${WEBI_HOST:-"https://webinstall.dev"} + sudo -i -u "$my_new_user" sh -c "curl -fsSL '$WEBI_HOST/webi' | sh" || + sudo -i -u "$my_new_user" sh -c "wget -q -O - '$WEBI_HOST/webi' | sh" + + # TODO ensure that ssh-password login is off + my_pass="$(grep 'PasswordAuthentication yes' /etc/ssh/sshd_config)" + my_pam="" + if [ "Darwin" = "$(uname -s)" ]; then + # Turn off PAM for macOS or it will allow password login + my_pam="$(grep 'UsePAM yes' /etc/ssh/sshd_config)" + fi + if [ -n "${my_pass}" ] || [ -n "${my_pam}" ]; then + echo "######################################################################" + echo "# #" + echo "# WARNING #" + echo "# #" + echo "# Found /etc/ssh/sshd_config: #" + if [ -n "${my_pass}" ]; then + echo "# PasswordAuthentication yes #" + fi + if [ -n "${my_pam}" ]; then + echo "# UsePAM yes #" + fi + echo "# #" + echo "# This is EXTREMELY DANGEROUS and insecure. #" + echo "# We'll attempt to fix this now... #" + echo "# #" + + sed -i 's/#\?PasswordAuthentication \(yes\|no\)/PasswordAuthentication no/' \ + /etc/ssh/sshd_config + + sed -i 's/#\?UsePAM \(yes\|no\)/UsePAM no/' \ + /etc/ssh/sshd_config + + if grep "PasswordAuthentication yes" /etc/ssh/sshd_config; then + echo "# FAILED. Please check /etc/ssh/sshd_config manually. #" + else + echo "# Fixed... HOWEVER, you'll need to manually restart ssh: #" + echo "# #" + echo "# sudo systemctl restart ssh #" + echo "# #" + echo "# (you may want to make sure you can login as the new user first) #" + fi + echo "# #" + echo "######################################################################" + fi + + echo "Created user '${my_new_user}' as sudoer with a random password." + echo "(set a new password with 'password ${my_new_user}')" +} + +main "${1:-app}" "${2:-}" diff --git a/ssh-prohibit-password/README.md b/ssh-prohibit-password/README.md new file mode 100644 index 0000000..d2b2cf5 --- /dev/null +++ b/ssh-prohibit-password/README.md @@ -0,0 +1,11 @@ +--- +title: SSH Prohibit Password +homepage: https://webinstall.dev/sshd-prohibit-password +tagline: | + Alias for https://webinstall.dev/sshd-prohibit-password +alias: sshd-prohibit-password +description: | + See https://webinstall.dev/sshd-prohibit-password +--- + +Alias for https://webinstall.dev/sshd-prohibit-password diff --git a/ssh-prohibit-password/install.sh b/ssh-prohibit-password/install.sh new file mode 100644 index 0000000..276f173 --- /dev/null +++ b/ssh-prohibit-password/install.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -e +set -u + +# shellcheck disable=SC2016 + +__alias_sshd_prohibit_password() { + echo "'ssh-prohibit-password@${WEBI_TAG:-stable}' is an alias for 'sshd-prohibit-password@${WEBI_VERSION-}'" + WEBI_HOST=${WEBI_HOST:-"https://webinstall.dev"} + curl -fsSL "$WEBI_HOST/sshd-prohibit-password@${WEBI_VERSION-}" | sh +} + +__alias_sshd_prohibit_password diff --git a/ssh-utils/README.md b/ssh-utils/README.md index 3d49e82..e7f385f 100644 --- a/ssh-utils/README.md +++ b/ssh-utils/README.md @@ -8,7 +8,7 @@ tagline: | ## Cheat Sheet > SSH Utils includes shortcut commands for some common tasks, including -> `ssh-pubkey`, `ssh-setpass`, and `ssh-adduser` +> `ssh-pubkey`, `ssh-setpass`, `ssh-adduser`, and `sshd-prohibit-password` **ssh-pubkey**: @@ -32,6 +32,22 @@ correctly if run as root. `ssh-adduser` adds user `app` with the same **`~/.ssh/authorized_keys`** as the `root` user, with a long random password, and gives `app` `sudo` privileges. +**sshd-prohibit-password**: + +Enforces security for `/etc/ssh/sshd_config` + +```diff +- #PasswordAuthentication yes ++ PasswordAuthentication no + +- #PermitRootLogin yes ++ PermitRootLogin prohibit-password + + # macOS only +- UsePAM yes ++ UsePAM no +``` + **ssh-setpass**: `ssh-setpass` will ask you for your old passphrase (if any) and then for the new diff --git a/ssh-utils/install.sh b/ssh-utils/install.sh index c24989e..1d601fd 100644 --- a/ssh-utils/install.sh +++ b/ssh-utils/install.sh @@ -6,7 +6,8 @@ __install_ssh_utils() { rm -f \ "$HOME/.local/bin/ssh-pubkey" \ "$HOME/.local/bin/ssh-setpass" \ - "$HOME/.local/bin/ssh-adduser" + "$HOME/.local/bin/ssh-adduser" \ + "$HOME/.local/bin/sshd-prohibit-password" # done webi_download \ @@ -18,6 +19,9 @@ __install_ssh_utils() { webi_download \ "$WEBI_HOST/packages/ssh-adduser/ssh-adduser.sh" \ "$HOME/.local/bin/ssh-adduser" + webi_download \ + "$WEBI_HOST/packages/sshd-prohibit-password/sshd-prohibit-password" \ + "$HOME/.local/bin/sshd-prohibit-password" chmod a+x "$HOME/.local/bin/ssh-"* } diff --git a/sshd-prohibit-password/README.md b/sshd-prohibit-password/README.md new file mode 100644 index 0000000..5599820 --- /dev/null +++ b/sshd-prohibit-password/README.md @@ -0,0 +1,53 @@ +--- +title: SSH Prohibit Password +homepage: https://webinstall.dev/sshd-prohibit-password +tagline: | + SSH Prohibit Password: Because friends don't let friends ssh with passwords +linux: true +--- + +## Cheat Sheet + +> Will check if your system This will check if your Modern SSH deployments are +> key-only and don't allow root login. However, there's a lot of legacy systems +> out there. + +`sshd-prohibit-password` will inspect `/etc/ssh/sshd_config` and + +1. Enforce that `PasswordAuthentication` is `no` +2. Enforce that `PermitRootLogin` is `no` or `prohibit-password` \ + (or `without-password`, for older systems) +3. (macOS only) Enforce that `UsePAM` is `no` + +This **will run automatically** and **uses `sudo`** to make changes. + +### What's checked and changed? + +```diff +- #PasswordAuthentication yes ++ PasswordAuthentication no + +- #PermitRootLogin yes ++ PermitRootLogin prohibit-password + + # macOS only +- UsePAM yes ++ UsePAM no +``` + +### How to restart SSH? + +```sh +# Ubuntu / Debian / RedHat +sudo systemctl restart sshd + +# Alpine / Gentoo +sudo rc-service sshd restart + +# macOS +sudo launchctl unload /System/Library/LaunchDaemons/ssh.plist +sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist + +# others +killall sshd +``` diff --git a/sshd-prohibit-password/install.sh b/sshd-prohibit-password/install.sh new file mode 100644 index 0000000..c7c10fe --- /dev/null +++ b/sshd-prohibit-password/install.sh @@ -0,0 +1,20 @@ +#!/bin/sh +set -e +set -u + +__install_sshd_prohibit_password() { + my_cmd="sshd-prohibit-password" + + rm -f "$HOME/.local/bin/${my_cmd}" + + webi_download \ + "$WEBI_HOST/packages/${my_cmd}/${my_cmd}" \ + "$HOME/.local/bin/${my_cmd}" + + chmod a+x "$HOME/.local/bin/${my_cmd}" + + # run the command + "$HOME/.local/bin/${my_cmd}" +} + +__install_sshd_prohibit_password diff --git a/sshd-prohibit-password/sshd-prohibit-password b/sshd-prohibit-password/sshd-prohibit-password new file mode 100644 index 0000000..c1ffc24 --- /dev/null +++ b/sshd-prohibit-password/sshd-prohibit-password @@ -0,0 +1,148 @@ +#!/bin/sh +set -e +set -u + +main() { + cmd_sudo='' + if command -v sudo > /dev/null; then + my_id="$(id -u)" + if test "0" != "${my_id}"; then + cmd_sudo='sudo' + fi + fi + + cmd_sed="${cmd_sudo} sed -i -E" + my_bsd_sed='' + if ! sed -V 2>&1 | grep -q 'GNU'; then + cmd_sed="${cmd_sudo} sed -i .prohibit-password-bak -E" + my_bsd_sed='true' + fi + + my_changes='' + + # PasswordAuthentication + echo "" + echo "Checking for 'PasswordAuthentication no'..." + echo " grep '^\s*PasswordAuthentication\s*no' /etc/ssh/sshd_config" + my_allow_passwords="$( + grep -q \ + '^\s*PasswordAuthentication\s*no\s*$' \ + /etc/ssh/sshd_config || + echo 'true' + )" + if test -n "${my_allow_passwords}"; then + $cmd_sed \ + 's/#*[[:space:]]*PasswordAuthentication[[:space:]]*(yes|no)[[:space:]]*$/PasswordAuthentication no/g' \ + /etc/ssh/sshd_config + + if ! grep -q '^PasswordAuthentication no$' /etc/ssh/sshd_config; then + echo "" + echo "ERROR" + echo " failed to update '/etc/ssh/sshd_config'" + echo "" + return 1 + fi + echo " RESTART REQUIRED: disabled user password login" + my_changes='true' + else + echo " PASS: passwords are NOT allowed" + fi + + # PermitRootLogin + echo "" + echo "Checking for 'PermitRootLogin prohibit-password'..." + echo " grep -E '^\s*PermitRootLogin\s*(no|prohibit-password)' /etc/ssh/sshd_config" + my_allow_root="$( + grep -q -E \ + '^\s*PermitRootLogin\s*(no|prohibit-password|without-password)\s*$' \ + /etc/ssh/sshd_config || + echo 'true' + )" + if test -n "${my_allow_root}"; then + $cmd_sed \ + 's/#*[[:space:]]*PermitRootLogin[[:space:]]*(yes|no|prohibit-password|without-password)[[:space:]]*$/PermitRootLogin prohibit-password/g' \ + /etc/ssh/sshd_config + if ! grep -q -E \ + '^PermitRootLogin prohibit-password$' \ + /etc/ssh/sshd_config; then + echo "" + echo "ERROR" + echo " failed to update '/etc/ssh/sshd_config'" + echo "" + return 1 + fi + echo " RESTART REQUIRED: disabled root password login" + my_changes='true' + else + echo " PASS: 'root' can NOT login via password" + fi + + # UsePAM + echo "" + echo "Checking for 'UsePAM no' (macOS only)..." + # shellcheck disable=SC2016 + echo ' test "$(uname -s)" = "Darwin" &&' + echo " grep '^\s*UsePAM\s*no' /etc/ssh/sshd_config" + if test "$(uname -s)" = "Darwin"; then + my_allow_pam="$( + grep -q \ + '^\s*UsePAM\s*no' \ + /etc/ssh/sshd_config || + echo 'true' + )" + if test -n "${my_allow_pam}"; then + $cmd_sed \ + 's/#*[[:space:]]*UsePAM[[:space:]]*(yes|no)[[:space:]]*$/UsePAM no/g' \ + /etc/ssh/sshd_config + if ! grep -q '^UsePAM no$' /etc/ssh/sshd_config; then + echo "" + echo "ERROR" + echo " failed to update '/etc/ssh/sshd_config'" + echo "" + return 1 + fi + echo " RESTART REQUIRED: disabled macOS password login" + my_changes='true' + else + echo " PASS: passwords are NOT allowed" + fi + fi + + if test -z "${my_changes}"; then + echo "" + echo "" + echo "All checks pass. No changes necessary." + echo "" + return 0 + fi + + if test -n "${my_bsd_sed}"; then + $cmd_sudo rm -f /etc/ssh/sshd_config.prohibit-password-bak + fi + + echo "" + echo "##################################################################" + echo "# #" + echo "# IMPORTANT #" + echo "# #" + echo "# READ CAREFULLY #" + echo "# #" + echo "# (or get locked out) #" + echo "# #" + echo "##################################################################" + echo "" + + echo "" + echo "1. TEST SSH KEYS" + echo "" + echo " Be sure that you can login as an admin user with ssh keys" + echo "" + echo "2. RESTART SSH with one of the following:" + echo "" + echo " systemctl restart sshd # Ubuntu / Debian" + echo " rc-service sshd restart # Alpine / Gentoo" + echo " killall sshd # all others" + echo "" +} + +main "${1:-app}" "${2:-}"