#! /bin/bash
#
set -e
set -u


# Steps:
# Update puppet.conf
# Move the existing SSL directory out of the way
# Generate a new CA
# Configure Apache to trust clients with certificates issued by the new and old CA.
# Add the cve20113872::newcsr class to all of the agent catalogs.

# Check if the master is running.
if [[ -f $(puppet master --configprint pidfile) ]]; then
  echo "You must stop your Webrick puppet master before running this script" >&2
  exit 1
fi


module="cve20113872"
vardir="$(puppet master --configprint vardir)"
confdir="$(puppet master --configprint confdir)"
ssldir="$(puppet master --configprint ssldir)"
puppetconf="$(puppet master --configprint config)"
certname="$(puppet master --configprint certname)"
old_ca_cn="$(puppet master --configprint ca_name)"

timestamp="$(ruby -e 'puts Time.now.to_i')"

# If this starts at 0, incrementing it will exit nonzero
# See: https://gist.github.com/1310371
# and https://github.com/puppetlabs/puppetlabs-cve20113872/issues/69
idx=1

# This is some shell magic to read a file into a variable if the
# variable isn't already set.
: ${DNS_NAME:=$(cat "${vardir}/${module}/dns_name")}
if [[ -z "${DNS_NAME}" ]]; then
  echo "Error: Could not determine the intermediate DNS name from step 1." >&2
  echo "Did you run step 1 first?" >&2
  exit 1
fi
: ${DNS_ALT_NAMES:=$(cat "${vardir}/${module}/alt_names")}
if [[ -z "${DNS_ALT_NAMES}" ]]; then
  echo "Error: Could not determine the certdnsnames from step 1." >&2
  echo "Did you run step 1 first?" >&2
  echo "If you do not want any alternate names in your new master certificate" >&2
  echo "simply touch ${vardir}/${module}/alt_names to create an empty file." >&2
  exit 1
fi

# Figure out if we've been patched or not.
if puppet master --configprint dns_alt_names &>/dev/null; then
  HAVE_PATCHED='true'
  alt_names_option="dns_alt_names"
  alt_names_separator=","
else
  HAVE_PATCHED='false'
  alt_names_option="certdnsnames"
  alt_names_separator=":"
fi

# This is disabled at this point in puppet.conf, so we have to read it from what
# we wrote out in step1
alt_names_value="${DNS_ALT_NAMES}"
intermediate_name="${DNS_NAME}"

# Patch the CA name setting in puppet.conf
# This is a ruby implementation of "grep"
if ruby -e 'while gets() do; exit(0) if /^\s*ca_name\s*=/; end; exit 1;' "${puppetconf}"
then
  # This replacement is for the case we already have ca_name in the config file
  ruby -p -l -i.backup.${timestamp}.${idx} -e \
    'gsub(/^(\s*)(\s*ca_name)(\s*=\s*)(.*)$/) { "#{$1}# CVE-2011-3872 Previous Name: #{$4}\n#{$1}ca_name = '"'Puppet CA: ${intermediate_name} ${timestamp}'"'" }' \
    "${puppetconf}"
  ((idx++))
else
  echo -n "Adding ca_name setting to [main] in puppet.conf ..." >&2
  # This replacement is for when we need to add ca_name to the config file
  ruby -p -l -i.backup.${timestamp}.${idx} -e \
    'gsub(/^(\s*)(\[main\].*)$/) { "#{$2}\n    # CVE-2011-3872 Previous Name: '"${old_ca_cn}"'\n    ca_name = '"'Puppet CA: ${intermediate_name} ${timestamp}'"'" }' \
    "${puppetconf}"
  ((idx++))
  echo "done." >&2
fi

# Move the SSL directory.
oldssldir="${ssldir}.previous"
echo -n "Moving ${ssldir} to ${oldssldir} ..." >&2
mv "${ssldir}" "${oldssldir}"
echo "done." >&2

# Generate the new CA (The ca_name setting needs to be set in puppet.conf when
# this is run.)
echo -n "Generating the new CA ..." >&2
puppet cert --generate "--${alt_names_option}" "${alt_names_value}" "${certname}" >/dev/null
echo "done." >&2

# Copy the new, secure SSL certificate generated by the OLD CA into the location
# used by the PE Apache server
echo -n "Copying puppet master's secured certificate into place ..." >&2
for d in certs private_keys public_keys; do
  # NOTE, we copy the new cert, e.g. "puppetmaster.secure" to the old name, e.g. "puppetmaster"
  cp -p "${oldssldir}/${d}/${certname}.pem" "${ssldir}/${d}/${certname}.pem"
done
echo "done." >&2

# JJM We're appending the old certificate to the new certificate This allows us
# to avoid hacking at the Apache configuration file.

echo -n "Appending previous CA certificate to the new CA certificate ..." >&2
cat "${oldssldir}/certs/ca.pem" >> "${ssldir}/certs/ca.pem"
# The previous CRL needs to be listed first, otherwise the agent on the master
# gets a certificate verify failed error.
cp -p "${ssldir}/crl.pem" "${ssldir}/crl.pem.new"
cat "${oldssldir}/crl.pem" > "${ssldir}/crl.pem"
cat "${ssldir}/crl.pem.new" >> "${ssldir}/crl.pem"
# Clean up to prevent confusion
rm -f "${ssldir}/crl.pem.new"
echo "done." >&2

echo -n "Directing cacert at bundled localcacert certificate file ..." >&2
# Patch the CA cert setting in puppet.conf
# This is a ruby implementation of "grep"
if ruby -e 'while gets() do; exit(0) if /^\s*cacert\s*=/; end; exit 1;' "${puppetconf}"
then
  # This replacement is for the case we already have cacrt in the config file
  ruby -p -l -i.backup.${timestamp}.${idx} -e \
    'gsub(/^(\s*)(\s*cacert)(\s*=\s*)(.*)$/) { "#{$1}# CVE-2011-3872 \n#{$1}cacrt = '"${ssldir}"'/certs/ca.pem" }' \
    "${puppetconf}"
  ((idx++))
else
  echo -n "Adding cacert setting to [main] in puppet.conf ..." >&2
  # This replacement is for when we need to add cacrt to the config file
  ruby -p -l -i.backup.${timestamp}.${idx} -e \
    'gsub(/^(\s*)(\[main\].*)$/) { "#{$2}\n    # CVE-2011-3872\n    cacrt = '"${ssldir}"'/certs/ca.pem" }' \
    "${puppetconf}"
  ((idx++))
  echo "done." >&2
fi

echo -n "Directing cacrl at bundled hostcrl certificate file ..." >&2
# Patch the CA cert setting in puppet.conf
# This is a ruby implementation of "grep"
if ruby -e 'while gets() do; exit(0) if /^\s*cacrl\s*=/; end; exit 1;' "${puppetconf}"
then
  # This replacement is for the case we already have cacrl in the config file
  ruby -p -l -i.backup.${timestamp}.${idx} -e \
    'gsub(/^(\s*)(\s*cacrl)(\s*=\s*)(.*)$/) { "#{$1}# CVE-2011-3872 \n#{$1}cacrl = '"${ssldir}"'/crl.pem" }' \
    "${puppetconf}"
  ((idx++))
else
  echo -n "Adding cacrl setting to [main] in puppet.conf ..." >&2
  # This replacement is for when we need to add cacrt to the config file
  ruby -p -l -i.backup.${timestamp}.${idx} -e \
    'gsub(/^(\s*)(\[main\].*)$/) { "#{$2}\n    # CVE-2011-3872\n    cacrl = '"${ssldir}"'/crl.pem" }' \
    "${puppetconf}"
  ((idx++))
  echo "done." >&2
fi


echo "You should now start your webrick puppet master." >&2
