4c403f2a2647ea4f074a8bad5637ad134190a97d
[monkeysphere.git] / src / monkeysphere-server
1 #!/bin/bash
2
3 # monkeysphere-server: MonkeySphere server admin tool
4 #
5 # The monkeysphere scripts are written by:
6 # Jameson Rollins <jrollins@fifthhorseman.net>
7 #
8 # They are Copyright 2008, and are all released under the GPL, version 3
9 # or later.
10
11 ########################################################################
12 PGRM=$(basename $0)
13 PGRM_PATH=$(dirname $0)
14
15 SHARE=${SHARE:-"/usr/share/monkeysphere"}
16 export SHARE
17 . "${SHARE}/common"
18
19 VARLIB="/var/lib/monkeysphere"
20 export VARLIB
21
22 # date in UTF format if needed
23 DATE=$(date -u '+%FT%T')
24
25 # unset some environment variables that could screw things up
26 GREP_OPTIONS=
27
28 # default return code
29 ERR=0
30
31 ########################################################################
32 # FUNCTIONS
33 ########################################################################
34
35 usage() {
36 cat <<EOF
37 usage: $PGRM <subcommand> [args]
38 MonkeySphere server admin tool.
39
40 subcommands:
41   update-users (u) [USER]...            update users authorized_keys files
42   gen-key (g) [HOSTNAME]                generate gpg key for the server
43   show-fingerprint (f)                  show server's host key fingerprint
44   publish-key (p)                       publish server's host key to keyserver
45   trust-key (t) KEYID [LEVEL]           set owner trust for keyid
46   help (h,?)                            this help
47
48 EOF
49 }
50
51 # generate server gpg key
52 gen_key() {
53     local hostName
54     local userID
55     local keyParameters
56     local fingerprint
57
58     hostName=${1:-$(hostname --fqdn)}
59
60     SERVICE=${SERVICE:-"ssh"}
61     userID="${SERVICE}://${hostName}"
62
63     GNUPGHOME="$GNUPGHOME_HOST"
64     if gpg --list-key ="$userID" > /dev/null 2>&1 ; then
65         failure "Key for '$userID' already exists"
66     fi
67
68     # set key defaults
69     KEY_TYPE=${KEY_TYPE:-"RSA"}
70     KEY_LENGTH=${KEY_LENGTH:-"2048"}
71     KEY_USAGE=${KEY_USAGE:-"auth"}
72     KEY_EXPIRE=${KEY_EXPIRE:-"0"}
73     cat <<EOF
74 Please specify how long the key should be valid.
75          0 = key does not expire
76       <n>  = key expires in n days
77       <n>w = key expires in n weeks
78       <n>m = key expires in n months
79       <n>y = key expires in n years
80 EOF
81     read -p "Key is valid for? ($KEY_EXPIRE) " KEY_EXPIRE; KEY_EXPIRE=${KEY_EXPIRE:-"0"}
82
83     # set key parameters
84     keyParameters=$(cat <<EOF
85 Key-Type: $KEY_TYPE
86 Key-Length: $KEY_LENGTH
87 Key-Usage: $KEY_USAGE
88 Name-Real: $userID
89 Expire-Date: $KEY_EXPIRE
90 EOF
91 )
92
93     # add the revoker field if requested
94     # FIXME: the "1:" below assumes that $REVOKER's key is an RSA key.  why?
95     # FIXME: why is this marked "sensitive"?  how will this signature ever
96     # be transmitted to the expected revoker?
97     if [ "$REVOKER" ] ; then
98         keyParameters="${keyParameters}"$(cat <<EOF
99
100 Revoker: 1:$REVOKER sensitive
101 EOF
102 )
103     fi
104
105     echo "The following key parameters will be used:"
106     echo "$keyParameters"
107
108     read -p "Generate key? [Y|n]: " OK; OK=${OK:=Y}
109     if [ ${OK/y/Y} != 'Y' ] ; then
110         failure "aborting."
111     fi
112
113     # add commit command
114     keyParameters="${keyParameters}"$(cat <<EOF
115
116 %commit
117 %echo done
118 EOF
119 )
120
121     log "generating server key..."
122     GNUPGHOME="$GNUPGHOME_HOST"
123     echo "$keyParameters" | gpg --batch --gen-key
124
125     # output the server fingerprint
126     fingerprint_server_key "=${userID}"
127
128     # find the key fingerprint of the server primary key
129     GNUPGHOME="$GNUPGHOME_HOST"
130     fingerprint=$(gpg --list-key --with-colons --with-fingerprint "=${userID}" | \
131         grep '^fpr:' | head -1 | cut -d: -f10)
132
133     # export the host key to the authentication keyring
134     GNUPGHOME="$GNUPGHOME_HOST" gpg --export "$fingerprint" | \
135         su --preserve-environment "$MONKEYSPHERE_USER" -c -- \
136         "GNUPGHOME=$GNUPGHOME_AUTHENTICATION gpg --import"
137
138     # set host key owner trust to ultimate in authentication keyring
139     echo "${fingerprint}:6:" | \
140         su --preserve-environment "$MONKEYSPHERE_USER" -c -- \
141         "GNUPGHOME=$GNUPGHOME_AUTHENTICATION gpg --import-ownertrust"
142
143     # write the key to the file
144     # NOTE: assumes that the primary key is the proper key to use
145     GNUPGHOME="$GNUPGHOME_HOST"
146     (umask 077 && gpgsecret2ssh "$fingerprint" > "${VARLIB}/ssh_host_rsa_key")
147     log "Private SSH host key output to file: ${VARLIB}/ssh_host_rsa_key"
148 }
149
150 # gpg output key fingerprint
151 fingerprint_server_key() {
152     local ID
153
154     if [ "$1" ] ; then
155         ID="$1"
156     else
157         ID="=ssh://$(hostname --fqdn)"
158     fi
159
160     gpg --fingerprint --list-secret-keys "$ID"
161 }
162
163 # publish server key to keyserver
164 publish_server_key() {
165     read -p "really publish key to $KEYSERVER? [y|N]: " OK; OK=${OK:=N}
166     if [ ${OK/y/Y} != 'Y' ] ; then
167         failure "aborting."
168     fi
169
170     # publish host key
171     # FIXME: need to figure out better way to identify host key
172     # dummy command so as not to publish fakes keys during testing
173     # eventually:
174     #gpg --keyserver "$KEYSERVER" --send-keys $(hostname -f)
175     failure "NOT PUBLISHED (to avoid permanent publication errors during monkeysphere development).
176 To publish manually, do: gpg --keyserver $KEYSERVER --send-keys $(hostname -f)"
177 }
178
179
180 # retrieve key from web of trust, and set owner trust to "full"
181 # if key is found.
182 trust_key() {
183     local keyID
184     local trustLevel
185
186     keyID="$1"
187     trustLevel="$2"
188
189     if [ -z "$keyID" ] ; then
190         failure "You must specify key to trust."
191     fi
192
193     export keyID
194
195     # get the key from the key server
196     GNUPGHOME="$GNUPGHOME_AUTHENTICATION"
197     su --preserve-environment "$MONKEYSPHERE_USER" -c -- \
198         "gpg --keyserver $KEYSERVER --recv-key $keyID"
199     if [ "$?" != 0 ] ; then
200         failure "Could not retrieve key '$keyID'."
201     fi
202
203     # move the key from the authentication keyring to the host keyring
204     GNUPGHOME="$GNUPGHOME_AUTHENTICATION"
205     su --preserve-environment "$MONKEYSPHERE_USER" -c -- \
206         "gpg --export $keyID" | \
207         GNUPGHOME="$GNUPGHOME_HOST" gpg --import
208
209     # get key fingerprint
210     GNUPGHOME="$GNUPGHOME_HOST"
211     fingerprint=$(get_key_fingerprint "$keyID")
212
213     echo "key found:"
214     GNUPGHOME="$GNUPGHOME_HOST"
215     gpg --fingerprint "$fingerprint"
216
217     while [ -z "$trustLevel" ] ; do
218         cat <<EOF
219 Please decide how far you trust this user to correctly verify other users' keys
220 (by looking at passports, checking fingerprints from different sources, etc.)
221
222   1 = I don't know or won't say
223   2 = I do NOT trust
224   3 = I trust marginally
225   4 = I trust fully
226   5 = I trust ultimately
227
228 EOF
229         read -p "Your decision? " trustLevel
230         if echo "$trustLevel" | grep -v "[1-5]" ; then
231             echo "Unknown trust level '$trustLevel'."
232             unset trustLevel
233         elif [ "$trustLevel" = 'q' ] ; then
234             failure "Aborting."
235         fi
236     done
237
238     # attach a "non-exportable" signature to the key
239     # this is required for the key to have any validity at all
240     # the 'y's on stdin indicates "yes, i really want to sign"
241     GNUPGHOME="$GNUPGHOME_HOST"
242     echo -e 'y\ny' | \
243         gpg --quiet --lsign-key --command-fd 0 "$fingerprint"
244
245     # copy the host keyring into the authentication keyring
246     mv "$GNUPGHOME_AUTHENTICATION"/pubring.gpg{,.old}
247     cp "$GNUPGHOME_HOST"/pubring.gpg "$GNUPGHOME_AUTHENTICATION"/pubring.gpg
248     chown "$MONKEYSPHERE_USER" "$GNUPGHOME_AUTHENTICATION"/pubring.gpg
249     GNUPGHOME="$GNUPGHOME_AUTHENTICATION"
250     su --preserve-environment "$MONKEYSPHERE_USER" -c -- \
251         "gpg --import ${GNUPGHOME_AUTHENTICATION}/pubring.gpg.old"
252
253     # index trustLevel by one to difference between level in ui and level
254     # internally
255     trustLevel=$((trustLevel+1))
256
257     # import new owner trust level for key
258     GNUPGHOME="$GNUPGHOME_AUTHENTICATION"
259     echo "${fingerprint}:${trustLevel}:" | \
260         su --preserve-environment "$MONKEYSPHERE_USER" -c -- \
261         "GNUPGHOME=$GNUPGHOME_AUTHENTICATION gpg --import-ownertrust"
262
263     if [ $? = 0 ] ; then
264         log "Owner trust updated."
265     else
266         failure "There was a problem changing owner trust."
267     fi
268 }
269
270 ########################################################################
271 # MAIN
272 ########################################################################
273
274 COMMAND="$1"
275 [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
276 shift
277
278 # set ms home directory
279 MS_HOME=${MS_HOME:-"$ETC"}
280
281 # load configuration file
282 MS_CONF=${MS_CONF:-"$MS_HOME"/monkeysphere-server.conf}
283 [ -e "$MS_CONF" ] && . "$MS_CONF"
284
285 # set empty config variable with defaults
286 MONKEYSPHERE_USER=${MONKEYSPHERE_USER:-"monkeysphere"}
287 KEYSERVER=${KEYSERVER:-"subkeys.pgp.net"}
288 CHECK_KEYSERVER=${CHECK_KEYSERVER:="true"}
289 AUTHORIZED_USER_IDS=${AUTHORIZED_USER_IDS:-"%h/.config/monkeysphere/authorized_user_ids"}
290 RAW_AUTHORIZED_KEYS=${RAW_AUTHORIZED_KEYS:-"%h/.ssh/authorized_keys"}
291
292 # other variables
293 REQUIRED_USER_KEY_CAPABILITY=${REQUIRED_USER_KEY_CAPABILITY:-"a"}
294 GNUPGHOME_HOST=${GNUPGHOME_HOST:-"${VARLIB}/gnupg-host"}
295 GNUPGHOME_AUTHENTICATION=${GNUPGHOME_AUTHENTICATION:-"${VARLIB}/gnupg-authentication"}
296
297 # export variables
298 export MODE
299 export MONKEYSPHERE_USER
300 export KEYSERVER
301 export CHECK_KEYSERVER
302 export REQUIRED_USER_KEY_CAPABILITY
303 export GNUPGHOME_HOST
304 export GNUPGHOME_AUTHENTICATION
305 export GNUPGHOME
306
307 case $COMMAND in
308     'update-users'|'update-user'|'u')
309         if [ "$1" ] ; then
310             # get users from command line
311             unames="$@"
312         else
313             # or just look at all users if none specified
314             unames=$(getent passwd | cut -d: -f1)
315         fi
316
317         # set mode
318         MODE="authorized_keys"
319
320         # set gnupg home
321         GNUPGHOME="$GNUPGHOME_AUTHENTICATION"
322
323         # check to see if the gpg trust database has been initialized
324         if [ ! -s "${GNUPGHOME}/trustdb.gpg" ] ; then
325             failure "GNUPG trust database uninitialized.  Please see MONKEYSPHERE-SERVER(8)."
326         fi
327
328         # make sure the authorized_keys directory exists
329         mkdir -p "${VARLIB}/authorized_keys"
330
331         # loop over users
332         for uname in $unames ; do
333             # check all specified users exist
334             if ! getent passwd "$uname" >/dev/null ; then
335                 error "----- unknown user '$uname' -----"
336                 continue
337             fi
338
339             # set authorized_user_ids and raw authorized_keys variables,
340             # translating ssh-style path variables
341             authorizedUserIDs=$(translate_ssh_variables "$uname" "$AUTHORIZED_USER_IDS")
342             rawAuthorizedKeys=$(translate_ssh_variables "$uname" "$RAW_AUTHORIZED_KEYS")
343
344             # if neither is found, skip user
345             if [ ! -s "$authorizedUserIDs" ] ; then
346                 if [ "$rawAuthorizedKeys" = '-' -o ! -s "$rawAuthorizedKeys" ] ; then
347                     continue
348                 fi
349             fi
350
351             log "----- user: $uname -----"
352
353             # make temporary directory
354             TMPDIR=$(mktemp -d)
355
356             # trap to delete temporary directory on exit
357             trap "rm -rf $TMPDIR" EXIT
358
359             # create temporary authorized_user_ids file
360             TMP_AUTHORIZED_USER_IDS="${TMPDIR}/authorized_user_ids"
361             touch "$TMP_AUTHORIZED_USER_IDS"
362
363             # create temporary authorized_keys file
364             AUTHORIZED_KEYS="${TMPDIR}/authorized_keys"
365             touch "$AUTHORIZED_KEYS"
366
367             # set restrictive permissions on the temporary files
368             # FIXME: is there a better way to do this?
369             chmod 0700 "$TMPDIR"
370             chmod 0600 "$AUTHORIZED_KEYS"
371             chmod 0600 "$TMP_AUTHORIZED_USER_IDS"
372             chown -R "$MONKEYSPHERE_USER" "$TMPDIR"
373
374             # if the authorized_user_ids file exists...
375             if [ -s "$authorizedUserIDs" ] ; then
376                 # copy user authorized_user_ids file to temporary
377                 # location
378                 cat "$authorizedUserIDs" > "$TMP_AUTHORIZED_USER_IDS"
379
380                 # export needed variables
381                 export AUTHORIZED_KEYS
382                 export TMP_AUTHORIZED_USER_IDS
383
384                 # process authorized_user_ids file, as monkeysphere
385                 # user
386                 su --preserve-environment "$MONKEYSPHERE_USER" -c -- \
387                     ". ${SHARE}/common; process_authorized_user_ids $TMP_AUTHORIZED_USER_IDS"
388             fi
389
390             # add user-controlled authorized_keys file path if specified
391             if [ "$rawAuthorizedKeys" != '-' -a -s "$rawAuthorizedKeys" ] ; then
392                 log -n "adding raw authorized_keys file... "
393                 cat "$rawAuthorizedKeys" >> "$AUTHORIZED_KEYS"
394                 loge "done."
395             fi
396
397             # openssh appears to check the contents of the
398             # authorized_keys file as the user in question, so the
399             # file must be readable by that user at least.
400             # FIXME: is there a better way to do this?
401             chown root "$AUTHORIZED_KEYS"
402             chgrp $(getent passwd "$uname" | cut -f4 -d:) "$AUTHORIZED_KEYS"
403             chmod g+r "$AUTHORIZED_KEYS"
404
405             # if the resulting authorized_keys file is not empty, move
406             # it into place
407             mv -f "$AUTHORIZED_KEYS" "${VARLIB}/authorized_keys/${uname}"
408
409             log "authorized_keys file updated."
410             
411             # destroy temporary directory
412             rm -rf "$TMPDIR"
413         done
414         ;;
415
416     'gen-key'|'g')
417         # set gnupg home
418         GNUPGHOME="$GNUPGHOME_HOST"
419         gen_key "$@"
420         ;;
421
422     'show-fingerprint'|'f')
423         # set gnupg home
424         GNUPGHOME="$GNUPGHOME_HOST"
425         fingerprint_server_key "$@"
426         ;;
427
428     'publish-key'|'p')
429         # set gnupg home
430         GNUPGHOME="$GNUPGHOME_HOST"
431         publish_server_key
432         ;;
433
434     'trust-key'|'t')
435         trust_key "$@"
436         ;;
437
438     'help'|'h'|'?')
439         usage
440         ;;
441
442     *)
443         failure "Unknown command: '$COMMAND'
444 Type '$PGRM help' for usage."
445         ;;
446 esac
447
448 exit "$ERR"