Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
168e0b3
Add ncp app for setting up multi factor authentication for ssh
theCalcaholic Dec 14, 2019
6fa0ef4
Properly restore default pam configuration if totp was not enabled.
theCalcaholic Dec 14, 2019
71042da
Add configuration for multi factor authentication app
theCalcaholic Dec 14, 2019
35d7f04
Add cleanup() function to multi-factor-authentication.sh
theCalcaholic Dec 14, 2019
07a642d
multi-factor-authentication: use apt in non-interactive mode
theCalcaholic Dec 14, 2019
8f2ec46
multi-factor-authentication: Simplify is-active function
theCalcaholic Dec 14, 2019
48622bb
multi-factor-authentication: Use jq in 'raw' mode (to avoid additiona…
theCalcaholic Dec 14, 2019
938499e
multi-factor-authentication: Implement public key setup
theCalcaholic Dec 14, 2019
386129a
multi-factor-authentication: Fix improper check whether or not SSH us…
theCalcaholic Dec 14, 2019
2caaaf6
Implement escaping of spaces in ncp-app parameters
theCalcaholic Dec 14, 2019
f98f21f
multi-factor-authentication: Prevent enabling of totp+pw and other pa…
theCalcaholic Dec 15, 2019
97c9df4
multi-factor-authentication: Only enable the "UsePAM" setting, if tot…
theCalcaholic Dec 15, 2019
ed0d597
multi-factor-authentication: Remove obsolete "ACTIVE" parameter
theCalcaholic Dec 15, 2019
76465c8
multi-factor-authentication: Improve description
theCalcaholic Dec 15, 2019
a71071d
multi-factor-authentication: Remove existing authorized ssh pubkeys i…
theCalcaholic Dec 15, 2019
dbea3bc
multi-factor-authentication: Ensure we have permissions to delete goo…
theCalcaholic Dec 15, 2019
9c1b253
Use <pre> tags and UTF8 mode to show qr code in browser
Dec 19, 2019
58a6c1f
Revert "Use <pre> tags and UTF8 mode to show qr code in browser"
Dec 26, 2019
e487dcd
multi-factor-authentication.sh: Remove mention of qr code since it is…
Dec 26, 2019
500e9dc
multi-factor-authentication.cfg: Fix typos and improve concision.
Dec 27, 2019
4fc85ce
multi-factor-authentication.*: Provide 5 fields for SSH pub keys
theCalcaholic Jan 10, 2020
cbbf624
library.sh: Support parameters with 'allow_unsafe'
theCalcaholic Jan 10, 2020
5083978
multi-factor-authentication.sh: Remove debug output
theCalcaholic Jan 11, 2020
c482983
multi-factor-authentication.sh: Fix typo in variable name
theCalcaholic Jan 11, 2020
586297a
Merge branch 'devel' into feature/multi-factor-ssh
theCalcaholic Jan 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
291 changes: 291 additions & 0 deletions bin/ncp/SECURITY/multi-factor-authentication.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
#!/usr/bin/env bash

# Authentication Options:
# =======================
# * Password
# * Pubkey
# * TOTP + Password
# (* TOTP + Pubkey + Password) not yet supported
# * TOTP + Password / TOTP + Pubkey

PAMD_PATH="/etc/pam.d"
PAMD_BACKUP_PATH="/etc/pam.backup"
SSHD_CONFIG_PATH="/etc/ssh/sshd_config"


# Configure pam.d/sshd config
# ======================

patch_pam_ssh_config() {
local cfg

if [[ "$1" == "--reset" ]]
then
if [[ -f "${PAMD_BACKUP_PATH}/sshd" ]]; then
echo "Restoring original configuration for '${PAMD_PATH}/sshd'..."
mv "${PAMD_BACKUP_PATH}/sshd" "${PAMD_PATH}/sshd" || return 1
[[ -f "${PAMD_PATH}/sshd-mfa" ]] && rm "${PAMD_PATH}/sshd-mfa"
return 0
else
echo "ERROR: Could not restore '${PAMD_PATH}/sshd' from backup '${PAMD_BACKUP_PATH}/sshd' (not found)!"
return 1
fi
fi

mkdir -p "$PAMD_BACKUP_PATH"
[[ -f "${PAMD_BACKUP_PATH}/sshd" ]] || {
echo "Backing up '${PAMD_PATH}/sshd'..."
cp "${PAMD_PATH}/sshd" "${PAMD_BACKUP_PATH}/sshd"
} || {
echo "Error creating backup. '${PAMD_PATH}/sshd will remain unchanged!"
return 1
}

echo "Writing pam configuration..."
if [[ "$enable_totp_and_pw" != "yes" ]]
then
cp "${PAMD_BACKUP_PATH}/sshd" "${PAMD_PATH}/sshd" || return 1
[[ -f "${PAMD_PATH}/sshd-mfa" ]] && rm "${PAMD_PATH}/sshd-mfa"
return 0
fi
echo "" > "${PAMD_PATH}/sshd-mfa" || return 1

if [[ "$enable_totp_and_pw" == "yes" ]]; then
echo "auth required pam_google_authenticator.so nullok" >> "${PAMD_PATH}/sshd-mfa"
fi
echo "@include common-auth" >> "${PAMD_PATH}/sshd-mfa"

sed 's/@include.*common-auth/@include sshd-mfa/g' "${PAMD_BACKUP_PATH}/sshd" > "${PAMD_PATH}/sshd" || return 1


}

# Configure sshd_config
# =====================

#sshd_authentication_options=("password" "publickey" "publickey,password" "keyboard-interactive"
# "keyboard-interactive,publickey" "keyboard-interactive,publickey keyboard-interactive,password")
patch_sshd_config() {
local cfg
local auth_method="${1?}"

if [[ "$auth_method" == "--reset" ]]
then
if [[ -f "${SSHD_CONFIG_PATH}.backup" ]]
then
echo "Restoring '${SSHD_CONFIG_PATH}' from '${SSHD_CONFIG_PATH}.backup'..."
mv "${SSHD_CONFIG_PATH}.backup" "${SSHD_CONFIG_PATH}" || return 1
return 0
else
echo "ERROR: Could not restore '${SSHD_CONFIG_PATH}' from '${SSHD_CONFIG_PATH}.backup' (not found)!"
return 1
fi
fi

# backup sshd_config
mkdir -p /etc/pam.backup
[[ -f "${SSHD_CONFIG_PATH}.backup" ]] || {
echo "Backing up '${SSHD_CONFIG_PATH}'..."
cp "$SSHD_CONFIG_PATH" "${SSHD_CONFIG_PATH}.backup"
} || {
echo "Error creating backup. '${PAMD_PATH}/sshd will remain unchanged!"
return 1
}

# get sshd_config without google_authenticator
cfg="$(
grep -v -e "AuthenticationMethods" "${SSHD_CONFIG_PATH}.backup" |
grep -v -e "ChallengeResponseAuthentication" |
grep -v -e "PasswordAuthentication" |
grep -v -e "PubkeyAuthentication" |
grep -v -e "UsePAM"
)"

echo "Writing sshd configuration..."
echo "$cfg" > "$SSHD_CONFIG_PATH" || return 1
cat << EOF >> "$SSHD_CONFIG_PATH"

###################################

PasswordAuthentication yes
PubkeyAuthentication yes
ChallengeResponseAuthentication yes
UsePAM $enable_totp_and_pw
AuthenticationMethods $auth_method

EOF

}

setup_configuration() {
local auth_method=""

[[ "$enable_pubkey_only" == "yes" ]] && auth_method="publickey"
[[ "$enable_pw_only" == "yes" ]] && auth_method="${auth_method} password"

[[ "$enable_totp_and_pw" == "yes" ]] && auth_method="keyboard-interactive"
[[ "$enable_pubkey_and_pw" == "yes" ]] && auth_method="${auth_method} publickey,password"

patch_pam_ssh_config || return 2
patch_sshd_config "$auth_method" || return 1
}

setup_totp_secret() {
local ssh_user="${1?}"
local ssh_user_home="${2?}"

[[ "$reset_totp_secret" == "yes" ]] \
&& [[ -f "$ssh_user_home/.google_authenticator" ]] \
&& {
echo "Deleting google authenticator configuration"
su "$ssh_user" -c "chmod u+w '${ssh_user_home}/.google_authenticator'"
su "$ssh_user" -c "rm '${ssh_user_home}/.google_authenticator'"
}


if [[ "$enable_totp_and_pw" == "yes" ]] && [[ ! -f "${ssh_user_home}/.google_authenticator" ]]
then
echo "We will now generate TOTP a client secret for your ssh user ('$ssh_user')."
echo "Please store the following information in a safe place. Use your secret key to setup your authenticator app."
echo ""
su "$ssh_user" -c "google-authenticator -tdf -w 1 --no-rate-limit"
fi
}

restore() {
local ret=0
patch_pam_ssh_config --reset
ret=$((ret + $?))
patch_sshd_config --reset
ret=$((ret + $?))
return $ret
}

check_configuration_is_valid() {
if { [[ "$enable_pubkey_and_pw" == "yes" ]] || [[ "$enable_pubkey_only" == "yes" ]]; } && [[ -z "$SSH_PUBLIC_KEY" ]]
then
echo "ERROR: Public key reliant authentication methods have been enabled, but no publick key has been specified. Aborting..."
return 1
fi

if [[ "$enable_totp_and_pw" == "yes" ]] && [[ "$enable_pubkey_and_pw" == "yes" ]]
then
echo "Due to limitations of the sshd configuration, totp+pw and other authentication methods involving a password can't be enabled at the same time."
echo "Aborting..."
return 1
fi
}

################################################################

cleanup() {
restore
[[ -d "${PAMD_BACKUP_PATH}" ]] && rm -r "${PAMD_BACKUP_PATH}"
}

install() {
apt install -y libpam-google-authenticator
}

is_active() {
grep -q -e "AuthenticationMethods.*keyboard-interactive" -e "AuthenticationMethods.*publickey" "${SSHD_CONFIG_PATH}" \
|| grep -q -e "sshd-mfa" "${PAMD_PATH}/sshd"
}

configure() {

local active enable_totp_and_pw enable_pubkey_and_pw enable_pubkey_only enable_pw_only reset_totp_secret ssh_pubkeys
enable_totp_and_pw="$ENABLE_TOTP_AND_PASSWORD"
enable_pubkey_and_pw="$ENABLE_PUBLIC_KEY_AND_PASSWORD"
enable_pubkey_only="$ENABLE_PUBLIC_KEY_ONLY"
enable_pw_only="$ENABLE_PASSWORD_ONLY"
reset_totp_secret="$RESET_TOTP_SECRET"
active="yes"
ssh_pubkeys=("$(unescape "$SSH_PUBLIC_KEY_1")" \
"$(unescape "$SSH_PUBLIC_KEY_2")" \
"$(unescape "$SSH_PUBLIC_KEY_3")" \
"$(unescape "$SSH_PUBLIC_KEY_4")" \
"$(unescape "$SSH_PUBLIC_KEY_5")")

trap 'restore' HUP INT QUIT PIPE TERM

if [[ -f "/usr/local/etc/ncp-config.d/SSH.cfg" ]]
then
# TODO: Should we rather provider an input field for the SSH user (what happens if it is changed in the ssh config)?
SSH_USER="$(jq -r '.params[] | select(.id == "USER") | .value' < /usr/local/etc/ncp-config.d/SSH.cfg)"
SSH_USER_HOME="$(sudo -Hu "$SSH_USER" bash -c 'echo "$HOME"')"
fi

[[ -n "$SSH_USER" ]] || id -u "$SSH_USER" > /dev/null || {
echo "Setup incomplete. Please configure SSH via the ncp app and rerun."
return 1
}

[[ "$enable_totp_and_pw" == "no" ]] && [[ "$enable_pubkey_and_pw" == "no" ]] \
&& [[ "$enable_pubkey_only" == "no" ]] && [[ "$enable_pw_only" == "yes" ]] && {
echo "Default configuration has been detected. Restoring defaults..."
active="no"
}

if [[ "$active" == "yes" ]] && [[ "$enable_totp_and_pw" != "yes" ]] \
&& [[ "$enable_pubkey_and_pw" != "yes" ]] && [[ "$enable_pubkey_only" != "yes" ]]
then
[[ $enable_pw_only ]] \
|| echo "WARNING: No authentication method has been enabled. Enabling default authentication (password only)..."
active="no"
fi

if [[ "$active" != "yes" ]]
then
ret=0
is_active && { restore || ret=$?; }
systemctl is-enabled ssh -q && systemctl restart ssh
return $ret
else

check_configuration_is_valid || return 3

if [[ "$enable_totp_and_pw" == "yes" ]] || [[ "$enable_pubkey_and_pw" == "yes" ]]
then
echo "At least one multifactor authentication method has been enabled. Therefore, weaker authentication methods will be disabled automatically."
[[ "$enable_pubkey_only" == "yes" ]] && {
echo "Disabling 'public key only' authentication"
enable_pubkey_only=no
}
[[ "$enable_pw_only" == "yes" ]] && {
echo "Disabling 'password only' authentication"
enable_pw_only=no
}
fi
fi

echo "Setting up configuration files..."
setup_configuration || {
ret=$?
restore
return $ret
}

echo "Restarting ssh service..."
systemctl is-enabled ssh -q && systemctl restart ssh

# Setup SSH public key
if [[ -n "${ssh_pubkeys[*]}" ]]
then
echo "Setting up SSH public key..."
local IFS_BK="$IFS"
IFS=$'\n'
echo "${ssh_pubkeys[*]}" > "${SSH_USER_HOME}/.ssh/authorized_keys"
IFS="$IFS_BK"
chown "${SSH_USER}:" "${SSH_USER_HOME}/.ssh/authorized_keys"
elif [[ -f "${SSH_USER_HOME}/.ssh/authorized_keys" ]]
then
echo "Removing authorized ssh public key"
rm "${SSH_USER_HOME}/.ssh/authorized_keys"
fi

setup_totp_secret "$SSH_USER" "$SSH_USER_HOME"

echo "Done."

}
13 changes: 13 additions & 0 deletions etc/library.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,13 @@ function configure_app()
$DIALOG_OK)
while read val; do local ret_vals+=("$val"); done <<<"$value"

local allow_unsafe

for (( i = 0 ; i < len ; i++ )); do
# check for invalid characters
# check if unsafe characters (spaces) are allowed (will return 'null' if key not found)
allow_unsafe="$(jq -r .params[$i].allow_unsafe <<<"$cfg")"
[[ "${allow_unsafe}" == 'true' ]] && ret_vals[$i]="${ret_vals[$i]// /%SPACE%}"
grep -q '[\\&#;'"'"'`|*?~<>^"()[{}$&[:space:]]' <<< "${ret_vals[$i]}" && { echo "Invalid characters in field ${vars[$i]}"; return 1; }

cfg="$(jq ".params[$i].value = \"${ret_vals[$i]}\"" <<<"$cfg")"
Expand Down Expand Up @@ -279,6 +284,13 @@ function check_distro()
return 1
}


function unescape()
{
local str="${1?}"
echo "${str//"%SPACE%"/" "}"
}

function clear_password_fields()
{
local cfg_file="$1"
Expand All @@ -291,6 +303,7 @@ function clear_password_fields()
cfg="$(jq -r ".params[$i].value=\"$val\"" <<<"$cfg")"
done
echo "$cfg" > "$cfg_file"

}

function apt_install()
Expand Down
Loading