Skip to content
Snippets Groups Projects
Select Git revision
  • 7565c62b6982acd8b17c2f068afd713f8518d7d8
  • master default protected
  • simple-task/7248-eol-check-add-node-22
  • 6877_check_iml_deployment
4 results

check_smartstatus

Blame
  • cm.sh 13.59 KiB
    #!/usr/bin/env bash
    # ======================================================================
    #
    # WRAPPER FOR ACME.SH 
    # Let's Encrypt client
    #
    # requires
    # - bash
    # - openssl
    # - curl
    # - dig (opional)
    # - acme.sh client
    #
    # ----------------------------------------------------------------------
    # 2021-02-02  <axel.hahn@iml.unibe.ch>  first lines
    # 2021-02-10  <axel.hahn@iml.unibe.ch>  compare hashes, logging
    # 2021-02-12  <axel.hahn@iml.unibe.ch>  added self test
    # ======================================================================
    
    
    # ----------------------------------------------------------------------
    #
    # CONFIG
    #
    # ----------------------------------------------------------------------
    
    touchfile="./log/lastchange.txt"
    logfile="./log/certmanager.log"
    
    csrfile="./templates/csr.txt"
    
    line="_______________________________________________________________________________"
    
    showdebug=1
    writelog=1
    
    
    # ----------------------------------------------------------------------
    #
    # INTERNAL FUNCTIONS
    #
    # ----------------------------------------------------------------------
    
    # internal function; list certificates incl. creation date and renew date
    function _listCerts(){
    	$ACME --list	
    }
    
    # internal function; get a list of fqdn of all existing certs
    function _listCertdomains(){
    	_listCerts | sed -n '2,$p' | awk '{ print $1 }'
    }
    
    # internal function; checks if a certificate for a given FQDN already exists
    # used in _certMustExist, _certMustNotExist
    # param  string  FQDN
    function _certExists(){
    	_listCertdomains | grep "^${CM_fqdn}$" >/dev/null
    }
    
    # internal function; a certificate of a given FQDN must exist - otherwise
    # the script will be aborted
    # param  string  FQDN
    function _certMustExist(){
    	_certExists
    	if [ $? -ne 0 ]; then
    		echo "ERROR: cert ${CM_fqdn} was not added yet."
    		exit 1
    	fi
    }
    
    # internal function; a certificate of a given FQDN must not exist - otherwise
    # the script will be aborted
    # param  string  FQDN
    function _certMustNotExist(){
    	_certExists
    	if [ $? -eq 0 ]; then
    		echo "ERROR: cert ${CM_fqdn} was added already."
    		exit 1
    	fi
    }
    
    # internal function: transfer generated/ updated cert data to a
    # known directory (based on CM_diracme - see inc_config.sh)
    # used in public_add and public_renew
    # used in ADD and RENEW action
    function _certTransfer(){
    	_wd "--- acme internal data - ~/.acme.sh/${CM_fqdn}"
    	ls -l ~/.acme.sh/${CM_fqdn}
    
    	_wd "--- transfer acme.sh files to ${CM_dircerts}"
    	$ACME \
    		--install-cert \
    		-d ${CM_fqdn} \
    		--cert-file       ${CM_outfile_cert}  \
    		--fullchain-file  ${CM_outfile_chain} \
    		--ca-file         ${CM_outfile_ca} \
    		|| exit 1
    		# --key-file        ${CM_dircerts}/${CM_fqdn}.key.pem  \
    
    	_wd "--- copy key to ${CM_dircerts}"
    	cp ${CM_filekey} ${CM_outfile_key}
    
    	_wd "--- content of output dir $CM_dircerts:"
    	ls -l $CM_dircerts/*
    }
    
    # internal function; show md5 hashsums for certificate, csr and key
    # for visual comparison if the match
    function _certMatching(){
    	local md5_csr=$(  test -f ${CM_filecsr}      & openssl req  -noout -modulus -in ${CM_filecsr}      | openssl md5 | cut -f 2 -d " " )
    	local md5_key=$(  test -f ${CM_outfile_key}  & openssl rsa  -noout -modulus -in ${CM_outfile_key}  | openssl md5 | cut -f 2 -d " " )
    	local md5_cert=$( test -f ${CM_outfile_cert} & openssl x509 -noout -modulus -in ${CM_outfile_cert} | openssl md5 | cut -f 2 -d " " )
    
    	echo
    	echo "--- compare hashes"
    	echo "csr  : $md5_csr (used for creation of cert)"
    	echo "key  : $md5_key"
    	echo "cert : $md5_cert"
    	if [ "$md5_key" = "$md5_cert" ]; then
    		echo "OK, key and cert match :-)"
    	else
    		echo "ERROR: key and cert do NOT MATCH!"
    	fi
    	echo
    }
    
    # internal function: dig for given fqdn.
    # Function stops if fqdn was not found in DNS.
    # If dig is not found the function skips the DNS check.
    # This function is used in _gencsr
    # param  string  fqdn to check
    function _checkDig(){
        local myfqdn=$1
        which dig >/dev/null
        if [ $? -eq 0 ]; then
            _wd "CHECK: $myfqdn exists in DNS (using dig) ..."
            dig $myfqdn | grep -v '^;' | grep $myfqdn 
            if [ $? -ne 0 ]; then
                echo "ERROR: not found. Was there a typo in the hostname??"
                exit 2
            fi
            _wd "OK"
        else
            _wd "SKIP: dig was not found"
        fi
        echo
    
    }
    
    # internal function; generate a csr file before creating a new certifcate
    # this function is used in public_add
    function _gencsr(){
    
    	altdns=
    	_checkDig $CM_fqdn
    	for myalt in $*
    	do
    		altdns="${altdns}DNS:$myalt,"
    	done
    	altdns=$( echo $altdns | sed "s#,\$##" )
        _wd "--- $CM_fqdn"
    	_wd "DNS alternative names: $altdns"
    
    	rm -f $CM_filecnf $CM_filekey $CM_filecsr
    	mkdir -p "${CM_dircsr}" 2>/dev/null
    
    	cat $csrfile \
    		| sed "s#__FQDN__#$CM_fqdn#g"  		\
    		| sed "s#__ALTNAMES__#$altdns#g"	\
    		> $CM_filecnf || exit 1
    
    	# generate csr
    	_wd "creating key and csr"
    	openssl req -new -config $CM_filecnf -keyout $CM_filekey -out $CM_filecsr || exit 1
    
    	# view csr
    	# openssl req -noout -text -in $CM_filecsr
    	ls -ltr $CM_filecnf $CM_filekey $CM_filecsr
    }
    
    # internal function; check if a required 2nd CLI parameter was given
    # if not the script will abort
    function _requiresFqdn(){
    	if [ -z "$CM_fqdn" ]; then
    		echo "ERROR: 2nd parameter must be a FQDN for Main_Domain."
    		exit 1
    	fi
    }
    
    # internal function; it shows a message if the current instance uses a stage
    # server. It shows a message that it is allowed to test arround ... or to be 
    # careful with LE requests on a production system
    function _testStaging(){
    	echo $ACME_Params | grep "\-\-staging" >/dev/null
    	if [ $? -eq 0 ]; then
    		_wd "Using Let's Encrypt STAGE environment ..."
    		_wd "You can test and mess around. Do not use certs in production."
    	else
    		_wd "Using Let's Encrypt LIVE environment for production."
    		_wd "Be careful with count of connects to Let's Encrypt servers."
    	fi
    	echo
    }
    
    # set update message in a file
    # param  string(s)  message
    function _update(){
    	echo "[$( date )] $*" > ${touchfile}
    	test ${writelog} && echo "[$( date )] $*" >> ${logfile}
    }
    
    # write debug output if showdebug is set to 1
    function _wd(){
    	test ${showdebug} && echo "DEBUG: $*"
    }
    
    # set environment for a single certificate based on FQDN
    # param  string  FQDN
    function _setenv(){
    	CM_fqdn=$1
    	CM_filecsr="${CM_dircsr}/${CM_fqdn}.csr"
    	CM_filecnf="${CM_dircsr}/${CM_fqdn}.cnf"
    	CM_filekey="${CM_dircsr}/${CM_fqdn}.key"
    
    	CM_dircerts="${CM_diracme}/${CM_fqdn}"
    	CM_outfile_cert=${CM_dircerts}/${CM_fqdn}.cert.cer
    	CM_outfile_chain=${CM_dircerts}/${CM_fqdn}.fullchain.cer
    	CM_outfile_key=${CM_dircerts}/${CM_fqdn}.key.pem
    	CM_outfile_ca=${CM_dircerts}/${CM_fqdn}.ca.cer
    
    	# echo $CM_fqdn; set | grep "^CM_"; echo
    
    }
    # ----------------------------------------------------------------------
    #
    # PUBLIC FUNCTIONS
    #
    # ----------------------------------------------------------------------
    
    #
    # pulic function ADD certificate
    # 
    function public_add(){
    	_requiresFqdn
    	_certMustNotExist
    	_gencsr $CM_fqdn $*
    
    	_wd "--- create output dir $dircerts"
    	mkdir -p "${CM_dircerts}" 2>/dev/null
    
    	_wd "--- csr data"
    	$ACME --showcsr  --csr $CM_filecsr || exit 1
    
    	_wd "--- create certificate"
    	$ACME --signcsr --csr $CM_filecsr $ACME_Params 
    	if [ $? -ne 0 ]; then
    		echo "ERROR: adding cert failed. Trying to delete internal data ..."
    		public_delete $CM_fqdn
    		exit 1
    	fi
    	# $ACME --issue -d $CM_fqdn $ACME_Params || exit 1
    
    	_certTransfer
    	_certMatching
    
    	_update "added $CM_fqdn $*"
    }
    
    #
    # pulic function ADD OR RENEW certificate
    # 
    function public_add-or-renew(){
    	_requiresFqdn
    	_certExists
    	if [ $? -eq 0 ]; then
    		_wd "--- cert was found ... renew it (ignore --force - it comes from acme.sh)"
    		public_renew $*
    	else
    		_wd "--- cert does mot exist ... add it"
    		public_add $*
    	fi
    }
    
    #
    # public function to delete a cert
    #
    function public_delete(){
    	_requiresFqdn
    	_certMustExist
    
    	# TODO: revoke it too??
    	# $ACME --revoke -d ${CM_fqdn}
    
    	_wd "--- delete ACME.SH data"
    	$ACME --remove -d ${CM_fqdn} $ACME_Params
    	_wd "--- delete local data"
    	rm -rf ${CM_dircerts} ${CM_filecnf} ${CM_filekey} ${CM_filecsr} ~/.acme.sh/${CM_fqdn}
    	_update "deleted ${CM_fqdn}"
    }
    
    
    #
    # public function; list certificates incl. creation date and renew date
    # 
    function public_list(){
    	_listCerts
    }
    
    #
    # public function - renew a certificate
    # param  string  fqdn of domain to renew
    function public_renew(){
    	_requiresFqdn
    	_certMustExist
    	$ACME --renew -d ${CM_fqdn} $ACME_Params
    	local _rc=$?
    
    	case $_rc in
    		0)
    			_certTransfer
    			_certMatching
    			_update "renewed ${CM_fqdn}"
    			;;
    		2)
    			_wd "renew was skipped ... we need to wait a while."
    			;;
    		*)
    			_wd "Error ocured."
    			exit $_rc
    	esac
    }
    
    #
    # public function - renew all certificates (to be used in a cronjob)
    # no params
    function public_renew-all(){
    
    	_listCertdomains | while read mydomain
    	do
    		_wd "--- renew $mydomain"
    		_setenv ${mydomain}
    		public_renew
    	done
    
    }
    
    
    # internal function; helper for selftest to handle a single selftest
    # if a given command is successful it shows "OK" or "ERROR" followed
    # by the label inparam 2.
    # The value _iErrors will be incremented by 1 if an error occured.
    # param  string  command to verify
    # param  string  output label
    function _selftestItem(){
    	local _check=$1
    	local _label=$2
    	local _status="OK:"
    
    	eval "$_check"
    	if [ $? -ne 0 ]; then
    		_status="ERROR: the check failed for the test of -"
    		_iErrors=$_iErrors+1
    	fi
    
    	echo "$_status $_label"
    }
    
    #
    # list existing certs
    # no params
    function public_selftest(){
    
    	typeset -i _iErrors=0
    
    	echo
    	echo --- dependencies
    	_selftestItem "which openssl" "opemssl was found"
    	_selftestItem "which curl" "curl was found"
    	echo
    
    	echo --- acme.sh client
    	_selftestItem "ls -ld ${ACME}" "${ACME} exits"
    	_selftestItem "test -x ${ACME}" "${ACME} is executable"
    	echo
    
    	echo --- acme.sh installation \(may fail in future releases of acme.sh\)
    	_selftestItem "ls -ld ~/.acme.sh" "internal acme data were found = [acme.sh --install] was done"
    	_selftestItem "test -w ~/.acme.sh/" "it is writable"
    	echo
    
    	echo --- csr template
    	_selftestItem "ls -ld ${csrfile}"  "csr base template exists"
    	_selftestItem "test -r ${csrfile}" "it is readable"
    	echo
    
    	echo --- output directory for csr and key
    	_selftestItem "ls -ld ${CM_dircsr}"  "data dir for csr exists"
    	_selftestItem "test -w ${CM_dircsr}" "it is writable"
    	echo
    
    	echo --- output dir for centralized place of certificates
    	_selftestItem "ls -ld ${CM_diracme}"  "central output dir for certificate data exists"
    	_selftestItem "test -w ${CM_diracme}" "it is writable"
    	echo
    
    
    	echo --- logs
    	_selftestItem "ls -ld ./log/" "Logdir exists"
    	_selftestItem "test -w" "Logdir is writable"
    	test -f $logfile    &&  _selftestItem  "test -w $logfile" "Logfile $logfile is writable"
    	test -f $touchfile  && _selftestItem "test -w $touchfile" "Logfile $touchfile is writable"
    	echo
    
    	echo --- Errors: $_iErrors
    	test $_iErrors -eq 0 && echo "OK, this looks fine."
    	echo
    	exit $_iErrors
    }
    
    #
    # list existing certs
    # no params
    function public_show(){
    	_requiresFqdn
    	_certMustExist
    
    	ls -l ${CM_filecsr} ${CM_dircerts}/*
    	echo $line
    	echo CSR $CM_filecsr
    	openssl req -noout -text -in $CM_filecsr | grep -E "(Subject:|DNS:)"
    
    	echo $line
    	echo Cert ${CM_outfile_cert}
    	# openssl x509 -noout -text -in ${CM_outfile_cert}
    	openssl x509 -noout -text -in ${CM_outfile_cert} | grep -E "(Issuer:|Subject:|DNS:)"
    	_certMatching
    }
    
    
    # ----------------------------------------------------------------------
    #
    # main
    #
    # ----------------------------------------------------------------------
    
    cd $( dirname $0 )
    
    cat <<ENDOFHEADER
    $line
    
    
                 	- - - ---===>>> CERT MANAGER <<<===--- - - -
    
    $line
    
    ENDOFHEADER
    
    which openssl >/dev/null || exit 1
    
    . ./inc_config.sh
    if [ $? -ne 0 ]; then
    	echo "ERROR: loading the config failed."
    	echo "Copy the inc_config.sh.dist to inc_config.sh and make your settings in it."
    	echo
    	exit 1
    fi
    
    _testStaging
    
    test -z "${CM_diracme}" && CM_diracme=./certs
    test -z "${CM_dircsr}"  && CM_dircsr=./csr
    
    grep "function\ public_$1" $( basename $0 ) >/dev/null 
    if [ $# -gt 0 -a $? -eq 0 ]; then
    	# _wd $*
    	action=$1
    	CM_fqdn=$2
    	shift 2
    
    	test -z "${ACME}" && ACME=$( which acme.sh )
    	if [ ! -x "${ACME}" ]; then
    		echo "ERROR: acme.sh not found. You need to install acme.sh client and configure it in inc_config.sh."
    		exit 1
    	fi
    
    	_setenv $CM_fqdn
    
    	_wd "A C T I O N -->> $action <<--"
    	eval "public_$action $*"
    else
    	self=$( basename $0 )
    	cat <<EOF
    
    HELP
    
    The basic syntax is
    $self ACTION [FQDN] [ALIAS_1 [.. ALIAS_N]]
    
    The ACTIONs for SINGLE certificate handlings are:
    
            add FQDN [.. FQDN-N] 
                    create new certificate
                    The first FQDN is a hostname to generate the certificate for. 
                    Following multiple hostnames will be used as DNS aliases in the 
                    same certificate.
                    It updates files in ${CM_diracme}
    
            add-or-renew FQDN [.. FQDN-N] 
                    This param is for automation tools like Ansible or Puppet.
                    It checks if the certificate for first (*) FQDN exists.
                    If not: add a new cert (see "add").
                    If so: call renew action (see "renew")
    
                    (*) it doesn't verify the DNS aliases
    
            delete FQDN
                    delete all files of a given certificate
    
            renew FQDN
                    renew (an already added) certificate
                    and update files in ${CM_diracme}
    
            show FQDN
                    show place of csr + certificate data and show certificate
    
    ACTIONs for ALL certs
    
            list
                    list all certificates including creation and renew date
    
            renew-all
                    renew all certificates (fast mode - without --force)
                    and update files in ${CM_diracme}
                    It is useful for a cronjob.
    
    other ACTIONs
    
            selftest
                    check of health with current setup and requirements.
                    This command is helpful for initial setups.
    
    EOF
    fi
    
    echo
    _testStaging