#!/bin/bash # monkeysphere-server: MonkeySphere server admin tool # # The monkeysphere scripts are written by: # Jameson Rollins # # They are Copyright 2008, and are all released under the GPL, version 3 # or later. ######################################################################## PGRM=$(basename $0) SHARE=${MONKEYSPHERE_SHARE:="/usr/share/monkeysphere"} export SHARE . "${SHARE}/common" || exit 1 VARLIB="/var/lib/monkeysphere" export VARLIB # date in UTF format if needed DATE=$(date -u '+%FT%T') # unset some environment variables that could screw things up unset GREP_OPTIONS # default return code RETURN=0 ######################################################################## # FUNCTIONS ######################################################################## usage() { cat < [options] [args] MonkeySphere server admin tool. subcommands: update-users (u) [USER]... update user authorized_keys files gen-key (g) [HOSTNAME] generate gpg key for the server -l|--length BITS key length in bits (2048) -e|--expire EXPIRE date to expire -r|--revoker FINGERPRINT add a revoker show-fingerprint (f) show server's host key fingerprint publish-key (p) publish server's host key to keyserver diagnostics (d) report on the server's monkeysphere status add-identity-certifier (a) KEYID import and tsign a certification key -n|--domain DOMAIN limit ID certifications to IDs in DOMAIN -t|--trust TRUST trust level of certifier (full) -d|--depth DEPTH trust depth for certifier (1) remove-identity-certifier (r) KEYID remove a certification key list-identity-certifiers (l) list certification keys gpg-authentication-cmd CMD gnupg-authentication command -h|--help|help (h,?) this help EOF } su_monkeysphere_user() { su --preserve-environment "$MONKEYSPHERE_USER" -- -c "$@" } # function to interact with the host gnupg keyring gpg_host() { local returnCode GNUPGHOME="$GNUPGHOME_HOST" export GNUPGHOME # NOTE: we supress this warning because we need the monkeysphere # user to be able to read the host pubring. we realize this might # be problematic, but it's the simplest solution, without too much # loss of security. gpg --no-permission-warning "$@" returnCode="$?" # always reset the permissions on the host pubring so that the # monkeysphere user can read the trust signatures chgrp "$MONKEYSPHERE_USER" "${GNUPGHOME_HOST}/pubring.gpg" chmod g+r "${GNUPGHOME_HOST}/pubring.gpg" return "$returnCode" } # function to interact with the authentication gnupg keyring # FIXME: this function requires basically accepts only a single # argument because of problems with quote expansion. this needs to be # fixed/improved. gpg_authentication() { GNUPGHOME="$GNUPGHOME_AUTHENTICATION" export GNUPGHOME su_monkeysphere_user "gpg $@" } # update authorized_keys for users update_users() { if [ "$1" ] ; then # get users from command line unames="$@" else # or just look at all users if none specified unames=$(getent passwd | cut -d: -f1) fi # set mode MODE="authorized_keys" # set gnupg home GNUPGHOME="$GNUPGHOME_AUTHENTICATION" # check to see if the gpg trust database has been initialized if [ ! -s "${GNUPGHOME}/trustdb.gpg" ] ; then failure "GNUPG trust database uninitialized. Please see MONKEYSPHERE-SERVER(8)." fi # make sure the authorized_keys directory exists mkdir -p "${VARLIB}/authorized_keys" # loop over users for uname in $unames ; do # check all specified users exist if ! getent passwd "$uname" >/dev/null ; then log "----- unknown user '$uname' -----" continue fi # set authorized_user_ids and raw authorized_keys variables, # translating ssh-style path variables authorizedUserIDs=$(translate_ssh_variables "$uname" "$AUTHORIZED_USER_IDS") rawAuthorizedKeys=$(translate_ssh_variables "$uname" "$RAW_AUTHORIZED_KEYS") # if neither is found, skip user if [ ! -s "$authorizedUserIDs" ] ; then if [ "$rawAuthorizedKeys" = '-' -o ! -s "$rawAuthorizedKeys" ] ; then continue fi fi log "----- user: $uname -----" # exit if the authorized_user_ids file is empty if ! check_key_file_permissions "$uname" "$AUTHORIZED_USER_IDS" ; then log "Improper permissions on authorized_user_ids file path." continue fi # check permissions on the authorized_keys file path if ! check_key_file_permissions "$uname" "$RAW_AUTHORIZED_KEYS" ; then log "Improper permissions on authorized_keys file path path." continue fi # make temporary directory TMPDIR=$(mktemp -d) # trap to delete temporary directory on exit trap "rm -rf $TMPDIR" EXIT # create temporary authorized_user_ids file TMP_AUTHORIZED_USER_IDS="${TMPDIR}/authorized_user_ids" touch "$TMP_AUTHORIZED_USER_IDS" # create temporary authorized_keys file AUTHORIZED_KEYS="${TMPDIR}/authorized_keys" touch "$AUTHORIZED_KEYS" # set restrictive permissions on the temporary files # FIXME: is there a better way to do this? chmod 0700 "$TMPDIR" chmod 0600 "$AUTHORIZED_KEYS" chmod 0600 "$TMP_AUTHORIZED_USER_IDS" chown -R "$MONKEYSPHERE_USER" "$TMPDIR" # if the authorized_user_ids file exists... if [ -s "$authorizedUserIDs" ] ; then # copy user authorized_user_ids file to temporary # location cat "$authorizedUserIDs" > "$TMP_AUTHORIZED_USER_IDS" # export needed variables export AUTHORIZED_KEYS export TMP_AUTHORIZED_USER_IDS # process authorized_user_ids file, as monkeysphere # user su_monkeysphere_user \ ". ${SHARE}/common; process_authorized_user_ids $TMP_AUTHORIZED_USER_IDS" RETURN="$?" fi # add user-controlled authorized_keys file path if specified if [ "$rawAuthorizedKeys" != '-' -a -s "$rawAuthorizedKeys" ] ; then log -n "adding raw authorized_keys file... " cat "$rawAuthorizedKeys" >> "$AUTHORIZED_KEYS" loge "done." fi # openssh appears to check the contents of the # authorized_keys file as the user in question, so the # file must be readable by that user at least. # FIXME: is there a better way to do this? chown root "$AUTHORIZED_KEYS" chgrp $(getent passwd "$uname" | cut -f4 -d:) "$AUTHORIZED_KEYS" chmod g+r "$AUTHORIZED_KEYS" # move the resulting authorized_keys file into place mv -f "$AUTHORIZED_KEYS" "${VARLIB}/authorized_keys/${uname}" # destroy temporary directory rm -rf "$TMPDIR" done } # generate server gpg key gen_key() { local keyType local keyLength local keyUsage local keyExpire local revoker local hostName local userID local keyParameters local fingerprint # set default key parameter values keyType="RSA" keyLength="2048" keyUsage="auth" keyExpire= revoker= # get options TEMP=$(getopt -o e:l:r -l expire:,length:,revoker: -n "$PGRM" -- "$@") if [ $? != 0 ] ; then exit 1 fi # Note the quotes around `$TEMP': they are essential! eval set -- "$TEMP" while true ; do case "$1" in -l|--length) keyLength="$2" shift 2 ;; -e|--expire) keyExpire="$2" shift 2 ;; -r|--revoker) revoker="$2" shift 2 ;; --) shift ;; *) break ;; esac done hostName=${1:-$(hostname --fqdn)} userID="ssh://${hostName}" # check for presense of key with user ID if gpg_host --list-key ="$userID" > /dev/null 2>&1 ; then failure "Key for '$userID' already exists" fi # prompt about key expiration if not specified if [ -z "$keyExpire" ] ; then cat < = key expires in n days w = key expires in n weeks m = key expires in n months y = key expires in n years EOF while [ -z "$keyExpire" ] ; do read -p "Key is valid for? (0) " keyExpire if ! test_gpg_expire ${keyExpire:=0} ; then echo "invalid value" unset keyExpire fi done elif ! test_gpg_expire "$keyExpire" ; then failure "invalid key expiration value '$keyExpire'." fi # set key parameters keyParameters=$(cat < "${VARLIB}/ssh_host_rsa_key") log "Private SSH host key output to file: ${VARLIB}/ssh_host_rsa_key" } # gpg output key fingerprint fingerprint_server_key() { gpg_host --fingerprint --list-secret-keys } # publish server key to keyserver publish_server_key() { read -p "Really publish key to $KEYSERVER? (y/N) " OK; OK=${OK:=N} if [ ${OK/y/Y} != 'Y' ] ; then failure "aborting." fi # publish host key # FIXME: need to figure out better way to identify host key # dummy command so as not to publish fakes keys during testing # eventually: #gpg_authentication "--keyserver $KEYSERVER --send-keys =ssh://$(hostname -f)" echo "NOT PUBLISHED (to avoid permanent publication errors during monkeysphere development)." echo "The following command should publish the key:" echo "monkeysphere-server gpg-authentication-cmd '--keyserver $KEYSERVER --send-keys =ssh://$(hostname -f)'" exit 255 } diagnostics() { # * check on the status and validity of the key and public certificates local seckey local keysfound local curdate local warnwindow local warndate local create local expire local uid local fingerprint local badhostkeys seckey=$(gpg_host --list-secret-keys --fingerprint --with-colons --fixed-list-mode) keysfound=$(echo "$seckey" | grep -c ^sec:) curdate=$(date +%s) # warn when anything is 2 months away from expiration warnwindow='2 months' warndate=$(date +%s -d "$warnwindow") echo "Checking host GPG key..." if (( "$keysfound" < 1 )); then echo "! No host key found." echo " - Recommendation: run 'monkeysphere-server gen-key'" elif (( "$keysfound" > 1 )); then echo "! More than one host key found?" # FIXME: recommend a way to resolve this else create=$(echo "$seckey" | grep ^sec: | cut -f6 -d:) expire=$(echo "$seckey" | grep ^sec: | cut -f7 -d:) fingerprint=$(echo "$seckey" | grep ^fpr: | head -n1 | cut -f10 -d:) # check for key expiration: if [ "$expire" ]; then if (( "$expire" < "$curdate" )); then echo "! Host key is expired." # FIXME: recommend a way to resolve this other than re-keying? elif (( "$expire" < "$warndate" )); then echo "! Host key expires in less than $warnwindow:" $(date -d "$(( $expire - $curdate )) seconds" +%F) # FIXME: recommend a way to resolve this? fi fi # and weirdnesses: if [ "$create" ] && (( "$create" > "$curdate" )); then echo "! Host key was created in the future(?!). Is your clock correct?" echo " - Recommendation: Check clock ($(date +%F_%T)); use NTP?" fi # check for UserID expiration: echo "$seckey" | grep ^uid: | cut -d: -f6,7,10 | \ while IFS=: read create expire uid ; do # FIXME: should we be doing any checking on the form # of the User ID? Should we be unmangling it somehow? if [ "$create" ] && (( "$create" > "$curdate" )); then echo "! User ID '$uid' was created in the future(?!). Is your clock correct?" echo " - Recommendation: Check clock ($(date +%F_%T)); use NTP?" fi if [ "$expire" ] ; then if (( "$expire" < "$curdate" )); then echo "! User ID '$uid' is expired." # FIXME: recommend a way to resolve this elif (( "$expire" < "$warndate" )); then echo "! User ID '$uid' expires in less than $warnwindow:" $(date -d "$(( $expire - $curdate )) seconds" +%F) # FIXME: recommend a way to resolve this fi fi done # FIXME: verify that the host key is properly published to the # keyservers (do this with the non-privileged user) # FIXME: check that there are valid, non-expired certifying signatures # attached to the host key after fetching from the public keyserver # (do this with the non-privileged user as well) # FIXME: propose adding a revoker to the host key if none exist (do we # have a way to do that after key generation?) # Ensure that the ssh_host_rsa_key file is present and non-empty: echo "Checking host SSH key..." if [ ! -s "${VARLIB}/ssh_host_rsa_key" ] ; then echo "! The host key as prepared for SSH (${VARLIB}/ssh_host_rsa_key) is missing or empty." else if [ $(stat -c '%a' "${VARLIB}/ssh_host_rsa_key") != 600 ] ; then echo "! Permissions seem wrong for ${VARLIB}/ssh_host_rsa_key -- should be 0600." fi # propose changes needed for sshd_config (if any) if ! grep -q "^HostKey[[:space:]]\+${VARLIB}/ssh_host_rsa_key$" /etc/ssh/sshd_config; then echo "! /etc/ssh/sshd_config does not point to the monkeysphere host key (${VARLIB}/ssh_host_rsa_key)." echo " - Recommendation: add a line to /etc/ssh/sshd_config: 'HostKey ${VARLIB}/ssh_host_rsa_key'" fi if badhostkeys=$(grep -i '^HostKey' | grep -q -v "^HostKey[[:space:]]\+${VARLIB}/ssh_host_rsa_key$") ; then echo "! /etc/sshd_config refers to some non-monkeysphere host keys:" echo "$badhostkeys" echo " - Recommendation: remove the above HostKey lines from /etc/ssh/sshd_config" fi fi fi # FIXME: look at the ownership/privileges of the various keyrings, # directories housing them, etc (what should those values be? can # we make them as minimal as possible?) # FIXME: look to see that the ownertrust rules are set properly on the # authentication keyring # FIXME: make sure that at least one identity certifier exists echo "Checking for MonkeySphere-enabled public-key authentication for users ..." # Ensure that User ID authentication is enabled: if ! grep -q "^AuthorizedKeysFile[[:space:]]\+${VARLIB}/authorized_keys/%u$" /etc/ssh/sshd_config; then echo "! /etc/ssh/sshd_config does not point to monkeysphere authorized keys." echo " - Recommendation: add a line to /etc/ssh/sshd_config: 'AuthorizedKeysFile ${VARLIB}/authorized_keys/%u'" fi if badauthorizedkeys=$(grep -i '^AuthorizedKeysFile' | grep -q -v "^AuthorizedKeysFile[[:space:]]\+${VARLIB}/authorized_keys/%u$") ; then echo "! /etc/sshd_config refers to non-monkeysphere authorized_keys files:" echo "$badauthorizedkeys" echo " - Recommendation: remove the above AuthorizedKeysFile lines from /etc/ssh/sshd_config" fi } # retrieve key from web of trust, import it into the host keyring, and # ltsign the key in the host keyring so that it may certify other keys add_certifier() { local domain local trust local depth local keyID local fingerprint local ltsignCommand local trustval # set default values for trust depth and domain domain= trust=full depth=1 # get options TEMP=$(getopt -o n:t:d: -l domain:,trust:,depth: -n "$PGRM" -- "$@") if [ $? != 0 ] ; then exit 1 fi # Note the quotes around `$TEMP': they are essential! eval set -- "$TEMP" while true ; do case "$1" in -n|--domain) domain="$2" shift 2 ;; -t|--trust) trust="$2" shift 2 ;; -d|--depth) depth="$2" shift 2 ;; --) shift ;; *) break ;; esac done keyID="$1" if [ -z "$keyID" ] ; then failure "You must specify the key ID of a key to add." fi export keyID # get the key from the key server gpg_authentication "--keyserver $KEYSERVER --recv-key '$keyID'" # get the full fingerprint of a key ID fingerprint=$(gpg_authentication "--list-key --with-colons --with-fingerprint $keyID" | \ grep '^fpr:' | grep "$keyID" | cut -d: -f10) echo "key found:" gpg_authentication "--fingerprint $fingerprint" echo "Are you sure you want to add this key as a certifier of" read -p "users on this system? (y/N) " OK; OK=${OK:-N} if [ "${OK/y/Y}" != 'Y' ] ; then failure "aborting." fi # export the key to the host keyring gpg_authentication "--export $keyID" | gpg_host --import if [ "$trust" == marginal ]; then trustval=1 elif [ "$trust" == full ]; then trustval=2 else failure "trust value requested ('$trust') was unclear (only 'marginal' or 'full' are supported)" fi # ltsign command # NOTE: *all* user IDs will be ltsigned ltsignCommand=$(cat <