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