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

check_proc_zombie

Blame
  • cm.sh 15.46 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
    # 2021-02-17  <axel.hahn@iml.unibe.ch>  ensure checks list of aliases; new: optional host filter before adding a cert
    # 2021-03-12  <axel.hahn@iml.unibe.ch>  create file for haproxy
    # 2021-03-22  <axel.hahn@iml.unibe.ch>  for haproxy: use chained cert instead of server cert
    # ======================================================================
    
    
    # ----------------------------------------------------------------------
    #
    # 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 "--- create chained file for haproxy"
    	cat ${CM_outfile_chain} ${CM_outfile_key} > ${CM_outfile_haproxy}
    
    	_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; get a sorted list of DNS aliases in the current cert
    function _getAliases(){
    	_sortWords $(
    		openssl x509 -noout -text -in ${CM_outfile_cert} \
    			| grep -E "(DNS:)" \
    			| sed "s#^\ *##g"  \
    			| sed "s#DNS:##g"  \
    			| sed "s#,##g"
    	)
    }
    
    # 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_haproxy=${CM_dircerts}/${CM_fqdn}.haproxy.pem
    	CM_outfile_ca=${CM_dircerts}/${CM_fqdn}.ca.cer
    
    	# echo $CM_fqdn; set | grep "^CM_"; echo
    
    }
    # internal function; helper: sort words in alphabetic order
    function _sortWords(){
    	echo $* | tr " " "\n" | sort | tr "\n" " "
    }
    
    
    # ----------------------------------------------------------------------
    #
    # PUBLIC FUNCTIONS
    #
    # ----------------------------------------------------------------------
    
    #
    # pulic function ADD certificate
    # 
    function public_add(){
    	_requiresFqdn
    	_certMustNotExist
    
    	for myhost in $( echo $CM_fqdn $*)
    	do 
    		echo $myhost | grep "$CM_certmatch" >/dev/null
    		if [ $? -ne 0 ]; then
    			echo "ERROR: host $myhost does not match [$CM_certmatch]."
    			exit 1
    		fi
    	done
    	_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_ensure(){
    	_requiresFqdn
    	_certExists
    	if [ $? -eq 0 ]; then
    		_wd "--- cert $CM_fqdn was found ... compare aliases"
    		local _newAliases=$( _sortWords $CM_fqdn $* )
    		local _certAliases=$( _getAliases )
    
    		_wd "from params: $_newAliases"
    		_wd "inside cert: $_certAliases"
    		if [ "$_newAliases" = "$_certAliases" ]; then
    			_wd "--- DNS aliases match ... renew it (ignore --force - it comes from acme.sh)"
    			public_renew $*
    		else
    			_wd "--- DNS aliases do NOT match ... deleting cert and create a new one"
    			public_delete $*
    			public_add $*
    		fi
    	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??
    	_wd "--- revoke cert"
    	$ACME --revoke -d ${CM_fqdn} $ACME_Params
    	_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} && echo OK
    	_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}/*
    	_certMatching
    	echo $line
    	echo CSR $CM_filecsr
    	openssl req -noout -text -in $CM_filecsr | grep -E "(Subject:|DNS:)" | sed "s#^\ *##g"
    
    	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:|Not\ |DNS:)"| sed "s#^\ *##g"
    }
    
    
    # ----------------------------------------------------------------------
    #
    # 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}
    
            ensure FQDN [.. FQDN-N] 
                    It ensures that a certificate with given aliases exists and is up to date.
                    This param is for simple usage in automation tools like Ansible or Puppet.
                    It is required to add all aliases as parameters what is unhandy for
                    direct usage on cli.
    
                    If the cert does not exist it will be created (see "add").
                    If fqdn and aliases are the same like in the certificate it performs a renew.
                    If fqdn and aliases differ:
                    - the current certificate will be rejected + deleted (see "delete")
                    - a new certificate will be added ()
    
            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 basic certificate data
                    (issuer, subject, aliases, ending date)
    
    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