Skip to content
Snippets Groups Projects
acme-downloader.sh 11.62 KiB
#!/bin/bash
#
# exit 0: the certificate still looks good and it won't be replaced
# exit 64: a new certificate was installed and we need to reload our service
# anything else: something went wrong
#
# the certificate will be checked, if it is valid, if the key matches and
# if it contains at least the cert-name
#
PROG_VERSION="VERSION_SET_BY_CI"
BUILD_TIME="BUILDTIME_SET_BY_CI"
REDIS_URL="https://redis.geant.org/GET"
VAULT_URL="https://vault.geant.org/v1"
TMP_CERT=$(mktemp)
TMP_FULLCHAIN=$(mktemp)
TMP_CA=$(mktemp)
TMP_KEY=$(mktemp)

tty -s && stty -echoctl # hide ^C

# function called by trap
clean_up() {
    rm -f $TMP_CERT $TMP_FULLCHAIN $TMP_CA $TMP_KEY
    if [ "$#" -eq 1 ]; then
        exit $1
    fi
    exit
}
trap 'clean_up' SIGINT

if [ $(id -u) -ne 0 ]; then
    echo -e "plase run this script as root\ngiving up..."
    clean_up 2
elif ! which jq &>/dev/null; then
    echo -e "please install jq\ngiving up..."
    clean_up 2
elif ! which curl &>/dev/null; then
    echo -e "please install curl\ngiving up..."
    clean_up 2
fi

check_version() {
    # check upstrem version
    SCRIPT_URL="https://artifactory.software.geant.org/artifactory/acme-downloader/acme-downloader.sh"
    METADATA_URL="https://artifactory.software.geant.org/artifactory/api/storage/acme-downloader/acme-downloader.sh?properties=version"
    REMOTE_VERSION=$(curl -s $METADATA_URL | jq -j .properties.version[0])
    if [[ $PROG_VERSION != $REMOTE_VERSION ]]; then
        echo ""
        echo "you are running $(basename $0) ${PROG_VERSION}"
        echo "$(basename $0) $REMOTE_VERSION is available"
        echo "to install the new version you can run: $(basename $0) --update"
        echo ""
        return 1
    else
        if [[ -n $CHECK ]]; then
            echo "you are running the latest version"
        fi
    fi
    if [[ -n $CHECK ]]; then
        exit 0
    fi
}

# lsb_release is not always installed
if ! source /etc/os-release &>/dev/null; then
    echo "no idea what to do with this OS: I was not able to access /etc/os-release"
    echo ""
    clean_up 2
elif [[ "$ID" == "ubuntu" ]] || [[ "$ID" == "debian" ]] || [[ "$ID" == "arch"* ]]; then
    CERT_BASE="/etc/ssl/certs"
    KEY_BASE="/etc/ssl/private"
    GROUPNAME="ssl-cert"
elif [[ "$ID" == "centos" ]] || [[ "$ID" == "rhel" ]]; then
    CERT_BASE="/etc/pki/tls/certs"
    KEY_BASE="/etc/pki/tls/private"
    GROUPNAME="root"
else
    echo "no idea what to do with OS: $ID"
    echo "please help us to amend this script accordingly"
    echo ""
    clean_up 2
fi

usage() {
    echo "Usage: $(basename $0)"
    echo "    -h | --help [Print this help and exit]"
    echo "    --redis-token (Redis access token)"
    echo "    --vault-token (Vault access token)"
    echo "    --cert-name (Certificate name)"
    echo "    --team-name (Team name: swd, it, neteng, nmaas ...)"
    echo "    --type (OV or EV)"
    echo "    --days [OPTIONAL check days before expiration. Default: 30)"
    echo "    --cert-destination [OPTIONAL Default: ${CERT_BASE}/<cert-name>.crt]"
    echo "    --fullchain-destination [OPTIONAL Default: ${CERT_BASE}/<cert-name>_fullchain.crt]"
    echo "    --key-destination [OPTIONAL Default: ${KEY_BASE}/<cert-name>.key]"
    echo "    --ca-destination [OPTIONAL Default: ${CERT_BASE}/COMODO_<type>.crt]"
    echo "    --wildcard [OPTIONAL if the certificate is wildcard]"
    echo "    --check-version [OPTIONAL check difference with upstream exit]"
    echo "    --update [OPTIONAL self-updates the script if a new version is available and exit]"
    echo "    --force-update [OPTIONAL always self-updates the script and exit]"
    echo ""
    clean_up 2
}

OPTS=$(getopt -o "h" --longoptions "help,redis-token:,vault-token:,cert-name:,team-name:,days:,type:,cert-destination:,fullchain-destination:,key-destination:,ca-destination:,check-version,update,force-update,version,wildcard" -- "$@")
eval set -- "$OPTS"

while true; do
    case "$1" in
    -h | --help)
        usage
        ;;
    --redis-token)
        shift
        REDIS_TOKEN="${1}"
        ;;
    --vault-token)
        shift
        VAULT_TOKEN="${1}"
        ;;
    --cert-name)
        shift
        CERT_NAME="${1}"
        ;;
    --team-name)
        shift
        TEAM_NAME="${1}"
        ;;
    --days)
        shift
        DAYS="${1}"
        ;;
    --type)
        shift
        TYPE="${1}"
        ;;
    --cert-destination)
        shift
        CERT_DESTINATION="${1}"
        ;;
    --fullchain-destination)
        shift
        FULLCHAIN_DESTINATION="${1}"
        ;;
    --key-destination)
        shift
        KEY_DESTINATION="${1}"
        ;;
    --ca-destination)
        shift
        CA_DESTINATION="${1}"
        ;;
    --wildcard)
        WILDCARD='WILDCARD'
        ;;
    --update)
        UPDATE='UPDATE'
        ;;
    --force-update)
        FORCE_UPDATE='FORCE_UPDATE'
        ;;
    --check-version)
        CHECK='CHECK'
        ;;
    --version)
        PARAM_VERSION='PARAM_VERSION'
        ;;
    --)
        shift
        break
        ;;
    esac
    shift
done

# print version buildtime and say goodbye
if [[ -n $PARAM_VERSION ]]; then
    echo "$(basename $0) version $PROG_VERSION built on ${BUILD_TIME}"
    clean_up 0
fi

# check if we are using the latest version
check_version
VERSION_STATUS=$?

if [[ -n $FORCE_UPDATE ]]; then
    curl $SCRIPT_URL -o $0
    UPDATE_STATUS=$?
    if [ $UPDATE_STATUS == 0 ]; then
        echo -e "\n$0 updated successfully\n"
    else
        echo -e "\nfailed to update $0"
        echo -e "Please download the script manually from this URL: ${SCRIPT_URL}\n"
    fi
    clean_up $UPDATE_STATUS
fi

if [[ -n $UPDATE ]]; then
    UPDATE_STATUS="skip"
    if [ $VERSION_STATUS -eq 1 ]; then
        curl $SCRIPT_URL -o $0
        UPDATE_STATUS=$?
    fi
    if [ $UPDATE_STATUS == "skip" ]; then
        EXIT_STATUS=0
        echo -e "\n$0 is already up to date\n"
    elif [ $UPDATE_STATUS -eq 0 ]; then
        EXIT_STATUS=0
        echo -e "\n$0 updated successfully\n"
    else
        EXIT_STATUS=$UPDATE_STATUS
        echo -e "\nfailed to update $0"
        echo -e "Please download the script manually from this URL: ${SCRIPT_URL}\n"
    fi
    clean_up $EXIT_STATUS
fi


if [[ -z $REDIS_TOKEN ]] || [[ -z $VAULT_TOKEN ]] || [[ -z $CERT_NAME ]] || [[ -z $TEAM_NAME ]] || [[ -z $TYPE ]]; then
    echo -e "\n--redis-token, --vault-token, --cert-name, --team-name and --type are mandatory\n"
    usage
fi

# [[ -z $TYPE ]] && TYPE="EV" # let's default to EV type
if [[ $TYPE != "EV" ]] && [[ $TYPE != "OV" ]]; then
    echo "type must be either EV, ev, OV, ov"
    usage
fi

type=$(echo $TYPE | tr '[:upper:]' '[:lower:]')
TYPE=$(echo $TYPE | tr '[:lower:]' '[:upper:]')
PROVIDER="sectigo_${type}"

if [[ -z $WILDCARD ]]; then
    MODIFIED_CERT_NAME=$CERT_NAME
else
    MODIFIED_CERT_NAME="wildcard_${CERT_NAME}"
fi
[[ -z $DAYS ]] && DAYS=30
[[ -z $CERT_DESTINATION ]] && CERT_DESTINATION="${CERT_BASE}/${MODIFIED_CERT_NAME}.crt"
[[ -z $FULLCHAIN_DESTINATION ]] && FULLCHAIN_DESTINATION="${CERT_BASE}/${MODIFIED_CERT_NAME}_fullchain.crt"
[[ -z $KEY_DESTINATION ]] && KEY_DESTINATION="${KEY_BASE}/${MODIFIED_CERT_NAME}.key"
[[ -z $CA_DESTINATION ]] && CA_DESTINATION="${CERT_BASE}/COMODO_${TYPE}.crt"

UNDERSCORED_CERT_NAME=$(echo $CERT_NAME | sed -e 's,\.,_,g')
MINUTES=$((${DAYS} * 86400))

# give up if the certificate expiration is still within a proper range
if openssl x509 -checkend $MINUTES -noout -in $FULLCHAIN_DESTINATION &>/dev/null; then
    echo "the certificate $FULLCHAIN_DESTINATION is still valid"
    rm -f $TMP_CERT $TMP_FULLCHAIN $TMP_CA $TMP_KEY
    exit 0
fi

# download certificates and delete the last empty line if it exists and remove the first line from Webdis
CERTNAME_PREFIX="${PROVIDER}_${UNDERSCORED_CERT_NAME}"
WILDCARD_CERTNAME_PREFIX="${PROVIDER}_wildcard_${UNDERSCORED_CERT_NAME}"

if [[ -z $WILDCARD ]]; then
    curl -s -u redis:$REDIS_TOKEN ${REDIS_URL}/${TEAM_NAME}:${CERT_NAME}:redis_${CERTNAME_PREFIX}_pem.txt >$TMP_CERT
    curl -s -u redis:$REDIS_TOKEN ${REDIS_URL}/${TEAM_NAME}:${CERT_NAME}:redis_${CERTNAME_PREFIX}_fullchain_pem.txt >$TMP_FULLCHAIN
    curl -s -u redis:$REDIS_TOKEN ${REDIS_URL}/${TEAM_NAME}:${CERT_NAME}:redis_${CERTNAME_PREFIX}_chain_pem.txt >$TMP_CA
    curl -s -H "X-Vault-Token: ${VAULT_TOKEN}" ${VAULT_URL}/${TEAM_NAME}/${CERT_NAME}/vault_${CERTNAME_PREFIX}_key | jq -j .data.value >$TMP_KEY
else
    if [[ "$TEAM_NAME" == "puppet" ]]; then  # TLD Wildcard
        curl -s -u redis:$REDIS_TOKEN ${REDIS_URL}/${TEAM_NAME}:common:redis_${WILDCARD_CERTNAME_PREFIX}_pem.txt >$TMP_CERT
        curl -s -u redis:$REDIS_TOKEN ${REDIS_URL}/${TEAM_NAME}:common:redis_${WILDCARD_CERTNAME_PREFIX}_fullchain_pem.txt >$TMP_FULLCHAIN
        curl -s -u redis:$REDIS_TOKEN ${REDIS_URL}/${TEAM_NAME}:common:redis_${WILDCARD_CERTNAME_PREFIX}_chain_pem.txt >$TMP_CA
        curl -s -H "X-Vault-Token: ${VAULT_TOKEN}" ${VAULT_URL}/${TEAM_NAME}/common/vault_${WILDCARD_CERTNAME_PREFIX}_key | jq -j .data.value >$TMP_KEY
    else
        curl -s -u redis:$REDIS_TOKEN ${REDIS_URL}/${TEAM_NAME}:${CERT_NAME}:redis_${WILDCARD_CERTNAME_PREFIX}_pem.txt >$TMP_CERT
        curl -s -u redis:$REDIS_TOKEN ${REDIS_URL}/${TEAM_NAME}:${CERT_NAME}:redis_${WILDCARD_CERTNAME_PREFIX}_fullchain_pem.txt >$TMP_FULLCHAIN
        curl -s -u redis:$REDIS_TOKEN ${REDIS_URL}/${TEAM_NAME}:${CERT_NAME}:redis_${WILDCARD_CERTNAME_PREFIX}_chain_pem.txt >$TMP_CA
        curl -s -H "X-Vault-Token: ${VAULT_TOKEN}" ${VAULT_URL}/${TEAM_NAME}/${CERT_NAME}/vault_${WILDCARD_CERTNAME_PREFIX}_key | jq -j .data.value >$TMP_KEY
    fi
fi

# Before installing any certificate we need to check the validity of
# Certificate, Full Chain, CA and Key that we downloaded

# checking if certificates are valid
if ! openssl x509 -checkend $MINUTES -noout -in $TMP_CERT &>/dev/null; then
    echo "the Certificate is malformed or is expiring. Giving up"
    clean_up 2
fi
if ! openssl x509 -checkend $MINUTES -noout -in $TMP_FULLCHAIN &>/dev/null; then
    echo "the Full Chain is malformed or is expiring. Giving up"
    clean_up 2
fi
if ! openssl x509 -in $TMP_CA -text -noout &>/dev/null; then
    echo "the CA is malformed. Giving up"
    clean_up 2
fi

# checking if key matches the certificate and the full-chain
KEY_MD5=$(openssl rsa -noout -modulus -in $TMP_KEY | openssl md5 | awk '{print $NF}')
FULLCHAIN_MD5=$(openssl x509 -noout -modulus -in $TMP_FULLCHAIN | openssl md5 | awk '{print $NF}')
CRT_MD5=$(openssl x509 -noout -modulus -in $TMP_CERT | openssl md5 | awk '{print $NF}')
if [[ $KEY_MD5 != $CRT_MD5 ]] || [[ $KEY_MD5 != $FULLCHAIN_MD5 ]]; then
    echo "the Key $TMP_KEY is either malformed or it does not match the certificate. Giving up"
    clean_up 2
fi

# checking if the certificate contains at least our cert_name
if ! openssl x509 -noout -text -in $TMP_CERT | grep -qw $CERT_NAME; then
    echo "the certificate does not match your CN $CERT_NAME"
    clean_up 2
fi
if ! openssl x509 -noout -text -in $TMP_FULLCHAIN | grep -qw $CERT_NAME; then
    echo "the full chain certificate does not match your CN $CERT_NAME"
    clean_up 2
fi

# let's install the certificates
install --owner=root --group=root --mode=0755 --directory $CERT_BASE
install --owner=root --group=$GROUPNAME --mode=0750 --directory $KEY_BASE
install --owner=root --group=root --mode=0644 -T ${TMP_CERT} ${CERT_DESTINATION}
install --owner=root --group=root --mode=0644 -T ${TMP_FULLCHAIN} ${FULLCHAIN_DESTINATION}
install --owner=root --group=root --mode=0644 -T ${TMP_CA} ${CA_DESTINATION}
install --owner=root --group=$GROUPNAME --mode=0640 -T ${TMP_KEY} ${KEY_DESTINATION}
rm -f $TMP_CERT $TMP_FULLCHAIN $TMP_CA $TMP_KEY

echo "installed: ${CERT_DESTINATION}"
echo "installed: ${FULLCHAIN_DESTINATION}"
echo "installed: ${CA_DESTINATION}"
echo "installed: ${KEY_DESTINATION}"

# exiting 64: if we are here we need to reload our service
exit 64