New script to manage ssh_pubkey with Duplicati
This commit is contained in:
parent
87b26d6c6d
commit
bc190d399c
|
@ -0,0 +1,455 @@
|
|||
#!/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
|
Loading…
Reference in New Issue