scripts/duplicati/add.ssh.pubkey.sh

462 lines
15 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/sh
#
# Purpose {{{
# This script will try to allow SSH connection for a user by
# 1. Getting the username.
# * Directly from the argument.
# * Or from an ldap search with a given email address.
# * Verify that the system knows this user.
# 2. Adding the pubkey to a specific authorized_keys file.
# * System wide authorized_keys directory (default to /etc/ssh/authorized_keys).
# * One authorized_keys file per user.
# * Adding some restriction to the pubkey (allow only scp by default).
# …
#
# 2022-07-19
# }}}
# Vars {{{
PROGNAME=$(basename "${0}"); readonly PROGNAME
PROGDIR=$(readlink -m $(dirname "${0}")); readonly PROGDIR
ARGS="${*}"; readonly ARGS
readonly NBARGS="${#}"
[ -z "${DEBUG}" ] && DEBUG=1
## Export DEBUG for sub-script
export DEBUG
## Default values for some vars
readonly SSH_PUBKEY_FILE_DEFAULT="/tmp/duplicati_id_ed25519.pub"
readonly SSH_PUBKEY_OPTION_DEFAULT='no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command="/usr/lib/openssh/sftp-server"'
readonly SSHD_AUTH_KEY_DIR_DEFAULT="/home/.duplicati/ssh/authorized_keys"
## Colors
readonly PURPLE='\033[1;35m'
readonly RED='\033[0;31m'
readonly RESET='\033[0m'
readonly COLOR_DEBUG="${PURPLE}"
# }}}
usage() { # {{{
cat <<- HELP
usage: $PROGNAME [-a|-d|-f|-h|-k|-o] -e|-u
This script will try to add a SSH pubkey to allow user's connection.
EXAMPLES:
- Add default pubkey file (${SSH_PUBKEY_FILE_DEFAULT} to default authorized_keys location (${SSHD_AUTH_KEY_DIR_DEFAULT}) for USERNAME
${PROGNAME} --user USERNAME
- Add pubkey file to a user from the given email address
${PROGNAME} --keyfile /tmp/id_ed25519.pub --mail last.name@domain.org
- Add given pubkey for USERNAME
${PROGNAME} --key "ssh-ed25519 AAAAC3NzaC1…" --user USERNAME
- Add pubkey to specific authorized_keys location
${PROGNAME} --user USERNAME --authorized_keys /etc/ssh/authorized_keys
- Limit "only" port and X11 forwarding on the public key
${PROGNAME} --user USERNAME --option "no-port-forwarding,no-X11-forwarding"
OPTIONS:
-a,--authorized,--authorized_keys
Set authorized_keys directory to use.
Default : "${SSHD_AUTH_KEY_DIR_DEFAULT}
-d,--debug
Enable debug messages.
-h,--help
Print this help message.
-e,--email,--mail
Email address to request username with ldapsearch.
-f,--file,--keyfile
Path to a file containing the pubkey to allow.
Default : ${SSH_PUBKEY_FILE_DEFAULT}
-k,--key
Complete pubkey with algorithm and the key.
If not set, fall back to default ssh pubkey file (${SSH_PUBKEY_FILE_DEFAULT} .
-o,--option
Options to add to public key. Default basically allow only scp :
${SSH_PUBKEY_OPTION_DEFAULT}
-u,--user,--username
Username that should have access to the system with the given pubkey.
If not set, fall back to an LDAP request with email address to guess the username.
HELP
}
# }}}
debug_message() { # {{{
local_debug_message="${1}"
## Print message if DEBUG is enable (=0)
[ "${DEBUG}" -eq "0" ] && printf "${COLOR_DEBUG}%b${RESET}\n" "DEBUG ${PROGNAME}: ${local_debug_message}"
unset local_debug_message
return 0
}
# }}}
error_message() { # {{{
local_error_message="${1}"
local_error_code="${2}"
## Print message
printf '%b\n' "ERROR ${PROGNAME}: ${RED}${local_error_message}${RESET}"
exit "${local_error_code:=66}"
}
# }}}
define_vars() { # {{{
## If USER_NAME wasn't defined (argument) {{{
debug_message "define_vars \
Check USER related vars."
if is_var_empty "${USER_NAME}"; then
if is_var_defined "${USER_EMAIL}"; then
## Search with email address on the ldap
debug_message "define_vars \
Use given email address (${RED}${USER_EMAIL}${COLOR_DEBUG}) to request LDAP server for a matching username."
USER_NAME="$(ldapsearch -x -ZZ -s one "(mail=${USER_EMAIL})" \
| awk '/^uid: / { print $2 }')"
readonly USER_NAME
is_var_defined "${USER_NAME}" \
|| error_message "Error with username (${USER_NAME}), please verify the given email address (${USER_EMAIL})." 01
else
error_message "Expect USERNAME informations (please use --user or --mail option)." 02
fi
fi
debug_message "define_vars \
->${RED}${USER_NAME}${COLOR_DEBUG}<- username will be used."
## }}}
## If SSHD_AUTH_KEY_DIR wasn't defined (argument) {{{
debug_message "define_vars \
Check authorized_keys related var."
if is_var_empty "${SSHD_AUTH_KEY_DIR}"; then
SSHD_AUTH_KEY_DIR="${SSHD_AUTH_KEY_DIR_DEFAULT}"
readonly SSHD_AUTH_KEY_DIR
fi
debug_message "define_vars \
->${RED}${SSHD_AUTH_KEY_DIR}${COLOR_DEBUG}<- directory will be used to store public keys."
## }}}
## If SSH_PUBKEY wasn't defined (argument) {{{
debug_message "define_vars \
Check SSH_PUBKEY related vars."
if is_var_empty "${SSH_PUBKEY}"; then
## Try to get SSH_PUBKEY from SSH_PUBKEY_FILE {{{
### If SSH_PUBKEY_FILE wasn't defined {{{
if is_var_empty "${SSH_PUBKEY_FILE}"; then
### Use default value for SSH_PUBKEY_FILE
SSH_PUBKEY_FILE="${SSH_PUBKEY_FILE_DEFAULT}"
readonly SSH_PUBKEY_FILE
fi
### }}}
debug_message "define_vars \
->${RED}${SSH_PUBKEY_FILE}${COLOR_DEBUG}<- file will be used to provide user's public key."
### If SSH_PUBKEY_FILE doesn't exists {{{
#### AND Exit with error
is_file_absent "${SSH_PUBKEY_FILE}" \
&& error_message "Error, public key file (${SSH_PUBKEY_FILE}) doesn't exists (please check --keyfile option)." 03
### }}}
SSH_PUBKEY="$(grep --max-count=1 -- "ssh-" "${SSH_PUBKEY_FILE}" \
|| error_message "Error, can't retrieve ssh pubkey content from pubkey file (${SSH_PUBKEY_FILE}" 04)"
### }}}
fi
# Can't use default debug_message function due to some limitations (size ?)
if [ "${DEBUG}" -eq "0" ]; then
printf "${COLOR_DEBUG}%b${RESET}\n" "DEBUG ${PROGNAME} : define_vars ->"
printf "${RED}%b${RESET}\n" "${SSH_PUBKEY}"
printf "${COLOR_DEBUG}%b${RESET}\n" "<- key will be used as user's public key."
fi
## }}}
## If SSH_PUBKEY_OPTION wasn't defined (argument) {{{
debug_message "define_vars \
Check ssh_pubkey OPTIONS related var."
if is_var_empty "${SSH_PUBKEY_OPTION}"; then
### Use default value for SSH_PUBKEY_OPTION
SSH_PUBKEY_OPTION="${SSH_PUBKEY_OPTION_DEFAULT}"
readonly SSH_PUBKEY_OPTION
fi
debug_message "define_vars \
->${RED}${SSH_PUBKEY_OPTION}${COLOR_DEBUG}<- options will be used for public key."
## }}}
}
# }}}
is_var_defined() { # {{{
## Test if at least one of the given var(s) is defined
## Return False by default
return_var_defined="1"
## Total number of variables to test
local_total_var="${#}"
loop_count_var_defined="0"
## While it remains a variable to test
while [ "${local_total_var}" -gt "${loop_count_var_defined}" ]; do
debug_message "is_var_defined \
Test var: ${1}."
### Test if this is defined and set return value to True
[ -n "${1}" ] && return_var_defined="0"
### Increase the number of tested variables
loop_count_var_defined=$((loop_count_var_defined+1))
### Shift to the next variable
shift
done
unset local_total_var
unset loop_count_var_defined
return "${return_var_defined}"
}
# }}}
is_var_empty() { # {{{
## Test if at least one of the given var(s) is empty
## Return False by default
return_var_empty="1"
## Total number of variables to test
local_total_var="${#}"
loop_count_var_empty="0"
## While it remains a variable to test
while [ "${local_total_var}" -gt "${loop_count_var_empty}" ]; do
debug_message "is_var_empty \
Test var: ${1}."
### Test if this is empty and set return value to True
[ -z "${1}" ] && return_var_empty="0"
### Increase the number of tested variables
loop_count_var_empty=$((loop_count_var_empty+1))
### Shift to the next variable
shift
done
unset local_total_var
unset loop_count_var_defined
return "${return_var_empty}"
}
# }}}
is_directory_absent() { # {{{
local_directory_absent="${1}"
## Directory doesn't exists by default
return_is_directory_absent="0"
### Check if the directory exists
# shellcheck disable=SC2086
if test -d "${local_directory_absent}"; then
return_is_directory_absent="1"
debug_message "is_directory_absent \
The directory ${RED}${local_directory_absent}${COLOR_DEBUG} exists."
else
return_is_directory_absent="0"
debug_message "is_directory_absent \
The directory ${RED}${local_directory_absent}${COLOR_DEBUG} doesn't exist."
fi
return "${return_is_directory_absent}"
}
# }}}
is_file_absent() { # {{{
local_file_absent="${1}"
## File exists by default
return_is_file_absent="1"
### Check if the file exists
# shellcheck disable=SC2086
if find ${local_file_absent} > /dev/null 2>&1; then
return_is_file_absent="1"
debug_message "is_file_absent \
The file ${RED}${local_file_absent}${COLOR_DEBUG} exists."
else
return_is_file_absent="0"
debug_message "is_file_absent \
The file ${RED}${local_file_absent}${COLOR_DEBUG} doesn't exist."
fi
return "${return_is_file_absent}"
}
# }}}
main() { # {{{
## Define all vars
define_vars
## Check if user exists {{{
if id -u "${USER_NAME}" >/dev/null 2>&1; then
debug_message "main \
The user ${RED}${USER_NAME}${COLOR_DEBUG} was found on the system."
else
### OR Exit with error message
error_message "According to given informations the user (${USER_NAME}) wasn't found on this system.\nPlease check the value passed to --user or --mail options." 11
fi
## }}}
## Manage authorized_keys file {{{
USER_AUTHORIZED_KEYS_FILE="${SSHD_AUTH_KEY_DIR}/${USER_NAME}"
## Ensure authorized_keys directory exists {{{
if is_directory_absent "${SSHD_AUTH_KEY_DIR}"; then
debug_message "main \
Create authorized_keys directory (${RED}${SSHD_AUTH_KEY_DIR}${COLOR_DEBUG})."
mkdir --parents -- "${SSHD_AUTH_KEY_DIR}" \
|| error_message "Error when creating authorized_keys directory (${SSHD_AUTH_KEY_DIR})." 21
fi
## }}}
## Ensure user's authorized_keys file exists {{{
if is_file_absent "${USER_AUTHORIZED_KEYS_FILE}"; then
debug_message "main \
Create authorized_keys file for ${USER_NAME} (${RED}${USER_AUTHORIZED_KEYS_FILE}${COLOR_DEBUG})."
touch -- "${USER_AUTHORIZED_KEYS_FILE}" \
|| error_message "Error when creating ${USER_NAME}'s authorized_keys file (${USER_AUTHORIZED_KEYS_FILE})." 22
fi
## }}}
## }}}
## If pubkey is already in authorized_keys file {{{
## --fixed-strings to be able to manage backslash
### AND Ensure it's set with the right options
### AND Exit the script
if grep --fixed-strings --word-regexp --quiet -- "${SSH_PUBKEY}" "${USER_AUTHORIZED_KEYS_FILE}"; then
sed -i "s|.*${SSH_PUBKEY}.*|${SSH_PUBKEY_OPTION} ${SSH_PUBKEY}|" "${USER_AUTHORIZED_KEYS_FILE}" \
|| error_message "Error during SSH_PUBKEY replacement with expected options in authorized_keys file (${USER_AUTHORIZED_KEYS_FILE})." 31
debug_message "The given pubkey was already present in authorized_keys file (${RED}${USER_AUTHORIZED_KEYS_FILE}${COLOR_DEBUG}) and now have the expected permissions."
## }}}
## If pubkey isn't already in authorized_keys file {{{
### Add it with expected options
else
printf "%s" "${SSH_PUBKEY_OPTION} ${SSH_PUBKEY}" >> "${USER_AUTHORIZED_KEYS_FILE}" \
|| error_message "Error while adding SSH_PUBKEY with expected options in authorized_keys file (${USER_AUTHORIZED_KEYS_FILE})." 32
debug_message "The given pubkey is now present in authorized_keys file (${RED}${USER_AUTHORIZED_KEYS_FILE}${COLOR_DEBUG}) with expected permissions."
fi
## }}}
## If the key is present, exit with success {{{
## --fixed-strings to be able to manage backslash
if grep --fixed-strings --word-regexp --quiet -- "${SSH_PUBKEY_OPTION} ${SSH_PUBKEY}" "${USER_AUTHORIZED_KEYS_FILE}"; then
printf "%b" "The given pubkey (${RED}${SSH_PUBKEY_FILE}${RESET}) for ${RED}${USER_NAME}${RESET} user was successfully added to it's authorized_keys file (${RED}${USER_AUTHORIZED_KEYS_FILE}${COLOR_DEBUG})." \
&& exit 0
else
error_message "Error with verification of user authorized_keys content (${USER_AUTHORIZED_KEYS_FILE}). Can't detect the new SSH_PUBKEY." 33
fi
## }}}
}
# }}}
# Manage arguments # {{{
# This code can't be in a function due to argument management
if [ ! "${NBARGS}" -eq "0" ]; then
manage_arg="0"
## If the first argument is not an option
if ! printf -- '%s' "${1}" | grep -q -E -- "^-+";
then
## Print help message and exit
printf '%b\n' "${RED}Invalid option: ${1}${RESET}"
printf '%b\n' "---"
usage
exit 1
fi
# Parse all options (start with a "-") one by one
while printf -- '%s' "${1}" | grep -q -E -- "^-+"; do
case "${1}" in
-a|--authorized_keys|--authorized ) ## Define SSHD_AUTH_KEY_DIR with given arg
## Move to the next argument
shift
## Define var
readonly SSHD_AUTH_KEY_DIR="${1}"
;;
-d|--debug ) ## debug
DEBUG=0
;;
-h|--help ) ## help
usage
## Exit after help informations
exit 0
;;
-e|--email|--mail ) ## Define USER_EMAIL with given arg
## Move to the next argument
shift
## Define var
readonly USER_EMAIL="${1}"
;;
-f|--file|--keyfile ) ## Define SSH_PUBKEY_FILE with given arg
## Move to the next argument
shift
## Define var
readonly SSH_PUBKEY_FILE="${1}"
;;
-o|--option|--options ) ## Define SSH_PUBKEY_OPTION with given arg
## Move to the next argument
shift
## Define var
readonly SSH_PUBKEY_OPTION="${1}"
;;
-k|--key ) ## Define SSH_PUBKEY with given arg
## Move to the next argument
shift
## Define var
readonly SSH_PUBKEY="${1}"
;;
-u|--user|--username ) ## Define USER_NAME with given arg
## Move to the next argument
shift
## Define var
readonly USER_NAME="${1}"
;;
* ) ## unknow option
printf '%b\n' "${RED}Invalid option: ${1}${RESET}"
printf '%b\n' "---"
usage
exit 1
;;
esac
debug_message "Arguments management \
${RED}${1}${COLOR_DEBUG} option managed."
## Move to the next argument
shift
manage_arg=$((manage_arg+1))
done
debug_message "Arguments management \
${RED}${manage_arg}${COLOR_DEBUG} argument(s) successfully managed."
else
debug_message "Arguments management \
No arguments/options to manage."
fi
# }}}
# Call main
main
# The script should never reach this line
error_message "Unexpected end of the script, please use '--debug' option to have more informations" 255