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