From ab3b8b85c19b8fce28b13fe5aa4cf3c099908d88 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 6 Jul 2016 21:27:45 -0700 Subject: [PATCH] Completely refactor configuration handling, including adding support for RABBITMQ_SSL_VERIFY and RABBITMQ_SSL_FAIL_IF_NO_PEER_CERT and better error handling --- Dockerfile | 2 +- docker-entrypoint.sh | 254 +++++++++++++++++++++++++++---------------- 2 files changed, 164 insertions(+), 92 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6b197001..00590fc7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -59,7 +59,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # /usr/sbin/rabbitmq-server has some irritating behavior, and only exists to "su - rabbitmq /usr/lib/rabbitmq/bin/rabbitmq-server ..." ENV PATH /usr/lib/rabbitmq/bin:$PATH -RUN echo '[{rabbit, [{loopback_users, []}]}].' > /etc/rabbitmq/rabbitmq.config +RUN echo '[ { rabbit, [ { loopback_users, [ ] } ] } ].' > /etc/rabbitmq/rabbitmq.config # set home so that any `--user` knows where to put the erlang cookie ENV HOME /var/lib/rabbitmq diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 4615457a..c56ea237 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -9,15 +9,25 @@ if [[ "$1" == rabbitmq* ]] && [ "$(id -u)" = '0' ]; then exec gosu rabbitmq "$BASH_SOURCE" "$@" fi +# backwards compatibility for old environment variables +: "${RABBITMQ_SSL_CERTFILE:=${RABBITMQ_SSL_CERT_FILE:-}}" +: "${RABBITMQ_SSL_KEYFILE:=${RABBITMQ_SSL_KEY_FILE:-}}" +: "${RABBITMQ_SSL_CACERTFILE:=${RABBITMQ_SSL_CA_FILE:-}}" + +# https://www.rabbitmq.com/configure.html +fileConfigs=( + ssl_cacertfile + ssl_certfile + ssl_keyfile +) configs=( - # https://www.rabbitmq.com/configure.html default_pass default_user default_vhost hipe_compile - ssl_ca_file - ssl_cert_file - ssl_key_file + ssl_fail_if_no_peer_cert + ssl_verify + "${fileConfigs[@]}" ) haveConfig= @@ -32,6 +42,46 @@ for conf in "${configs[@]}"; do fi fi done +if [ "$haveSslConfig" ]; then + missing=() + for sslConf in cacertfile certfile keyfile; do + var="RABBITMQ_SSL_${sslConf^^}" + val="${!var}" + if [ -z "$val" ]; then + missing+=( "$var" ) + fi + done + if [ "${#missing[@]}" -gt 0 ]; then + { + echo + echo 'error: SSL requested, but missing required configuration' + for miss in "${missing[@]}"; do + echo " - $miss" + done + echo + } >&2 + exit 1 + fi +fi +missingFiles=() +for conf in "${fileConfigs[@]}"; do + var="RABBITMQ_${conf^^}" + val="${!var}" + if [ "$val" ] && [ ! -f "$val" ]; then + missingFiles+=( "$val ($var)" ) + fi +done +if [ "${#missingFiles[@]}" -gt 0 ]; then + { + echo + echo 'error: files specified, but missing' + for miss in "${missingFiles[@]}"; do + echo " - $miss" + done + echo + } >&2 + exit 1 +fi # If long & short hostnames are not the same, use long hostnames if [ "$(hostname)" != "$(hostname -s)" ]; then @@ -52,116 +102,138 @@ if [ "$RABBITMQ_ERLANG_COOKIE" ]; then fi fi -if [ "$1" = 'rabbitmq-server' ]; then - if [ "$haveConfig" ]; then - cat > /etc/rabbitmq/rabbitmq.config <<-'EOH' - [ - {rabbit, - [ - EOH - - if [ "$haveSslConfig" ]; then - cat >> /etc/rabbitmq/rabbitmq.config <<-EOS - { tcp_listeners, [ ] }, - { ssl_listeners, [ 5671 ] }, - { ssl_options, [ - { certfile, "$RABBITMQ_SSL_CERT_FILE" }, - { keyfile, "$RABBITMQ_SSL_KEY_FILE" }, - { cacertfile, "$RABBITMQ_SSL_CA_FILE" }, - { verify, verify_peer }, - { fail_if_no_peer_cert, true } ] }, - EOS - else - cat >> /etc/rabbitmq/rabbitmq.config <<-EOS - { tcp_listeners, [ 5672 ] }, - { ssl_listeners, [ ] }, - EOS - fi - +# prints "$2$1$3$1...$N" +join() { + local sep="$1"; shift + local out; printf -v out "${sep//%/%%}%s" "$@" + echo "${out#$sep}" +} +indent() { + if [ "$#" -gt 0 ]; then + echo "$@" + else + cat + fi | sed 's/^/\t/g' +} +rabbit_array() { + echo -n '[' + case "$#" in + 0) echo -n ' ' ;; + 1) echo -n " $1 " ;; + *) + local vals="$(join $',\n' "$@")" + echo + indent "$vals" + esac + echo -n ']' +} + +if [ "$1" = 'rabbitmq-server' ] && [ "$haveConfig" ]; then + rabbitConfig=( + "{ loopback_users, $(rabbit_array) }" + ) + + rabbitSslOptions=() + if [ "$haveSslConfig" ]; then for conf in "${configs[@]}"; do + sslConf="${conf#ssl_}" + [ "$sslConf" != "$conf" ] || continue + var="RABBITMQ_${conf^^}" val="${!var}" - rawVal= - case "$conf" in - # SSL-related options are configured above, so should be ignored here - ssl_*) continue ;; + # default values + case "$sslConf" in + verify) : "${val:=verify_peer}" ;; + fail_if_no_peer_cert) : "${val:=true}" ;; + esac - # convert shell booleans into Erlang booleans - hipe_compile) - [ "$val" ] && rawVal='true' || rawVal='false' - ;; + rawVal= + case "$sslConf" in + verify|fail_if_no_peer_cert) rawVal="$val" ;; - # otherwise, assume string-based (and skip or add appropriate decorations) *) [ "$val" ] || continue - rawVal='<<"'"$val"'">>' + rawVal='"'"$val"'"' ;; esac [ "$rawVal" ] || continue - cat >> /etc/rabbitmq/rabbitmq.config <<-EOC - { $conf, $rawVal }, - EOC + rabbitSslOptions+=( "{ $sslConf, $rawVal }" ) done - cat >> /etc/rabbitmq/rabbitmq.config <<-'EOF' - {loopback_users, []} - EOF - - # If management plugin is installed, then generate config consider this - if [ "$(rabbitmq-plugins list -m -e rabbitmq_management)" ]; then - cat >> /etc/rabbitmq/rabbitmq.config <<-'EOF' - ] - }, - { rabbitmq_management, [ - { listener, [ - EOF - - if [ "$haveSslConfig" ]; then - cat >> /etc/rabbitmq/rabbitmq.config <<-EOS - { port, 15671 }, - { ssl, true }, - { ssl_opts, [ - { certfile, "$RABBITMQ_SSL_CERT_FILE" }, - { keyfile, "$RABBITMQ_SSL_KEY_FILE" }, - { cacertfile, "$RABBITMQ_SSL_CA_FILE" }, - { verify, verify_none }, - { fail_if_no_peer_cert, false } - ] - } - EOS - else - cat >> /etc/rabbitmq/rabbitmq.config <<-EOS - { port, 15672 }, - { ssl, false } - EOS - fi - - cat >> /etc/rabbitmq/rabbitmq.config <<-EOS - ] - } - EOS - fi + rabbitConfig+=( + "{ tcp_listeners, $(rabbit_array) }" + "{ ssl_listeners, $(rabbit_array 5671) }" + "{ ssl_options, $(rabbit_array "${rabbitSslOptions[@]}") }" + ) + else + rabbitConfig+=( + "{ tcp_listeners, $(rabbit_array 5672) }" + "{ ssl_listeners, $(rabbit_array) }" + ) + fi - cat >> /etc/rabbitmq/rabbitmq.config <<-'EOF' - ] - } - ]. - EOF + for conf in "${configs[@]}"; do + var="RABBITMQ_${conf^^}" + val="${!var}" + + rawVal= + case "$conf" in + # SSL-related options are configured above, so should be ignored here + ssl_*) continue ;; + + # convert shell booleans into Erlang booleans + hipe_compile) + [ "$val" ] && rawVal='true' || rawVal='false' + ;; + + # otherwise, assume string-based (and skip or add appropriate decorations) + *) + [ "$val" ] || continue + rawVal='<<"'"$val"'">>' + ;; + esac + [ "$rawVal" ] || continue + + rabbitConfig+=( "{ $conf, $rawVal }" ) + done + + # If management plugin is installed, then generate config consider this + if [ "$(rabbitmq-plugins list -m -e rabbitmq_management)" ]; then + rabbitManagementListenerConfig=() + if [ "$haveSslConfig" ]; then + rabbitManagementListenerConfig+=( + '{ port, 15671 }' + '{ ssl, true }' + "{ ssl_opts, $(rabbit_array "${rabbitSslOptions[@]}") }" + ) + else + rabbitManagementListenerConfig+=( + '{ port, 15672 }' + '{ ssl, false }' + ) + fi + rabbitConfig+=( + "{ rabbitmq_management, $(rabbit_array "{ listener, $(rabbit_array "${rabbitManagementListenerConfig[@]}") }") }" + ) fi + + echo "$(rabbit_array "{ rabbit, $(rabbit_array "${rabbitConfig[@]}") }")." > /etc/rabbitmq/rabbitmq.config fi -if [ "$haveSslConfig" ]; then +combinedSsl='/tmp/combined.pem' +if [ "$haveSslConfig" ] && [[ "$1" == rabbitmq* ]] && [ ! -f "$combinedSsl" ]; then # Create combined cert - cat "$RABBITMQ_SSL_CERT_FILE" "$RABBITMQ_SSL_KEY_FILE" > /tmp/combined.pem - chmod 0400 /tmp/combined.pem - + cat "$RABBITMQ_SSL_CERTFILE" "$RABBITMQ_SSL_KEYFILE" > "$combinedSsl" + chmod 0400 "$combinedSsl" +fi +if [ "$haveSslConfig" ] && [ -f "$combinedSsl" ]; then # More ENV vars for make clustering happiness # we don't handle clustering in this script, but these args should ensure # clustered SSL-enabled members will talk nicely export ERL_SSL_PATH="$(erl -eval 'io:format("~p", [code:lib_dir(ssl, ebin)]),halt().' -noshell)" - export RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="-pa '$ERL_SSL_PATH' -proto_dist inet_tls -ssl_dist_opt server_certfile /tmp/combined.pem -ssl_dist_opt server_secure_renegotiate true client_secure_renegotiate true" + export RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS="-pa '$ERL_SSL_PATH' -proto_dist inet_tls -ssl_dist_opt server_certfile '$combinedSsl' -ssl_dist_opt server_secure_renegotiate true client_secure_renegotiate true" export RABBITMQ_CTL_ERL_ARGS="$RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS" fi