#! /bin/bash

# This script must be run from the RHV Manager and it will renew the hypervisor's expired certificates
# and restart the hypervisor services in an unattended way (in the best case) .
# It will perform the following steps:
#  - If the CA certificate is not expired it will check if it has valid start and end dates
#    or it will renew it (See: https://access.redhat.com/solutions/3368861)
#  - If the CA certificate is expired it will enable global maitenance mode if an SHE installation is detected,
#    stop the engine and run engine-setup to renew the CA itself and all the engine certs.
#  - It will connect to the database using engine-pgsql.sh script to get the list of hypervisors.
#  - For each hypervisor it will check if the certificate is expired.
#  - If the certificate expired it will check if a password-less SSH connection to the hypervisor is available
#    and if not it will configure it (in this case the hypervisor's root password will be required)
#  - It will check if the CSR is available locally and if not, it will connect to the hypervisor to regenerate it.
#  - Then it will renew the certificate using 'pki-enroll-request.sh' script, and copy the new certificate
#    to the hypervisor along with the CA if it was renewed in previous steps.
#  - Once the new certificate is copied to the required destinations it will connect to the hypervisor and restart
#    the vdsmd and imageio services
#  - As final step it will start the engine again and it will disable global maintenance mode in case it was enabled.

set -e

engine_scripts_dir="/usr/share/ovirt-engine"
enroll_request="${engine_scripts_dir}/bin/pki-enroll-request.sh"
create_ca="${engine_scripts_dir}/bin/pki-create-ca.sh"
engine_psql="${engine_scripts_dir}/dbscripts/engine-psql.sh"

pki_dir="/etc/pki/ovirt-engine"
certs_dir="${pki_dir}/certs"
requests_dir="${pki_dir}/requests"
ca_file="${pki_dir}/ca.pem"
ca_truststore="${pki_dir}/.truststore"

ssh_key="${pki_dir}/keys/engine_id_rsa"
ssh_key_pub="${pki_dir}/keys/engine_id_rsa.pub"

ok='\e[32m\u25B6 GOOD:\e[0m'
error='\e[31m\u25B6 ERROR\e[0m'
warn='\e[33m\u25B6 WARN:\e[0m'
info='\e[34m\u25B6 INFO:\e[0m'

log() {
    echo -e "[$(date --rfc-3339=seconds)]: $*"
}

log_info() {
    log $info $*
}

log_ok() {
    log $ok $*
}

log_error() {
    log $error $*
}

log_warn() {
    log $warn $*
}

ssh_copy_id() {
    host="${1}"
    key_present='1'
    ssh -o 'PasswordAuthentication no' -o 'StrictHostKeyChecking no' -i "${ssh_key}" "root@${host}" exit 0 || key_present=0
    if [ "${key_present}" != "1" ]; then
        log_warn "SSH key is not present on host '${host}' copying it, you will need to enter the host's root password"
        ssh-copy-id -i "${ssh_key_pub}" "root@${host}"
    fi
}

ssh_run() {
    host="${1}"
    cmd="${@:2}"
    ssh_copy_id "${host}"
    ssh -i "${ssh_key}" "root@${host}" "${cmd}"
}

scp_run() {
    host="${1}"
    src="${2}"
    dst="${3}"
    ssh_copy_id "${host}"
    scp -i "${ssh_key}" "${src}" "root@${host}:${dst}"
}


OPTIND=1
force_renew=false


while getopts "hf" opt; do
   case "${opt}" in
   f)
       force_renew=true
       ;;
   *)
       echo "$0 [-f]"
       echo "  -f force certificate renewal even if not needed"
       exit 0
       ;;
   esac
done

if [ "$UID" != 0 ]; then
  log_error "This script needs to be run as root"
  exit 1
fi

if [ ! -f "${engine_psql}" -o ! -f "${enroll_request}" -o ! -f "${create_ca}" ]; then
  log_error "Engine required script does not exist. This script must be run within the RHV Manager"
  exit 1
fi

if [ ! -f "${ssh_key_pub}" ]; then
  log_info "Engine SSH pub key does not exist, generating it"
  ssh-keygen -y -f "${ssh_key}" > "${ssh_key_pub}"
fi

ca_updated=false
ca_date_gmt_count="$(openssl x509 -noout -startdate -enddate -in ${ca_file} | grep -c GMT)"
ca_expired="$(openssl verify -CAfile ${ca_file} ${ca_file} 2>&1 | grep -c -e 'error 10 at' -e 'certificate has expired' ||:)"
she_host="$(${engine_psql} --no-align -t -c 'select host_name from vds where is_hosted_engine_host limit 1;')"
sleep 1

if [ "$ca_expired" = "1" -o "${ca_date_gmt_count}" != "2" -o "${force_renew}" = "true" ]; then
  if [ ! -z "$she_host" ]; then
    log_info "Removing '${she_host}' from the 'known_hosts' file to avoid conflicts"
    ssh-keygen -R ${she_host}
    log_info "Enabling Global Maintenance mode on host ${she_host}"
    ssh_run "${she_host}" hosted-engine --set-maintenance --mode=global
  fi
  log_info "Stopping the oVirt Engine service"
  systemctl stop httpd
  systemctl stop ovirt-engine
  if [ "${ca_expired}" = "1" ]; then
    log_warn 'CA Certificate expired, running engine-setup to fix it'
    engine-setup --accept-defaults
  else
    if [ "${ca_date_gmt_count}" != "2" ]; then
      log_warn 'CA Certificate has an invalid date we need to renew it. (See https://access.redhat.com/solutions/3368861)'
    fi
    log_info 'Renewing CA Certificate.'
    cp "${ca_truststore}" "${ca_truststore}.$(date '+%Y%m%d%H%M%S')"
    ${create_ca} --renew --keystore-password=mypass
  fi
  ca_updated=true
fi


#ca_expires_in_seconds="$(date -d "$(openssl x509 -in ${ca_file} -noout -enddate| sed -e 's/^notAfter=//')" +%s)"
#now_in_seconds="$(date +%s)"
#ca_expires_in_days="$(( (ca_expires_in_seconds - now_in_seconds) / (60 * 60 * 24)))"
#days="${ca_expires_in_days}"


host_name=$1
echo $host_name
  if [ "$host_name" != "" ]; then
    log_info "Verifying certificate for host '${host_name}'"
    cert_file="${certs_dir}/${host_name}.cer"
#    subject="$(openssl x509 -in ${cert_file} -noout -subject|sed -e 's/^subject= //')"
    subject="$(openssl x509 -in ${cert_file} -noout -subject|sed -e 's/subject=/\//' |sed -e's/,/\//' |sed -e 's/ //g')"
    expired="$(openssl verify -CAfile ${ca_file} ${cert_file} 2>&1 | grep -c -e 'error 10 at' -e 'certificate has expired' ||:)"

    if [ "${expired}" = "1" -o "${force_renew}" = "true" ] ; then
      log_info "Removing '${host_name}' from the 'known_hosts' file to avoid conflicts"
      ssh-keygen -R ${host_name}
      log_warn "Certificate for host '${host_name}' has expired"

      req_file="${requests_dir}/${host_name}.req"
      if [ ! -f  "${req_file}" ]; then
        log_warn "CSR does not exists, connecting to ${host_name} to regenerate it"
        ssh_run "${host_name}" openssl req -new -key "/etc/pki/vdsm/keys/vdsmkey.pem" -subj "${subject}" > "${req_file}"
      fi

      log_info "Enrolling host ${host_name}"
      PWD="${pki_dir}" ${enroll_request} --name="${host_name}" --subject="${subject}" --days=1825
      log_info "Copying the certifificates to '${host_name}'"
      scp_run "${host_name}" "${cert_file}" "/etc/pki/vdsm/certs/vdsmcert.pem" &>/dev/null
      scp_run "${host_name}" "${cert_file}" "/etc/pki/libvirt/clientcert.pem" &>/dev/null
      scp_run "${host_name}" "${cert_file}" "/etc/pki/vdsm/libvirt-spice/server-cert.pem" &>/dev/null
      scp_run "${host_name}" "${cert_file}" "/etc/pki/vdsm/libvirt-vnc/server-cert.pem" &>/dev/null
      if [ "$ca_updated" = true ]; then
        log_info "Refreshing the CA certificate on host '${host_name}'"
        scp -i "${ssh_key}" "${pki_dir}/ca.pem" "root@${host_name}:/etc/pki/vdsm/certs/cacert.pem"  &>/dev/null
        scp -i "${ssh_key}" "${pki_dir}/ca.pem" "root@${host_name}:/etc/pki/vdsm/libvirt-spice/ca-cert.pem" &>/dev/null
        scp -i "${ssh_key}" "${pki_dir}/ca.pem" "root@${host_name}:/etc/pki/CA/cacert.pem" &>/dev/null
	scp -i "${ssh_key}" "${pki_dir}/ca.pem" "root@${host_name}:/etc/pki/vdsm/libvirt-vnc/ca-cert.pem" &>/dev/null
      fi
      log_info "Restarting libvirtd service on host '${host_name}'"
      ssh_run "${host_name}" systemctl restart libvirtd
      log_info "Restarting vdsm service on host '${host_name}'"
      ssh_run "${host_name}" systemctl restart vdsmd
      log_info "Restarting ovirt-imageio service on host '${host_name}'"
      ssh_run "${host_name}" systemctl restart ovirt-imageio
    else
      log_ok "Certificate not expired for host '${host_name}', skipping..."
    fi
  else
    log_error "Unable to get host's list, is the database running?"
    exit 1
  fi


if [ "${ca_updated}" = true ]; then
   log_info "Starting the oVirt Engine service"
   systemctl start ovirt-engine
   systemctl start httpd
   if [ ! -z "$she_host" ]; then
     sleep 30
     log_info "Disabling Global Maintenance mode on host ${she_host}"
     ssh_run ${she_host} hosted-engine --set-maintenance --mode=none
   fi
fi

