2022-08-18 11:18:58 +02:00
#!/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 {{{
2022-12-19 16:15:54 +01:00
## --fixed-strings to be able to manage backslash
2022-08-18 11:18:58 +02:00
### AND Ensure it's set with the right options
### AND Exit the script
2022-12-19 16:15:54 +01:00
if grep --fixed-strings --word-regexp --quiet -- " ${ SSH_PUBKEY } " " ${ USER_AUTHORIZED_KEYS_FILE } " ; then
2022-08-18 11:18:58 +02:00
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
2022-12-19 16:16:27 +01:00
printf "%s\n" " ${ SSH_PUBKEY_OPTION } ${ SSH_PUBKEY } " >> " ${ USER_AUTHORIZED_KEYS_FILE } " \
2022-08-18 11:18:58 +02:00
|| 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 {{{
2022-12-19 16:15:54 +01:00
## --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
2022-08-18 11:18:58 +02:00
## }}}
}
# }}}
# 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