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