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