From 6b83b50141e37e2926333dc1aa987bfb50317b5b Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Fri, 23 May 2008 19:01:50 -0400 Subject: [PATCH] major overhaul of rhesus: - much more sophisticated validity checking of keys/uids - broke out more functions - cleaned-up/simplified code - changed to new variable naming standard --- doc/MonkeySpec | 2 +- doc/README | 49 +++++- doc/git init | 128 --------------- monkeysphere.conf | 8 - rhesus/rhesus | 389 +++++++++++++++++++++++++--------------------- 5 files changed, 257 insertions(+), 319 deletions(-) delete mode 100644 doc/git init diff --git a/doc/MonkeySpec b/doc/MonkeySpec index 7a19df0..c36e7de 100644 --- a/doc/MonkeySpec +++ b/doc/MonkeySpec @@ -102,4 +102,4 @@ Write manpage for gpgkey2ssh gpg private key (start with passwordless) to PEM encoded private key: perl libraries, libopencdk / gnutls, gpgme setup remote git repo think through / plan merging of known_hosts (& auth_keys?) -think about policies and their representation \ No newline at end of file +think about policies and their representation diff --git a/doc/README b/doc/README index 4c70d1d..9dc8753 100644 --- a/doc/README +++ b/doc/README @@ -1,5 +1,48 @@ - Monkeysphere - ------------ +Monkeysphere README +------------------- +Default file locations: -This is the README! +MS_HOME=~/.config/monkeysphere +STAGING_AREA=$MS_HOME +GNUPGHOME=~/.gnupg +$MS_HOME/monkeysphere.conf +$MS_HOME/auth_host_ids +$MS_HOME/auth_user_ids +$STAGING_AREA/host_keys/KEYHASH +$STAGING_AREA/known_hosts +$STAGING_AREA/user_keys/KEYHASH +$STAGING_AREA/authorized_keys + +For a user to update their ms known_hosts file: + +$ rhesus --known_hosts + +For a user to update their ms authorized_keys file: + +$ rhesus --authorized_keys + +A system can maintain ms authorized_keys files for it's users. Some +different variables need to be defined to help manage this. The way +this is done is by first defining a new MS_HOME: + +MS_HOME=/etc/monkeysphere + +This directory would then have a monkeysphere.conf which defines the +following variables: + +AUTH_USER_FILE="$MS_HOME"/auth_user_ids/"$USER" +STAGING_AREA=/var/lib/monkeysphere/stage/$USER +GNUPGHOME=$MS_HOME/gnupg + +To update the ms authorized_keys file for user "foo", the system would +then run the following: + +# USER=foo MS_HOME=/etc/monkeysphere rhesus --authorized_keys + +To update the ms authorized_keys file for all users on the the system: + +MS_HOME=/etc/monkeysphere +for USER in $(ls -1 /etc/monkeysphere/auth_user_ids) ; do + rhesus --authorized_keys +done diff --git a/doc/git init b/doc/git init deleted file mode 100644 index 7ba5071..0000000 --- a/doc/git init +++ /dev/null @@ -1,128 +0,0 @@ -remote$ mkdir public_html/git -(etch) -remote$ GIT_DIR=~/public_html/git/monkeysphere.git git init-db -remote$ cd ~/public_html/git/monkeysphere.git -remote$ chmod a+x hooks/post-update -# NOT SURE IF THIS IS NEEDED: remote$ git-update-server-info -fetch = +refs/heads/*:refs/remotes/dkg/* - -(newer) -remote$ mkdir -p public_html/git/monkey.git -remote$ cd public_html/git/monkey.git -remote$ git --bare init -remote$ chmod a+x hooks/post-update -remote$ git-update-server-info - -(new way! no origin/) -$ cd ~/src -$ mkdir monkeysphere -$ cd monkeysphere -$ git init -$ git remote add -f mlcastle http://git.mlcastle.net/monkeysphere.git/ -$ git remote add grunt grunt:/whatever -$ git config remote.grunt.push "+refs/heads/*" -$ git merge mlcastle/master -$ git push grunt - -(old way!) -(in ~/src or wherever) -local$ git clone http://git.mlcastle.net/monkeysphere.git/ monkeysphere -local$ cd monkeysphere - -.git/config: - -[core] - repositoryformatversion = 0 - filemode = true - bare = false - logallrefupdates = true - -## THIS ONE NEEDS TO BE CHANGED TO YOUR REMOTE URI -[remote "post"] - url = YOUR-REMOTE-URL/git/monkeysphere.git - push = +refs/heads/* -### THE ABOVE ONE NEEDS TO BE CHANGED - -[remote "mlcastle"] - url = http://git.mlcastle.net/monkeysphere.git/ - fetch = +refs/heads/*:refs/remotes/mlcastle/* - -[remote "jrollins"] - url = http://lair.fifthhorseman.net/~jrollins/git/monkeysphere.git/ - fetch = +refs/heads/*:refs/remotes/jrollins/* - -[remote "dkg"] - url = http://lair.fifthhorseman.net/~dkg/git/monkeysphere.git/ - fetch = +refs/heads/*:refs/remotes/dkg/* - -[remote "mjgoins"] SEE: dkg, jrollins, etc. - -[remote "micah"] - url = http://micah.riseup.net/git/monkeysphere.git - fetch = +refs/heads/*:refs/remotes/micah/* - -[remote "enw"] - url = http://lair.fifthhorseman.net/~enw/git/monkeysphere.git/ - fetch = +refs/heads/*:refs/remotes/enw/* - -[remote "rossg"] - url = http://lair.fifthhorseman.net/~rossg/git/monkeysphere.git/ - fetch = +refs/heads/*:refs/remotes/rossg/* - -[remote "greg"] - url = http://lair.fifthhorseman.net/~greg/git/monkeysphere.git/ - fetch = +refs/heads/*:refs/remotes/greg/* - blood type = - ------------------ -[remote "upload"] - url = ssh://z.mlcastle.net/var/www/git/monkeysphere.git/ - push = +refs/heads/* - - -$ git fetch dkg -$ git checkout master -$ git merge remotes/dkg/master -$ git push post - - - - - - - -grunt's fingerprint: be:43:9c:03:9c:04:1a:97:7a:61:8a:fe:71:9d:6c:67 -(grunt is lair.fifthhorseman.net) - -for foo in $(git remote); do git fetch $foo; done - - - -set mainfont {Arial 12} -set textfont { Courier 12} -set uifont {Arial 10 bold} -set tabstop 8 -set findmergefiles 0 -set maxgraphpct 50 -set maxwidth 16 -set cmitmode patch -set wrapcomment none -set showneartags 1 -set showlocalchanges 1 -set datetimeformat {%Y-%m-%d %H:%M:%S} -set limitdiffs 1 -set bgcolor white -set fgcolor black -set colors {green red blue magenta darkgrey brown orange} -set diffcolors {red "#00a000" blue} -set diffcontext 3 -set selectbgcolor gray85 -set geometry(main) 1280x936+14+28 -set geometry(topwidth) 1278 -set geometry(topheight) 286 -set geometry(pwsash0) "638 1" -set geometry(pwsash1) "903 1" -set geometry(botwidth) 1001 -set geometry(botheight) 638 -set permviews {} - diff --git a/monkeysphere.conf b/monkeysphere.conf index a54b6bd..cd5e3b2 100644 --- a/monkeysphere.conf +++ b/monkeysphere.conf @@ -13,11 +13,3 @@ GNUPGHOME=/etc/monkeysphere/gnupg # gpg keyserver to search for keys KEYSERVER=subkeys.pgp.net - -# acceptable key capabilities for user keys -# can be any combination of: -# e = encrypt -# s = sign -# c = certify -# a = authentication -REQUIRED_KEY_CAPABILITY='sca' diff --git a/rhesus/rhesus b/rhesus/rhesus index fc2f2f5..2e05dfd 100755 --- a/rhesus/rhesus +++ b/rhesus/rhesus @@ -2,20 +2,6 @@ # rhesus: monkeysphere authorized_keys/known_hosts generating script # -# When run as a normal user, no special configuration is needed. -# -# When run as an administrator to update users' authorized_keys files, -# the following environment variables should be defined first: -# -# MS_CONF=/etc/monkeysphere/monkeysphere.conf -# USER=foo -# -# ie: -# -# for USER in $(ls -1 /home) ; do -# MS_CONF=/etc/monkeysphere/monkeysphere.conf rhesus --authorized_keys -# done -# # Written by # Jameson Rollins # @@ -23,6 +9,10 @@ CMD=$(basename $0) +######################################################################## +# FUNCTIONS +######################################################################## + usage() { cat </dev/null 2>&1 +} + +# convert escaped characters from gpg output back into original +# character +# FIXME: undo all escape character translation in with-colons gpg output +unescape() { + echo "$1" | sed 's/\\x3a/:/' +} + +# stand in until we get dkg's gpg2ssh program +gpg2ssh_tmp() { + local mode + local keyID + mode="$1" - keyid="$2" + keyID="$2" + userID="$3" + if [ "$mode" = '--authorized_keys' -o "$mode" = '-a' ] ; then - gpgkey2ssh "$keyid" | sed -e "s/COMMENT/$userid/" + gpgkey2ssh "$keyID" | sed -e "s/COMMENT/$userID/" elif [ "$mode" = '--known_hosts' -o "$mode" = '-k' ] ; then - echo -n "$userid "; gpgkey2ssh "$keyid" | sed -e 's/ COMMENT//' + echo -n "$userID "; gpgkey2ssh "$keyID" | sed -e 's/ COMMENT//' + fi +} + +# userid and key policy checking +# the following checks policy on the returned keys +# - checks that full key has appropriate valididy (u|f) +# - checks key has appropriate capability (E|A) +# - checks that particular desired user id has appropriate validity +# see /usr/share/doc/gnupg/DETAILS.gz +# FIXME: add some more status output +# expects global variable: "mode" +process_user_id() { + local userID + local cacheDir + local keyOK + local keyCapability + local keyFingerprint + local userIDHash + + userID="$1" + cacheDir="$2" + + # fetch all keys from keyserver + # if none found, break + if ! gpg_fetch_keys "$userID" ; then + echo " no keys found." + return fi + + # some crazy piping here that takes the output of gpg and + # pipes it into a "while read" loop that reads each line + # of standard input one-by-one. + gpg --fixed-list-mode --list-key --with-colons \ + --with-fingerprint ="$userID" 2> /dev/null | \ + cut -d : -f 1,2,5,10,12 | \ + while IFS=: read -r type validity keyid uidfpr capability ; do + # process based on record type + case $type in + 'pub') + # new key, wipe the slate + keyOK= + keyCapability= + keyFingerprint= + # check primary key validity + if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then + continue + fi + # check capability is not Disabled... + if echo "$capability" | grep -q 'D' ; then + continue + fi + # check capability is Encryption and Authentication + # FIXME: make more flexible capability specification + # (ie. in conf file) + if echo "$capability" | grep -q -v 'E' ; then + if echo "$capability" | grep -q -v 'A' ; then + continue + fi + fi + keyCapability="$capability" + keyOK=true + keyID="$keyid" + ;; + 'fpr') + # if key ok, get fingerprint + if [ "$keyOK" ] ; then + keyFingerprint="$uidfpr" + fi + ;; + 'uid') + # check key ok and we have key fingerprint + if [ -z "$keyOK" -o -z "$keyFingerprint" ] ; then + continue + fi + # check key validity + if [ "$validity" != 'u' -a "$validity" != 'f' ] ; then + continue + fi + # check the uid matches + if [ "$(unescape "$uidfpr")" != "$userID" ] ; then + continue + fi + # convert the key + # FIXME: needs to apply extra options if specified + echo -n " valid key found; generating ssh key(s)... " + userIDHash=$(echo "$userID" | sha1sum | awk '{ print $1 }') + # export the key with gpg2ssh + #gpg --export "$keyFingerprint" | gpg2ssh "$mode" > "$cacheDir"/"$userIDHash"."$keyFingerprint" + # stand in until we get dkg's gpg2ssh program + gpg2ssh_tmp "$mode" "$keyID" "$userID" > "$cacheDir"/"$userIDHash"."$keyFingerprint" + if [ "$?" = 0 ] ; then + echo "done." + else + echo "error." + fi + ;; + esac + done } -# expects global variables -# mode REQUIRED_KEY_CAPABILITY ids_file key_dir -process_keys() { - local nlines - local n - local userid - local userid_hash - local return - local pub_info - local key_trust - local key_capability - local gen_key - unset gen_key +# process the auth_*_ids file +# go through line-by-line, extracting and processing each user id +# expects global variable: "mode" +process_auth_file() { + local authIDsFile + local cacheDir + local nLines + local line + local userID + + authIDsFile="$1" + cacheDir="$2" # find number of user ids in auth_user_ids file - nlines=$(meat "$ids_file" | wc -l) + nLines=$(meat <"$authIDsFile" | wc -l) # make sure gpg home exists with proper permissions mkdir -p -m 0700 "$GNUPGHOME" # clean out keys file and remake keys directory - rm -rf "$key_dir" - mkdir -p "$key_dir" - - # loop through all user ids, and generate ssh keys - for n in $(seq 1 $nlines) ; do - - # get id - userid=$(meat "$ids_file" | cutline "$n" ) - userid_hash=$(echo "$userid" | sha1sum | awk '{ print $1 }') - - # search for key on keyserver - log "validating: '$userid'" - return=$(echo 1 | gpg --quiet --batch --command-fd 0 --with-colons --keyserver "$KEYSERVER" --search ="$userid") - - # if the key was found... - if [ "$return" ] ; then - echo " key found." - - # checking key attributes - # see /usr/share/doc/gnupg/DETAILS.gz - - pub_info=$(gpg --fixed-list-mode --with-colons --list-keys --with-fingerprint ="$userid" | grep '^pub:') - if [ -z "$pub_info" ] ; then - echo " error getting pub info -> SKIPPING" - continue - fi - - # extract needed fields - key_trust=$(echo "$pub_info" | cut -d: -f2) - keyid=$(echo "$pub_info" | cut -d: -f5) - key_capability=$(echo "$pub_info" | cut -d: -f12) - - # check if key disabled - if echo "$key_capability" | grep -q '[D]' ; then - echo " key disabled -> SKIPPING" - continue - fi - - # check key capability - if echo "$key_capability" | grep -q '[$REQUIRED_KEY_CAPABILITY]' ; then - echo " key capability verified ('$key_capability')." - else - echo " unacceptable key capability ('$key_capability') -> SKIPPING" - continue - fi - - # if key is not fully trusted exit - # (this includes not revoked or expired) - # determine trust - echo -n " key " - case "$key_trust" in - 'i') - echo -n "invalid" ;; - 'r') - echo -n "revoked" ;; - 'e') - echo -n "expired" ;; - '-'|'q'|'n'|'m') - echo -n "has unacceptable trust" ;; - 'f'|'u') - echo -n "fully trusted" - gen_key=true - ;; - *) - echo -n "has unknown trust" ;; - esac - - if [ "$gen_key" ] ; then - # convert pgp key to ssh key, and write to cache file - echo -n " -> generating ssh key... " - gpg2ssh "$mode" "$keyid" > "$key_dir"/"$userid_hash" - echo "done." - else - echo ". -> SKIPPING" - fi - - else - echo " key not found." - fi + rm -rf "$cacheDir" + mkdir -p "$cacheDir" + + # loop through all user ids + for line in $(seq 1 $nLines) ; do + # get user id + # FIXME: needs to handle extra options if necessary + userID=$(meat <"$authIDsFile" | cutline "$line" ) + + # process the user id and extract keys + log "processing user id: '$userID'" + process_user_id "$userID" "$cacheDir" done } + ######################################################################## # MAIN ######################################################################## @@ -180,11 +225,13 @@ if ! id -u "$USER" > /dev/null 2>&1 ; then failure "invalid user '$USER'." fi +# set user home directory HOME=$(getent passwd "$USER" | cut -d: -f6) +# get ms home directory MS_HOME=${MS_HOME:-"$HOME"/.config/monkeysphere} -# load conf file +# load configuration file MS_CONF=${MS_CONF:-"$MS_HOME"/monkeysphere.conf} [ -e "$MS_CONF" ] && . "$MS_CONF" @@ -194,78 +241,62 @@ AUTH_HOST_FILE=${AUTH_HOST_FILE:-"$MS_HOME"/auth_host_ids} AUTH_USER_FILE=${AUTH_USER_FILE:-"$MS_HOME"/auth_user_ids} GNUPGHOME=${GNUPGHOME:-"$HOME"/.gnupg} KEYSERVER=${KEYSERVER:-subkeys.pgp.net} -REQUIRED_KEY_CAPABILITY=${REQUIRED_KEY_CAPABILITY:-'a'} +USER_KNOW_HOSTS="$HOME"/.ssh/known_hosts +USER_AUTHORIZED_KEYS="$HOME"/.ssh/authorized_keys + +# export USER and GNUPGHOME variables, since they are used by gpg export USER export GNUPGHOME -host_keys_dir="$STAGING_AREA"/host_keys -user_keys_dir="$STAGING_AREA"/user_keys -known_hosts_stage_file="$STAGING_AREA"/known_hosts -authorized_keys_stage_file="$STAGING_AREA"/authorized_keys +# stagging locations +hostKeysCacheDir="$STAGING_AREA"/host_keys +userKeysCacheDir="$STAGING_AREA"/user_keys +msKnownHosts="$STAGING_AREA"/known_hosts +msAuthorizedKeys="$STAGING_AREA"/authorized_keys -# act on mode +# set mode variables if [ "$mode" = '--known_hosts' -o "$mode" = '-k' ] ; then + fileType=known_hosts + authIDsFile="$AUTH_HOST_FILE" + outFile="$msKnownHosts" + cacheDir="$hostKeysCacheDir" + userFile="$USER_KNOWN_HOSTS" +elif [ "$mode" = '--authorized_keys' -o "$mode" = '-a' ] ; then + fileType=authorized_keys + authIDsFile="$AUTH_USER_FILE" + outFile="$msAuthorizedKeys" + cacheDir="$userKeysCacheDir" + userFile="$USER_AUTHORIZED_KEYS" +else + failure "unknown command '$mode'." +fi - # set variables for process_keys command - ids_file="$AUTH_HOST_FILE" - log -n "[$USER] " - if [ ! -s "$ids_file" ] ; then - echo "auth_host_ids file is empty or does not exist." - exit - else - echo "updating known_hosts file..." - fi - key_dir="$host_keys_dir" - - # process the keys - process_keys - - # write known_hosts file - > "$known_hosts_stage_file" - if [ $(ls "$key_dir") ] ; then - log -n "writing known_hosts stage file..." - cat "$key_dir"/* > "$known_hosts_stage_file" - echo "done." - else - log "no gpg keys to add to known_hosts file." - fi - if [ -s "$HOME"/.ssh/known_hosts ] ; then - log -n "adding user known_hosts file... " - cat "$HOME"/.ssh/known_hosts >> "$known_hosts_stage_file" - echo "done." - fi - log "known_hosts file updated: $known_hosts_stage_file" +# check auth ids file +if [ ! -s "$authIDsFile" ] ; then + echo $(basename "$authIDsFile") "file is empty or does not exist." + exit +fi -elif [ "$mode" = '--authorized_keys' -o "$mode" = '-a' ] ; then +log "user '$USER': monkeysphere $fileType generation..." - # set variables for process_keys command - ids_file="$AUTH_USER_FILE" - log -n "[$USER] " - if [ ! -s "$ids_file" ] ; then - echo "auth_user_ids file is empty or does not exist." - exit - else - echo "updating authorized_keys file:" - fi - key_dir="$user_keys_dir" - - # process the keys - process_keys - - # write authorized_keys file - > "$authorized_keys_stage_file" - if [ $(ls "$key_dir") ] ; then - log -n "writing ms authorized_keys file... " - cat "$key_dir"/* > "$authorized_keys_stage_file" - echo "done." - else - log "no gpg keys to add to authorized_keys file." - fi - if [ -s "$HOME"/.ssh/authorized_keys ] ; then - log -n "adding user authorized_keys file... " - cat "$HOME"/.ssh/authorized_keys >> "$authorized_keys_stage_file" - echo "done." - fi - log "authorized_keys file updated: $authorized_keys_stage_file" +# process the auth file +process_auth_file "$authIDsFile" "$cacheDir" + +# write output key file +log "writing ms $fileType file... " +> "$outFile" +if [ "$(ls "$cacheDir")" ] ; then + log -n "adding gpg keys... " + cat "$cacheDir"/* > "$outFile" + echo "done." +else + log "no gpg keys to add." +fi +if [ -s "$userFile" ] ; then + log -n "adding user $fileType file... " + cat "$userFile" >> "$outFile" + echo "done." fi +log "ms $fileType file generated:" +log "$outFile" -- 2.25.1