summaryrefslogtreecommitdiff
path: root/src/monkeysphere-server
blob: 617c10a19ceeea4e7a73ab6facded3a1177e71cc (plain)
  1. #!/usr/bin/env bash
  2. # monkeysphere-server: MonkeySphere server admin tool
  3. #
  4. # The monkeysphere scripts are written by:
  5. # Jameson Rollins <jrollins@fifthhorseman.net>
  6. # Jamie McClelland <jm@mayfirst.org>
  7. # Daniel Kahn Gillmor <dkg@fifthhorseman.net>
  8. #
  9. # They are Copyright 2008, and are all released under the GPL, version 3
  10. # or later.
  11. ########################################################################
  12. PGRM=$(basename $0)
  13. SYSSHAREDIR=${MONKEYSPHERE_SYSSHAREDIR:-"/usr/share/monkeysphere"}
  14. export SYSSHAREDIR
  15. . "${SYSSHAREDIR}/common" || exit 1
  16. SYSDATADIR=${MONKEYSPHERE_SYSDATADIR:-"/var/lib/monkeysphere"}
  17. export SYSDATADIR
  18. # UTC date in ISO 8601 format if needed
  19. DATE=$(date -u '+%FT%T')
  20. # unset some environment variables that could screw things up
  21. unset GREP_OPTIONS
  22. # default return code
  23. RETURN=0
  24. ########################################################################
  25. # FUNCTIONS
  26. ########################################################################
  27. usage() {
  28. cat <<EOF >&2
  29. usage: $PGRM <subcommand> [options] [args]
  30. Monkeysphere server admin tool.
  31. subcommands:
  32. update-users (u) [USER]... update user authorized_keys files
  33. gen-key (g) [NAME[:PORT]] generate gpg key for the server
  34. --length (-l) BITS key length in bits (2048)
  35. --expire (-e) EXPIRE date to expire
  36. --revoker (-r) FINGERPRINT add a revoker
  37. extend-key (e) EXPIRE extend expiration to EXPIRE
  38. add-hostname (n+) NAME[:PORT] add hostname user ID to server key
  39. revoke-hostname (n-) NAME[:PORT] revoke hostname user ID
  40. show-key (s) output all server host key information
  41. publish-key (p) publish server host key to keyserver
  42. diagnostics (d) report on server monkeysphere status
  43. add-id-certifier (c+) KEYID import and tsign a certification key
  44. --domain (-n) DOMAIN limit ID certifications to DOMAIN
  45. --trust (-t) TRUST trust level of certifier (full)
  46. --depth (-d) DEPTH trust depth for certifier (1)
  47. remove-id-certifier (c-) KEYID remove a certification key
  48. list-id-certifiers (c) list certification keys
  49. gpg-authentication-cmd CMD gnupg-authentication command
  50. help (h,?) this help
  51. EOF
  52. }
  53. # function to run command as monkeysphere user
  54. su_monkeysphere_user() {
  55. # if the current user is the monkeysphere user, then just eval
  56. # command
  57. if [ $(id -un) = "$MONKEYSPHERE_USER" ] ; then
  58. eval "$@"
  59. # otherwise su command as monkeysphere user
  60. else
  61. su "$MONKEYSPHERE_USER" -c "$@"
  62. fi
  63. }
  64. # function to interact with the host gnupg keyring
  65. gpg_host() {
  66. local returnCode
  67. GNUPGHOME="$GNUPGHOME_HOST"
  68. export GNUPGHOME
  69. # NOTE: we supress this warning because we need the monkeysphere
  70. # user to be able to read the host pubring. we realize this might
  71. # be problematic, but it's the simplest solution, without too much
  72. # loss of security.
  73. gpg --no-permission-warning "$@"
  74. returnCode="$?"
  75. # always reset the permissions on the host pubring so that the
  76. # monkeysphere user can read the trust signatures
  77. chgrp "$MONKEYSPHERE_USER" "${GNUPGHOME_HOST}/pubring.gpg"
  78. chmod g+r "${GNUPGHOME_HOST}/pubring.gpg"
  79. return "$returnCode"
  80. }
  81. # function to interact with the authentication gnupg keyring
  82. # FIXME: this function requires basically accepts only a single
  83. # argument because of problems with quote expansion. this needs to be
  84. # fixed/improved.
  85. gpg_authentication() {
  86. GNUPGHOME="$GNUPGHOME_AUTHENTICATION"
  87. export GNUPGHOME
  88. su_monkeysphere_user "gpg $@"
  89. }
  90. # output just key fingerprint
  91. fingerprint_server_key() {
  92. gpg_host --list-secret-keys --fingerprint \
  93. --with-colons --fixed-list-mode 2> /dev/null | \
  94. grep '^fpr:' | head -1 | cut -d: -f10
  95. }
  96. # output key information
  97. show_server_key() {
  98. local fingerprint
  99. local tmpkey
  100. fingerprint=$(fingerprint_server_key)
  101. gpg_authentication "--fingerprint --list-key --list-options show-unusable-uids $fingerprint"
  102. # dumping to a file named ' ' so that the ssh-keygen output
  103. # doesn't claim any potentially bogus hostname(s):
  104. tmpkey=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
  105. gpg_authentication "--export $fingerprint" | openpgp2ssh "$fingerprint" 2>/dev/null > "$tmpkey/ "
  106. echo -n "ssh fingerprint: "
  107. (cd "$tmpkey" && ssh-keygen -l -f ' ' | awk '{ print $2 }')
  108. rm -rf "$tmpkey"
  109. echo -n "OpenPGP fingerprint: "
  110. echo "$fingerprint"
  111. }
  112. # update authorized_keys for users
  113. update_users() {
  114. if [ "$1" ] ; then
  115. # get users from command line
  116. unames="$@"
  117. else
  118. # or just look at all users if none specified
  119. unames=$(getent passwd | cut -d: -f1)
  120. fi
  121. # set mode
  122. MODE="authorized_keys"
  123. # set gnupg home
  124. GNUPGHOME="$GNUPGHOME_AUTHENTICATION"
  125. # check to see if the gpg trust database has been initialized
  126. if [ ! -s "${GNUPGHOME}/trustdb.gpg" ] ; then
  127. failure "GNUPG trust database uninitialized. Please see MONKEYSPHERE-SERVER(8)."
  128. fi
  129. # make sure the authorized_keys directory exists
  130. mkdir -p "${SYSDATADIR}/authorized_keys"
  131. # loop over users
  132. for uname in $unames ; do
  133. # check all specified users exist
  134. if ! getent passwd "$uname" >/dev/null ; then
  135. log error "----- unknown user '$uname' -----"
  136. continue
  137. fi
  138. log verbose "----- user: $uname -----"
  139. # make temporary directory
  140. TMPLOC=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX)
  141. # trap to delete temporary directory on exit
  142. trap "rm -rf $TMPLOC" EXIT
  143. # create temporary authorized_user_ids file
  144. TMP_AUTHORIZED_USER_IDS="${TMPLOC}/authorized_user_ids"
  145. touch "$TMP_AUTHORIZED_USER_IDS"
  146. # create temporary authorized_keys file
  147. AUTHORIZED_KEYS="${TMPLOC}/authorized_keys"
  148. touch "$AUTHORIZED_KEYS"
  149. # set restrictive permissions on the temporary files
  150. # FIXME: is there a better way to do this?
  151. chmod 0700 "$TMPLOC"
  152. chmod 0600 "$AUTHORIZED_KEYS"
  153. chmod 0600 "$TMP_AUTHORIZED_USER_IDS"
  154. chown -R "$MONKEYSPHERE_USER" "$TMPLOC"
  155. # process authorized_user_ids file
  156. # translating ssh-style path variables
  157. authorizedUserIDs=$(translate_ssh_variables "$uname" "$AUTHORIZED_USER_IDS")
  158. if [ -s "$authorizedUserIDs" ] ; then
  159. # check permissions on the authorized_user_ids file path
  160. if check_key_file_permissions "$uname" "$authorizedUserIDs" ; then
  161. # copy user authorized_user_ids file to temporary
  162. # location
  163. cat "$authorizedUserIDs" > "$TMP_AUTHORIZED_USER_IDS"
  164. # export needed variables
  165. export AUTHORIZED_KEYS
  166. export TMP_AUTHORIZED_USER_IDS
  167. # process authorized_user_ids file, as monkeysphere
  168. # user
  169. su_monkeysphere_user \
  170. ". ${SYSSHAREDIR}/common; process_authorized_user_ids $TMP_AUTHORIZED_USER_IDS"
  171. RETURN="$?"
  172. else
  173. log verbose "not processing authorized_user_ids."
  174. fi
  175. fi
  176. # add user-controlled authorized_keys file if specified
  177. # translate ssh-style path variables
  178. rawAuthorizedKeys=$(translate_ssh_variables "$uname" "$RAW_AUTHORIZED_KEYS")
  179. if [ "$rawAuthorizedKeys" -a -s "$rawAuthorizedKeys" ] ; then
  180. # check permissions on the authorized_keys file path
  181. if check_key_file_permissions "$uname" "$rawAuthorizedKeys" ; then
  182. log verbose "adding raw authorized_keys file... "
  183. cat "$rawAuthorizedKeys" >> "$AUTHORIZED_KEYS"
  184. else
  185. log verbose "not adding raw authorized_keys file."
  186. fi
  187. fi
  188. # move the new authorized_keys file into place
  189. if [ -s "$AUTHORIZED_KEYS" ] ; then
  190. # openssh appears to check the contents of the
  191. # authorized_keys file as the user in question, so the
  192. # file must be readable by that user at least.
  193. # FIXME: is there a better way to do this?
  194. chown root "$AUTHORIZED_KEYS"
  195. chgrp $(getent passwd "$uname" | cut -f4 -d:) "$AUTHORIZED_KEYS"
  196. chmod g+r "$AUTHORIZED_KEYS"
  197. mv -f "$AUTHORIZED_KEYS" "${SYSDATADIR}/authorized_keys/${uname}"
  198. else
  199. rm -f "${SYSDATADIR}/authorized_keys/${uname}"
  200. fi
  201. # unset the trap
  202. trap - EXIT
  203. # destroy temporary directory
  204. rm -rf "$TMPLOC"
  205. done
  206. }
  207. # generate server gpg key
  208. gen_key() {
  209. local keyType
  210. local keyLength
  211. local keyUsage
  212. local keyExpire
  213. local revoker
  214. local hostName
  215. local userID
  216. local keyParameters
  217. local fingerprint
  218. # set default key parameter values
  219. keyType="RSA"
  220. keyLength="2048"
  221. keyUsage="auth"
  222. keyExpire=
  223. revoker=
  224. # get options
  225. TEMP=$(PATH="/usr/local/bin:$PATH" getopt -o e:l:r -l expire:,length:,revoker: -n "$PGRM" -- "$@") || failure "getopt failed! Does your getopt support GNU-style long options?"
  226. if [ $? != 0 ] ; then
  227. exit 1
  228. fi
  229. # Note the quotes around `$TEMP': they are essential!
  230. eval set -- "$TEMP"
  231. while true ; do
  232. case "$1" in
  233. -l|--length)
  234. keyLength="$2"
  235. shift 2
  236. ;;
  237. -e|--expire)
  238. keyExpire="$2"
  239. shift 2
  240. ;;
  241. -r|--revoker)
  242. revoker="$2"
  243. shift 2
  244. ;;
  245. --)
  246. shift
  247. ;;
  248. *)
  249. break
  250. ;;
  251. esac
  252. done
  253. hostName=${1:-$(hostname -f)}
  254. userID="ssh://${hostName}"
  255. # check for presense of key with user ID
  256. if gpg_host --list-key ="$userID" > /dev/null 2>&1 ; then
  257. failure "Key for '$userID' already exists"
  258. fi
  259. # prompt about key expiration if not specified
  260. keyExpire=$(get_gpg_expiration "$keyExpire")
  261. # set key parameters
  262. keyParameters=$(cat <<EOF
  263. Key-Type: $keyType
  264. Key-Length: $keyLength
  265. Key-Usage: $keyUsage
  266. Name-Real: $userID
  267. Expire-Date: $keyExpire
  268. EOF
  269. )
  270. # add the revoker field if specified
  271. # FIXME: the "1:" below assumes that $REVOKER's key is an RSA key.
  272. # FIXME: key is marked "sensitive"? is this appropriate?
  273. if [ "$revoker" ] ; then
  274. keyParameters="${keyParameters}"$(cat <<EOF
  275. Revoker: 1:$revoker sensitive
  276. EOF
  277. )
  278. fi
  279. echo "The following key parameters will be used for the host private key:"
  280. echo "$keyParameters"
  281. read -p "Generate key? (Y/n) " OK; OK=${OK:=Y}
  282. if [ ${OK/y/Y} != 'Y' ] ; then
  283. failure "aborting."
  284. fi
  285. # add commit command
  286. keyParameters="${keyParameters}"$(cat <<EOF
  287. %commit
  288. %echo done
  289. EOF
  290. )
  291. log verbose "generating server key..."
  292. echo "$keyParameters" | gpg_host --batch --gen-key
  293. # output the server fingerprint
  294. fingerprint_server_key "=${userID}"
  295. # find the key fingerprint of the newly generated key
  296. fingerprint=$(fingerprint_server_key)
  297. # export host ownertrust to authentication keyring
  298. log verbose "setting ultimate owner trust for server key..."
  299. echo "${fingerprint}:6:" | gpg_authentication "--import-ownertrust"
  300. # translate the private key to ssh format, and export to a file
  301. # for sshs usage.
  302. # NOTE: assumes that the primary key is the proper key to use
  303. (umask 077 && \
  304. gpg_host --export-secret-key "$fingerprint" | \
  305. openpgp2ssh "$fingerprint" > "${SYSDATADIR}/ssh_host_rsa_key")
  306. log info "private SSH host key output to file: ${SYSDATADIR}/ssh_host_rsa_key"
  307. }
  308. # extend the lifetime of a host key:
  309. extend_key() {
  310. local fpr=$(fingerprint_server_key)
  311. local extendTo="$1"
  312. if [ -z "$fpr" ] ; then
  313. failure "You don't appear to have a MonkeySphere host key on this server. Try 'monkeysphere-server gen-key' first."
  314. fi
  315. # get the new expiration date
  316. extendTo=$(get_gpg_expiration "$extendTo")
  317. gpg_host --quiet --command-fd 0 --edit-key "$fpr" <<EOF
  318. expire
  319. $extendTo
  320. save
  321. EOF
  322. echo
  323. echo "NOTE: Host key expiration date adjusted, but not yet published."
  324. echo "Run '$PGRM publish-key' to publish the new expiration date."
  325. }
  326. # add hostname user ID to server key
  327. add_hostname() {
  328. local userID
  329. local fingerprint
  330. local tmpuidMatch
  331. local line
  332. local adduidCommand
  333. if [ -z "$1" ] ; then
  334. failure "You must specify a hostname to add."
  335. fi
  336. userID="ssh://${1}"
  337. fingerprint=$(fingerprint_server_key)
  338. # match to only ultimately trusted user IDs
  339. tmpuidMatch="u:$(echo $userID | gpg_escape)"
  340. # find the index of the requsted user ID
  341. # NOTE: this is based on circumstantial evidence that the order of
  342. # this output is the appropriate index
  343. if line=$(gpg_host --list-keys --with-colons --fixed-list-mode "0x${fingerprint}!" \
  344. | egrep '^(uid|uat):' | cut -f2,10 -d: | grep -n -x -F "$tmpuidMatch") ; then
  345. failure "Host userID '$userID' already exists."
  346. fi
  347. echo "The following user ID will be added to the host key:"
  348. echo " $userID"
  349. read -p "Are you sure you would like to add this user ID? (y/N) " OK; OK=${OK:=N}
  350. if [ ${OK/y/Y} != 'Y' ] ; then
  351. failure "User ID not added."
  352. fi
  353. # edit-key script command to add user ID
  354. adduidCommand=$(cat <<EOF
  355. adduid
  356. $userID
  357. save
  358. EOF
  359. )
  360. # execute edit-key script
  361. if echo "$adduidCommand" | \
  362. gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
  363. # update the trustdb for the authentication keyring
  364. gpg_authentication "--check-trustdb"
  365. show_server_key
  366. echo
  367. echo "NOTE: User ID added to key, but key not published."
  368. echo "Run '$PGRM publish-key' to publish the new user ID."
  369. else
  370. failure "Problem adding user ID."
  371. fi
  372. }
  373. # revoke hostname user ID to server key
  374. revoke_hostname() {
  375. local userID
  376. local fingerprint
  377. local tmpuidMatch
  378. local line
  379. local uidIndex
  380. local message
  381. local revuidCommand
  382. if [ -z "$1" ] ; then
  383. failure "You must specify a hostname to revoke."
  384. fi
  385. echo "WARNING: There is a known bug in this function."
  386. echo "This function has been known to occasionally revoke the wrong user ID."
  387. echo "Please see the following bug report for more information:"
  388. echo "http://web.monkeysphere.info/bugs/revoke-hostname-revoking-wrong-userid/"
  389. read -p "Are you sure you would like to proceed? (y/N) " OK; OK=${OK:=N}
  390. if [ ${OK/y/Y} != 'Y' ] ; then
  391. failure "aborting."
  392. fi
  393. userID="ssh://${1}"
  394. fingerprint=$(fingerprint_server_key)
  395. # match to only ultimately trusted user IDs
  396. tmpuidMatch="u:$(echo $userID | gpg_escape)"
  397. # find the index of the requsted user ID
  398. # NOTE: this is based on circumstantial evidence that the order of
  399. # this output is the appropriate index
  400. if line=$(gpg_host --list-keys --with-colons --fixed-list-mode "0x${fingerprint}!" \
  401. | egrep '^(uid|uat):' | cut -f2,10 -d: | grep -n -x -F "$tmpuidMatch") ; then
  402. uidIndex=${line%%:*}
  403. else
  404. failure "No non-revoked user ID '$userID' is found."
  405. fi
  406. echo "The following host key user ID will be revoked:"
  407. echo " $userID"
  408. read -p "Are you sure you would like to revoke this user ID? (y/N) " OK; OK=${OK:=N}
  409. if [ ${OK/y/Y} != 'Y' ] ; then
  410. failure "User ID not revoked."
  411. fi
  412. message="Hostname removed by monkeysphere-server $DATE"
  413. # edit-key script command to revoke user ID
  414. revuidCommand=$(cat <<EOF
  415. $uidIndex
  416. revuid
  417. y
  418. 4
  419. $message
  420. y
  421. save
  422. EOF
  423. )
  424. # execute edit-key script
  425. if echo "$revuidCommand" | \
  426. gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
  427. # update the trustdb for the authentication keyring
  428. gpg_authentication "--check-trustdb"
  429. show_server_key
  430. echo
  431. echo "NOTE: User ID revoked, but revocation not published."
  432. echo "Run '$PGRM publish-key' to publish the revocation."
  433. else
  434. failure "Problem revoking user ID."
  435. fi
  436. }
  437. # publish server key to keyserver
  438. publish_server_key() {
  439. read -p "Really publish host key to $KEYSERVER? (y/N) " OK; OK=${OK:=N}
  440. if [ ${OK/y/Y} != 'Y' ] ; then
  441. failure "key not published."
  442. fi
  443. # find the key fingerprint
  444. fingerprint=$(fingerprint_server_key)
  445. # publish host key
  446. gpg_authentication "--keyserver $KEYSERVER --send-keys '0x${fingerprint}!'"
  447. }
  448. diagnostics() {
  449. # * check on the status and validity of the key and public certificates
  450. local seckey
  451. local keysfound
  452. local curdate
  453. local warnwindow
  454. local warndate
  455. local create
  456. local expire
  457. local uid
  458. local fingerprint
  459. local badhostkeys
  460. local sshd_config
  461. local problemsfound=0
  462. # FIXME: what's the correct, cross-platform answer?
  463. sshd_config=/etc/ssh/sshd_config
  464. seckey=$(gpg_host --list-secret-keys --fingerprint --with-colons --fixed-list-mode)
  465. keysfound=$(echo "$seckey" | grep -c ^sec:)
  466. curdate=$(date +%s)
  467. # warn when anything is 2 months away from expiration
  468. warnwindow='2 months'
  469. warndate=$(advance_date $warnwindow +%s)
  470. if ! id monkeysphere >/dev/null ; then
  471. echo "! No monkeysphere user found! Please create a monkeysphere system user with bash as its shell."
  472. problemsfound=$(($problemsfound+1))
  473. fi
  474. if ! [ -d "$SYSDATADIR" ] ; then
  475. echo "! no $SYSDATADIR directory found. Please create it."
  476. problemsfound=$(($problemsfound+1))
  477. fi
  478. echo "Checking host GPG key..."
  479. if (( "$keysfound" < 1 )); then
  480. echo "! No host key found."
  481. echo " - Recommendation: run 'monkeysphere-server gen-key'"
  482. problemsfound=$(($problemsfound+1))
  483. elif (( "$keysfound" > 1 )); then
  484. echo "! More than one host key found?"
  485. # FIXME: recommend a way to resolve this
  486. problemsfound=$(($problemsfound+1))
  487. else
  488. create=$(echo "$seckey" | grep ^sec: | cut -f6 -d:)
  489. expire=$(echo "$seckey" | grep ^sec: | cut -f7 -d:)
  490. fingerprint=$(echo "$seckey" | grep ^fpr: | head -n1 | cut -f10 -d:)
  491. # check for key expiration:
  492. if [ "$expire" ]; then
  493. if (( "$expire" < "$curdate" )); then
  494. echo "! Host key is expired."
  495. echo " - Recommendation: extend lifetime of key with 'monkeysphere-server extend-key'"
  496. problemsfound=$(($problemsfound+1))
  497. elif (( "$expire" < "$warndate" )); then
  498. echo "! Host key expires in less than $warnwindow:" $(advance_date $(( $expire - $curdate )) seconds +%F)
  499. echo " - Recommendation: extend lifetime of key with 'monkeysphere-server extend-key'"
  500. problemsfound=$(($problemsfound+1))
  501. fi
  502. fi
  503. # and weirdnesses:
  504. if [ "$create" ] && (( "$create" > "$curdate" )); then
  505. echo "! Host key was created in the future(?!). Is your clock correct?"
  506. echo " - Recommendation: Check clock ($(date +%F_%T)); use NTP?"
  507. problemsfound=$(($problemsfound+1))
  508. fi
  509. # check for UserID expiration:
  510. echo "$seckey" | grep ^uid: | cut -d: -f6,7,10 | \
  511. while IFS=: read create expire uid ; do
  512. # FIXME: should we be doing any checking on the form
  513. # of the User ID? Should we be unmangling it somehow?
  514. if [ "$create" ] && (( "$create" > "$curdate" )); then
  515. echo "! User ID '$uid' was created in the future(?!). Is your clock correct?"
  516. echo " - Recommendation: Check clock ($(date +%F_%T)); use NTP?"
  517. problemsfound=$(($problemsfound+1))
  518. fi
  519. if [ "$expire" ] ; then
  520. if (( "$expire" < "$curdate" )); then
  521. echo "! User ID '$uid' is expired."
  522. # FIXME: recommend a way to resolve this
  523. problemsfound=$(($problemsfound+1))
  524. elif (( "$expire" < "$warndate" )); then
  525. echo "! User ID '$uid' expires in less than $warnwindow:" $(advance_date $(( $expire - $curdate )) seconds +%F)
  526. # FIXME: recommend a way to resolve this
  527. problemsfound=$(($problemsfound+1))
  528. fi
  529. fi
  530. done
  531. # FIXME: verify that the host key is properly published to the
  532. # keyservers (do this with the non-privileged user)
  533. # FIXME: check that there are valid, non-expired certifying signatures
  534. # attached to the host key after fetching from the public keyserver
  535. # (do this with the non-privileged user as well)
  536. # FIXME: propose adding a revoker to the host key if none exist (do we
  537. # have a way to do that after key generation?)
  538. # Ensure that the ssh_host_rsa_key file is present and non-empty:
  539. echo
  540. echo "Checking host SSH key..."
  541. if [ ! -s "${SYSDATADIR}/ssh_host_rsa_key" ] ; then
  542. echo "! The host key as prepared for SSH (${SYSDATADIR}/ssh_host_rsa_key) is missing or empty."
  543. problemsfound=$(($problemsfound+1))
  544. else
  545. if [ $(ls -l "${SYSDATADIR}/ssh_host_rsa_key" | cut -f1 -d\ ) != '-rw-------' ] ; then
  546. echo "! Permissions seem wrong for ${SYSDATADIR}/ssh_host_rsa_key -- should be 0600."
  547. problemsfound=$(($problemsfound+1))
  548. fi
  549. # propose changes needed for sshd_config (if any)
  550. if ! grep -q "^HostKey[[:space:]]\+${SYSDATADIR}/ssh_host_rsa_key$" "$sshd_config"; then
  551. echo "! $sshd_config does not point to the monkeysphere host key (${SYSDATADIR}/ssh_host_rsa_key)."
  552. echo " - Recommendation: add a line to $sshd_config: 'HostKey ${SYSDATADIR}/ssh_host_rsa_key'"
  553. problemsfound=$(($problemsfound+1))
  554. fi
  555. if badhostkeys=$(grep -i '^HostKey' "$sshd_config" | grep -v "^HostKey[[:space:]]\+${SYSDATADIR}/ssh_host_rsa_key$") ; then
  556. echo "! $sshd_config refers to some non-monkeysphere host keys:"
  557. echo "$badhostkeys"
  558. echo " - Recommendation: remove the above HostKey lines from $sshd_config"
  559. problemsfound=$(($problemsfound+1))
  560. fi
  561. fi
  562. fi
  563. # FIXME: look at the ownership/privileges of the various keyrings,
  564. # directories housing them, etc (what should those values be? can
  565. # we make them as minimal as possible?)
  566. # FIXME: look to see that the ownertrust rules are set properly on the
  567. # authentication keyring
  568. # FIXME: make sure that at least one identity certifier exists
  569. # FIXME: look at the timestamps on the monkeysphere-generated
  570. # authorized_keys files -- warn if they seem out-of-date.
  571. # FIXME: check for a cronjob that updates monkeysphere-generated
  572. # authorized_keys?
  573. echo
  574. echo "Checking for MonkeySphere-enabled public-key authentication for users ..."
  575. # Ensure that User ID authentication is enabled:
  576. if ! grep -q "^AuthorizedKeysFile[[:space:]]\+${SYSDATADIR}/authorized_keys/%u$" "$sshd_config"; then
  577. echo "! $sshd_config does not point to monkeysphere authorized keys."
  578. echo " - Recommendation: add a line to $sshd_config: 'AuthorizedKeysFile ${SYSDATADIR}/authorized_keys/%u'"
  579. problemsfound=$(($problemsfound+1))
  580. fi
  581. if badauthorizedkeys=$(grep -i '^AuthorizedKeysFile' "$sshd_config" | grep -v "^AuthorizedKeysFile[[:space:]]\+${SYSDATADIR}/authorized_keys/%u$") ; then
  582. echo "! $sshd_config refers to non-monkeysphere authorized_keys files:"
  583. echo "$badauthorizedkeys"
  584. echo " - Recommendation: remove the above AuthorizedKeysFile lines from $sshd_config"
  585. problemsfound=$(($problemsfound+1))
  586. fi
  587. if [ "$problemsfound" -gt 0 ]; then
  588. echo "When the above $problemsfound issue"$(if [ "$problemsfound" -eq 1 ] ; then echo " is" ; else echo "s are" ; fi)" resolved, please re-run:"
  589. echo " monkeysphere-server diagnostics"
  590. else
  591. echo "Everything seems to be in order!"
  592. fi
  593. }
  594. # retrieve key from web of trust, import it into the host keyring, and
  595. # ltsign the key in the host keyring so that it may certify other keys
  596. add_certifier() {
  597. local domain
  598. local trust
  599. local depth
  600. local keyID
  601. local fingerprint
  602. local ltsignCommand
  603. local trustval
  604. # set default values for trust depth and domain
  605. domain=
  606. trust=full
  607. depth=1
  608. # get options
  609. TEMP=$(PATH="/usr/local/bin:$PATH" getopt -o n:t:d: -l domain:,trust:,depth: -n "$PGRM" -- "$@") || failure "getopt failed! Does your getopt support GNU-style long options?"
  610. if [ $? != 0 ] ; then
  611. exit 1
  612. fi
  613. # Note the quotes around `$TEMP': they are essential!
  614. eval set -- "$TEMP"
  615. while true ; do
  616. case "$1" in
  617. -n|--domain)
  618. domain="$2"
  619. shift 2
  620. ;;
  621. -t|--trust)
  622. trust="$2"
  623. shift 2
  624. ;;
  625. -d|--depth)
  626. depth="$2"
  627. shift 2
  628. ;;
  629. --)
  630. shift
  631. ;;
  632. *)
  633. break
  634. ;;
  635. esac
  636. done
  637. keyID="$1"
  638. if [ -z "$keyID" ] ; then
  639. failure "You must specify the key ID of a key to add, or specify a file to read the key from."
  640. fi
  641. if [ -f "$keyID" ] ; then
  642. echo "Reading key from file '$keyID':"
  643. importinfo=$(gpg_authentication "--import" < "$keyID" 2>&1) || failure "could not read key from '$keyID'"
  644. # FIXME: if this is tried when the key database is not
  645. # up-to-date, i got these errors (using set -x):
  646. # ++ su -m monkeysphere -c '\''gpg --import'\''
  647. # Warning: using insecure memory!
  648. # gpg: key D21739E9: public key "Daniel Kahn Gillmor <dkg@fifthhorseman.net>" imported
  649. # gpg: Total number processed: 1
  650. # gpg: imported: 1 (RSA: 1)
  651. # gpg: can'\''t create `/var/monkeysphere/gnupg-host/pubring.gpg.tmp'\'': Permission denied
  652. # gpg: failed to rebuild keyring cache: Permission denied
  653. # gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
  654. # gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
  655. # gpg: next trustdb check due at 2009-01-10'
  656. # + failure 'could not read key from '\''/root/dkg.gpg'\'''
  657. # + echo 'could not read key from '\''/root/dkg.gpg'\'''
  658. keyID=$(echo "$importinfo" | grep '^gpg: key ' | cut -f2 -d: | cut -f3 -d\ )
  659. if [ -z "$keyID" ] || [ $(echo "$keyID" | wc -l) -ne 1 ] ; then
  660. failure "Expected there to be a single gpg key in the file."
  661. fi
  662. else
  663. # get the key from the key server
  664. gpg_authentication "--keyserver $KEYSERVER --recv-key '0x${keyID}!'" || failure "Could not receive a key with this ID from the '$KEYSERVER' keyserver."
  665. fi
  666. export keyID
  667. # get the full fingerprint of a key ID
  668. fingerprint=$(gpg_authentication "--list-key --with-colons --with-fingerprint 0x${keyID}!" | \
  669. grep '^fpr:' | grep "$keyID" | cut -d: -f10)
  670. if [ -z "$fingerprint" ] ; then
  671. failure "Key '$keyID' not found."
  672. fi
  673. echo
  674. echo "key found:"
  675. gpg_authentication "--fingerprint 0x${fingerprint}!"
  676. echo "Are you sure you want to add the above key as a"
  677. read -p "certifier of users on this system? (y/N) " OK; OK=${OK:-N}
  678. if [ "${OK/y/Y}" != 'Y' ] ; then
  679. failure "Identity certifier not added."
  680. fi
  681. # export the key to the host keyring
  682. gpg_authentication "--export 0x${fingerprint}!" | gpg_host --import
  683. if [ "$trust" == marginal ]; then
  684. trustval=1
  685. elif [ "$trust" == full ]; then
  686. trustval=2
  687. else
  688. failure "Trust value requested ('$trust') was unclear (only 'marginal' or 'full' are supported)."
  689. fi
  690. # ltsign command
  691. # NOTE: *all* user IDs will be ltsigned
  692. ltsignCommand=$(cat <<EOF
  693. ltsign
  694. y
  695. $trustval
  696. $depth
  697. $domain
  698. y
  699. save
  700. EOF
  701. )
  702. # ltsign the key
  703. if echo "$ltsignCommand" | \
  704. gpg_host --quiet --command-fd 0 --edit-key "0x${fingerprint}!" ; then
  705. # update the trustdb for the authentication keyring
  706. gpg_authentication "--check-trustdb"
  707. echo
  708. echo "Identity certifier added."
  709. else
  710. failure "Problem adding identify certifier."
  711. fi
  712. }
  713. # delete a certifiers key from the host keyring
  714. remove_certifier() {
  715. local keyID
  716. local fingerprint
  717. keyID="$1"
  718. if [ -z "$keyID" ] ; then
  719. failure "You must specify the key ID of a key to remove."
  720. fi
  721. if gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-key 0x${keyID}!" ; then
  722. read -p "Really remove above listed identity certifier? (y/N) " OK; OK=${OK:-N}
  723. if [ "${OK/y/Y}" != 'Y' ] ; then
  724. failure "Identity certifier not removed."
  725. fi
  726. else
  727. failure
  728. fi
  729. # delete the requested key
  730. if gpg_authentication "--delete-key --batch --yes 0x${keyID}!" ; then
  731. # delete key from host keyring as well
  732. gpg_host --delete-key --batch --yes "0x${keyID}!"
  733. # update the trustdb for the authentication keyring
  734. gpg_authentication "--check-trustdb"
  735. echo
  736. echo "Identity certifier removed."
  737. else
  738. failure "Problem removing identity certifier."
  739. fi
  740. }
  741. # list the host certifiers
  742. list_certifiers() {
  743. local keys
  744. local key
  745. # find trusted keys in authentication keychain
  746. keys=$(gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-keys --with-colons --fingerprint" | \
  747. grep ^pub: | cut -d: -f2,5 | egrep '^(u|f):' | cut -d: -f2)
  748. # output keys
  749. for key in $keys ; do
  750. gpg_authentication "--no-options --list-options show-uid-validity --keyring ${GNUPGHOME_AUTHENTICATION}/pubring.gpg --list-key --fingerprint $key"
  751. done
  752. }
  753. # issue command to gpg-authentication keyring
  754. gpg_authentication_cmd() {
  755. gpg_authentication "$@"
  756. }
  757. ########################################################################
  758. # MAIN
  759. ########################################################################
  760. # unset variables that should be defined only in config file
  761. unset KEYSERVER
  762. unset AUTHORIZED_USER_IDS
  763. unset RAW_AUTHORIZED_KEYS
  764. unset MONKEYSPHERE_USER
  765. # load configuration file
  766. [ -e ${MONKEYSPHERE_SERVER_CONFIG:="${SYSCONFIGDIR}/monkeysphere-server.conf"} ] && . "$MONKEYSPHERE_SERVER_CONFIG"
  767. # set empty config variable with ones from the environment, or with
  768. # defaults
  769. LOG_LEVEL=${MONKEYSPHERE_LOG_LEVEL:=${LOG_LEVEL:="INFO"}}
  770. KEYSERVER=${MONKEYSPHERE_KEYSERVER:=${KEYSERVER:="pool.sks-keyservers.net"}}
  771. AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:=${AUTHORIZED_USER_IDS:="%h/.monkeysphere/authorized_user_ids"}}
  772. RAW_AUTHORIZED_KEYS=${MONKEYSPHERE_RAW_AUTHORIZED_KEYS:=${RAW_AUTHORIZED_KEYS:="%h/.ssh/authorized_keys"}}
  773. MONKEYSPHERE_USER=${MONKEYSPHERE_MONKEYSPHERE_USER:=${MONKEYSPHERE_USER:="monkeysphere"}}
  774. # other variables
  775. CHECK_KEYSERVER=${MONKEYSPHERE_CHECK_KEYSERVER:="true"}
  776. REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"}
  777. GNUPGHOME_HOST=${MONKEYSPHERE_GNUPGHOME_HOST:="${SYSDATADIR}/gnupg-host"}
  778. GNUPGHOME_AUTHENTICATION=${MONKEYSPHERE_GNUPGHOME_AUTHENTICATION:="${SYSDATADIR}/gnupg-authentication"}
  779. # export variables needed in su invocation
  780. export DATE
  781. export MODE
  782. export MONKEYSPHERE_USER
  783. export LOG_LEVEL
  784. export KEYSERVER
  785. export CHECK_KEYSERVER
  786. export REQUIRED_USER_KEY_CAPABILITY
  787. export GNUPGHOME_HOST
  788. export GNUPGHOME_AUTHENTICATION
  789. export GNUPGHOME
  790. # get subcommand
  791. COMMAND="$1"
  792. [ "$COMMAND" ] || failure "Type '$PGRM help' for usage."
  793. shift
  794. case $COMMAND in
  795. 'update-users'|'update-user'|'u')
  796. update_users "$@"
  797. ;;
  798. 'gen-key'|'g')
  799. gen_key "$@"
  800. ;;
  801. 'extend-key'|'e')
  802. extend_key "$@"
  803. ;;
  804. 'add-hostname'|'add-name'|'n+')
  805. add_hostname "$@"
  806. ;;
  807. 'revoke-hostname'|'revoke-name'|'n-')
  808. revoke_hostname "$@"
  809. ;;
  810. 'show-key'|'show'|'s')
  811. show_server_key
  812. ;;
  813. 'publish-key'|'publish'|'p')
  814. publish_server_key
  815. ;;
  816. 'diagnostics'|'d')
  817. diagnostics
  818. ;;
  819. 'add-identity-certifier'|'add-id-certifier'|'add-certifier'|'c+')
  820. add_certifier "$@"
  821. ;;
  822. 'remove-identity-certifier'|'remove-id-certifier'|'remove-certifier'|'c-')
  823. remove_certifier "$@"
  824. ;;
  825. 'list-identity-certifiers'|'list-id-certifiers'|'list-certifiers'|'list-certifier'|'c')
  826. list_certifiers "$@"
  827. ;;
  828. 'gpg-authentication-cmd')
  829. gpg_authentication_cmd "$@"
  830. ;;
  831. '--help'|'help'|'-h'|'h'|'?')
  832. usage
  833. ;;
  834. *)
  835. failure "Unknown command: '$COMMAND'
  836. Type '$PGRM help' for usage."
  837. ;;
  838. esac
  839. exit "$RETURN"