456 lines
14 KiB
Bash
456 lines
14 KiB
Bash
|
#!/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 {{{
|
|||
|
### AND Ensure it's set with the right options
|
|||
|
### AND Exit the script
|
|||
|
if grep --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 {{{
|
|||
|
grep --word-regexp --quiet -- "${SSH_PUBKEY_OPTION} ${SSH_PUBKEY}" "${USER_AUTHORIZED_KEYS_FILE}" \
|
|||
|
&& 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
|
|||
|
## }}}
|
|||
|
}
|
|||
|
# }}}
|
|||
|
|
|||
|
# 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
|