From a808c674e4cf7f5e572b711f73f07102444152db Mon Sep 17 00:00:00 2001 From: "Hahn Axel (hahn)" <axel.hahn@unibe.ch> Date: Wed, 8 Jan 2025 13:52:01 +0100 Subject: [PATCH] optimize _wait_for_free_slot --- cm.sh | 9 +- cm.sh__bak | 963 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 970 insertions(+), 2 deletions(-) create mode 100755 cm.sh__bak diff --git a/cm.sh b/cm.sh index 074b6a7..d1d4597 100755 --- a/cm.sh +++ b/cm.sh @@ -354,6 +354,10 @@ function _update(){ # "neverending" loop that waits until the current process is # the one with lowest PID function _wait_for_free_slot(){ + if [ "$FREESLOT" = "1" ]; then + _debuglog "skip _wait_for_free_slot" + return 0 + fi local _bWait=true _debuglog "start in _wait_for_free_slot" typeset -i local _iFirstPID=0 @@ -363,10 +367,10 @@ function _wait_for_free_slot(){ _wd "--- Need to wait until own process PID $$ is on top ... " while [ $_bWait = true ]; do - _sProcesses=$( ps -ef | grep "bash.*$0" | grep -v "ssh.*@" | grep -v "grep" | sort -k 2 -n ) + _sProcesses=$( ps aux | grep "bash.*$0" | grep -v "ssh.*@" | grep -v "grep" | sort -k 2 -n ) _iPos=$( echo "$_sProcesses" | grep -n " $$ " | head -1 | cut -f 1 -d ':' ) - _wd "instances: $_iProcesses" + _wd "process $$ is on position $_iPos" test ${CM_showdebug} -ne 0 && echo "$_sProcesses" # if [ $_iFirstPID -eq $$ ]; then @@ -381,6 +385,7 @@ function _wait_for_free_slot(){ sleep $((3 + RANDOM % 3)); fi done + FREESLOT=1 _debuglog "end _wait_for_free_slot" } diff --git a/cm.sh__bak b/cm.sh__bak new file mode 100755 index 0000000..f44368a --- /dev/null +++ b/cm.sh__bak @@ -0,0 +1,963 @@ +#!/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 +# 2021-04-12 <axel.hahn@iml.unibe.ch> reject multiple usage of fqdn in cli params +# 2021-04-12 <axel.hahn@iml.unibe.ch> optional: force excecution with a given user only +# 2021-06-24 <axel.hahn@iml.unibe.ch> added transfer command; delete files if acme.sh --install-cert ... failes +# 2021-07-14 <axel.hahn@iml.unibe.ch> added _wait_for_free_slot in cert actions to execute multiple processes sequentially +# 2021-09-27 <axel.hahn@iml.unibe.ch> softer behaviour: do not revoke changed certs (add does not stop; ensure does not delete) +# 2021-12-23 <axel.hahn@iml.unibe.ch> added param --trace as 1st param to generate a trace log +# 2022-01-10 <axel.hahn@iml.unibe.ch> _wait_for_free_slot: exclude ssh calls +# 2022-03-30 <axel.hahn@iml.unibe.ch> remove usage of csr and generation of key file +# 2022-03-31 <axel.hahn@iml.unibe.ch> dns authentication with alias domain +# 2022-04-04 <axel.hahn@iml.unibe.ch> Bugfix: copy key from csr folder to ~/.acme/ +# 2022-04-04 <axel.hahn@iml.unibe.ch> added param "list-old" +# 2022-04-07 <axel.hahn@iml.unibe.ch> fix missing key in public_ensure before calling public_add too. +# 2022-04-20 <axel.hahn@iml.unibe.ch> fix multiple domains using domain alias +# 2022-04-21 <axel.hahn@iml.unibe.ch> mix multiple domains using domain alias or not +# 2022-05-19 <axel.hahn@iml.unibe.ch> add timer and debug.log +# 2022-05-20 <axel.hahn@iml.unibe.ch> update _wait_4_free_slot and data in debug.log +# 2023-02-01 <axel.hahn@unibe.ch> skip function _fixKeyfile with former workaround +# 2023-05-08 <axel.hahn@unibe.ch> fix: "key and cert do not match" +# 2024-03-21 <axel.hahn@unibe.ch> shorter sleep times +# 2024-07-09 <axel.hahn@unibe.ch> remove grep: warning: stray \ before white space; add --force for renewal +# ====================================================================== + + +# ---------------------------------------------------------------------- +# +# CONFIG +# +# ---------------------------------------------------------------------- + +_version="2024-07-09" +logdir="./log" +touchfile="$logdir/lastchange.txt" +logfile="$logdir/certmanager.log" +debuglogfile="$logdir/debug.log" + +# CSR USAGE WAS REMOVED +# csrfile="./templates/csr.txt" + +line="_______________________________________________________________________________" + +# flag: show debug infos on console (STDOUT) +CM_showdebug=0 + +# flag: write a log for created/ renewd/ deleted certs +CM_writelog=1 + +# flag: write a log for executed functions with timer and process count +CM_writedebuglog=0 + +CM_timer_start=$( date +%s.%N ) + +# ---------------------------------------------------------------------- +# +# INTERNAL FUNCTIONS +# +# ---------------------------------------------------------------------- + +# BUGFIX: acme.sh does not create a new key file on renew. +# After switching from csr method to param -d we got a 0 byte Keyfile +function _fixKeyfile(){ + echo "SKIP: _fixKeyfile won't be executed anymore." + # local _acme_keyfile=~/.acme.sh/${CM_fqdn}/${CM_fqdn}.key + # if test ! -f "$_acme_keyfile" + # then + # echo "FIX: copy key from csr folder $CM_filekey to $_acme_keyfile" + # if ! cp "$CM_filekey" "$_acme_keyfile" + # then + # exit 1 + # fi + # fi +} + +# 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 + $ACME --info -d "${CM_fqdn}" 2>/dev/null | grep "letsencrypt.org" >/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(){ + if _certExists + then + echo "ERROR: cert ${CM_fqdn} was added already." + # exit 1 + echo "Press Ctrl+C to abort within the next 3 sec..." + sleep 3 + 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 "--- delete current files in ${CM_dircerts}/ if they already exist." + test -d ${CM_dircerts} && rm -f "${CM_dircerts}/*" 2>/dev/null + + _wd "--- transfer acme.sh files to ${CM_dircerts}/" + if ! $ACME \ + --install-cert \ + -d "${CM_fqdn}" \ + --key-file "${CM_outfile_key}" \ + --cert-file "${CM_outfile_cert}" \ + --fullchain-file "${CM_outfile_chain}" \ + --ca-file "${CM_outfile_ca}" + then + echo "ERROR occured during acme transfer. Removing files in ${CM_dircerts} to prevent strange effects..." + rm -f "${CM_dircerts}/*" + exit 2 + fi + echo "OK." + + # _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:" + if ! ls -l "${CM_outfile_cert}" "${CM_outfile_chain}" "${CM_outfile_key}" "${CM_outfile_haproxy}" + then + echo "ERROR missing a file (or no access?)" + rm -f "${CM_dircerts}/*" + exit 2 + fi +} + +# internal function; show md5 hashsums for certificate and key +# for visual comparison if the match +function _certMatching(){ + # CSR USAGE WAS REMOVED + # 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" + # CSR USAGE WAS REMOVED + # 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 _dnsCheck +# param string fqdn to check +# param string type of dns entry; one of a|cname +# param string optional filter on output of dig (regex) +function _checkDig(){ + local myfqdn=$1 + local _type=${2:-"a"} + local _verify=${3:-"."} + + if which dig >/dev/null + then + # _wd "[$myfqdn] exists as type [$_type] in DNS?" + if ! dig "${myfqdn}" "${_type}" | grep "^${myfqdn}" | grep -E "${_verify}" + then + echo "ERROR: [$myfqdn] was not found. Maybe there is a typo in the hostname or it does not exist in DNS." + exit 2 + fi + _wd "OK: [$myfqdn] exists in DNS." + else + _wd "SKIP: dig was not found" + fi + echo + +} + +# internal function: check DNS entries +# - the hostname to be added in the certificate must exist +# - if a hostname does not match and CM_challenge_alias was set: +# - _acme-challenge.FQDN must be a cname to _acme-challenge.${CM_challenge_alias} +# Function stops if a fqdn was not found in DNS. +# param string fqdn(s) that are part of the certificate +function _dnsCheck(){ + local altdns= + local _mydomain= + local _subdomain='_acme-challenge' + + for _mydomain in $* + do + _wd "dig check - domain for cert" + _checkDig "$_mydomain" "a" "IN.*(A|CNAME)" # matches A and CNAME records + + if [ -n "${CM_challenge_alias}" ] && ! echo "$_mydomain" | grep "${CM_certmatch}" >/dev/null + then + _wd "dig check - cname ${_subdomain}.${_mydomain} must exist" + _checkDig "${_subdomain}.${_mydomain}" "cname" + _wd "dig check - cname ${_subdomain}.${_mydomain} must point to ${_subdomain}.${CM_challenge_alias}" + _checkDig "${_subdomain}.${_mydomain}" "cname" "${_subdomain}.${CM_challenge_alias}" + fi + done + +} + +# CSR USAGE WAS REMOVED +# internal function; generate a csr file before creating a new certifcate +# this function is used in public_add +# function _UNUSED_gencsr(){ + +# local altdns= + +# 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 +} +# internal function; if a user was set as CM_user then verify it with +# current user +function _testUser(){ + if [ ! -z "$CM_user" ]; then + local _sUser=`id | cut -f 2 -d "(" | cut -f 1 -d ")"` + if [[ $_sUser != "$CM_user" ]]; then + echo "ERROR: Run this script under user [$CM_user] - not as $_sUser." + exit 1 + fi + fi + +} + +# set update message into access log file +# global bool CM_writedebuglog flag to write access log. +# param string(s) message +function _debuglog(){ + if [ ${CM_writedebuglog} -eq 1 ]; then + echo "$( date ) $CM_fqdn [$$] | $(show_timer) | $*" >> ${debuglogfile} + fi +} + + +# set update message in a file +# param string(s) message +function _update(){ + echo "[$( date )] $*" > ${touchfile} + test ${CM_writelog} -ne 0 && echo "[$( date )] $*" >> ${logfile} +} + +# "neverending" loop that waits until the current process is +# the one with lowest PID +function _wait_for_free_slot(){ + local _bWait=true + _debuglog "start in _wait_for_free_slot" + typeset -i local _iFirstPID=0 + typeset -i local _iPos=0 + local _sProcesses + + _wd "--- Need to wait until own process PID $$ is on top ... " + while [ $_bWait = true ]; + do + _sProcesses=$( ps -ef | grep "bash.*$0" | grep -v "ssh.*@" | grep -v "grep" | sort -k 2 -n ) + _iPos=$( echo "$_sProcesses" | grep -n " $$ " | head -1 | cut -f 1 -d ':' ) + + _wd "instances: $_iProcesses" + test ${CM_showdebug} -ne 0 && echo "$_sProcesses" + + # if [ $_iFirstPID -eq $$ ]; then + if [ $_iPos -eq 1 ]; then + _bWait=false + _debuglog "GO from _wait_for_free_slot" + _wd "OK. Go!" + else + _iProcesses=$( echo "$_sProcesses" | wc -l ) + _iFirstPID=$( echo "$_sProcesses" | head -1 | awk '{ print $2}' ) + _debuglog "zzz ... waiting in _wait_for_free_slot ... $_iFirstPID is first ... my pos is $_iPos of $_iProcesses" + sleep $((3 + RANDOM % 3)); + fi + done + _debuglog "end _wait_for_free_slot" +} + +# write debug output if CM_showdebug is set to 1 +function _wd(){ + test ${CM_showdebug} -ne 0 && echo "DEBUG: $*" +} + +# set environment for a single certificate based on FQDN +# param string FQDN +function _setenv(){ + CM_fqdn=$1 + # CSR USAGE WAS REMOVED + # keeping vars to delete files of existing certs that used a csr + 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" " " +} + +# internal function; verify fqdn in cli params - each fqdn is allowed only once. +# on error it shows the count of usage of each fqdn +function _testFqdncount(){ + typeset -i local iHostsInParam=$( echo $* | wc -w ) + typeset -i iHostsUniq=$( echo $* | tr " " "\n" | sort -u | wc -w ) + if [ $iHostsInParam -ne $iHostsUniq ]; then + echo "ERROR: each given FQDN is allowed only once. You need to remove double entries." + + for myhost in $( echo $* | tr " " "\n" | sort -u ) + do + typeset -i iHostcount=$( echo $* | tr " " "\n" | grep "^$myhost$" | wc -l ) + test $iHostcount -gt 1 && echo " $iHostcount x $myhost" + done + echo + exit 1 + fi +} + +# get time in sec and milliseconds since start +# no parameter is required +function show_timer(){ + local timer_end=$( date +%s.%N ) + local totaltime=$( awk "BEGIN {print $timer_end - $CM_timer_start }" ) + + local sec_time=$( echo $totaltime | cut -f 1 -d "." ) + test -z "$sec_time" && sec_time=0 + + local ms_time=$( echo $totaltime | cut -f 2 -d "." | cut -c 1-3 ) + + echo "$sec_time.$ms_time sec" +} + +# ---------------------------------------------------------------------- +# +# PUBLIC FUNCTIONS +# +# ---------------------------------------------------------------------- + +# +# pulic function ADD certificate +# +function public_add(){ + local _params="" + + _debuglog "start public_add" + _wait_for_free_slot + _requiresFqdn + _certMustNotExist + + _dnsCheck $CM_fqdn $* + + for _mydomain in $CM_fqdn $* + do + _params+="-d $_mydomain --challenge-alias " + + if [ -n "${CM_challenge_alias}" ] && ! echo "$_mydomain" | grep "${CM_certmatch}" >/dev/null + then + _params+="${CM_challenge_alias} " + else + _params+="no " + fi + done + + # 2023-05-08: Specifies the domain key length + _params+="--keylength 2048 " + + _wd "--- create output dir $dircerts" + mkdir -p "${CM_dircerts}" 2>/dev/null + + _wd "--- create certificate" + echo "$ACME --issue $_params $ACME_Params" + if ! $ACME --issue $_params $ACME_Params + 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 $*" + _debuglog "end public_add" +} + +# CSR USAGE WAS REMOVED +# function OLD__public_add(){ +# _wait_for_free_slot +# _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" +# echo $ACME --signcsr --csr $CM_filecsr $ACME_Params +# $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(){ + _wait_for_free_slot + _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 $* + _wd "--- DNS aliases do NOT match ... creating a new one" + _fixKeyfile + public_add $* + fi + else + _wd "--- cert does mot exist ... add it" + public_add $* + fi +} + +# +# public function to delete a cert +# +function public_delete(){ + _debuglog "start public_delete" + _wait_for_free_slot + _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" + + # CSR USAGE WAS REMOVED + rm -rf ${CM_dircerts} ${CM_filecnf} ${CM_filekey} ${CM_filecsr} ~/.acme.sh/${CM_fqdn} && echo OK + _update "deleted ${CM_fqdn}" + _debuglog "end public_delete" +} + + +# +# public function; list certificates incl. creation date and renew date +# +function public_list(){ + _listCerts + +} +# +# public function; list old / outdated certificates +# +function public_list-old(){ + + local _iRuntime=90 + local _iWarn=65 + typeset -i local _rc=0 + + cd "${CM_dircerts}" || exit 1 + + echo + echo "Cert dir is $( pwd )" + echo + echo "---- Certificates expiring soon - with age $_iWarn ... $_iRuntime days:" + if find -type f -name "*.cert.cer" -mtime +$_iWarn -mtime -$_iRuntime | grep . >/dev/null + then + find -type f -name "*.cert.cer" -mtime +$_iWarn -mtime -$_iRuntime -exec ls -ld {} \; | nl + _rc=1 + else + echo " NONE." + fi + echo + echo "---- Certificate list ... older $_iRuntime days:" + if find -type f -name "*.cert.cer" -mtime +$_iRuntime | grep . >/dev/null + then + find -type f -name "*.cert.cer" -mtime +$_iRuntime -exec ls -ld {} \; | nl + _rc=2 + else + echo " NONE." + fi + + echo + cd - >/dev/null + + _wd "Exiting with rc=$_rc" + exit $_rc +} + + +# +# public function - renew a certificate +# param string fqdn of domain to renew +function public_renew(){ + _debuglog "start public_renew" + _wait_for_free_slot + _requiresFqdn + _certMustExist + + _fixKeyfile + + $ACME $CM_force --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 + _debuglog "end public_renew" +} + +# +# 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" "openssl 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 + + # CSR USAGE WAS REMOVED + # 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 + + # CSR USAGE WAS REMOVED + # ls -l ${CM_filecsr} ${CM_dircerts}/* + ls -l ${CM_dircerts}/* + _certMatching + + # CSR USAGE WAS REMOVED + # echo $line + # echo CSR $CM_filecsr + # openssl req -noout -text -in $CM_filecsr | grep -E "(Subject:|DNS:)" | sed "s#^\ *##g" + + for myfile in ${CM_outfile_cert} ${CM_outfile_haproxy} + do + echo $line + echo Cert ${myfile} + # openssl x509 -noout -text -in ${CM_outfile_cert} + openssl x509 -noout -text -in ${myfile} | grep -E "(Issuer:|Subject:|Not\ |DNS:)"| sed "s#^\ *##g" + done +} + +# Transfer cert from acme.sh internal cache to our output dir again +function public_transfer(){ + _wait_for_free_slot + _requiresFqdn + _certExists + + _certTransfer +} + +# ---------------------------------------------------------------------- +# +# main +# +# ---------------------------------------------------------------------- + +cd $( dirname $0 ) + +CM_force= +while [[ "$#" -gt 0 ]]; do case $1 in + --trace) + tracelog="$logdir/trace__$2__$3__`date +%Y-%m-%d__%H-%M-%S`.log" + exec > >(tee -a ${tracelog} ) + exec 2> >(tee -a ${tracelog} >&2) + echo "TRACELOG was triggered." + echo "TIME : $(date)" + echo "COMMAND: $0 $*" + echo "LOG : $tracelog" + + # set -vx + shift 1 + ;; + + --force) + echo "INFO: enable --force (for renewal)" + CM_force="--force" + shift 1 + ;; + *) if grep "^-" <<< "$1" >/dev/null ; then + echo; echo "ERROR: Unknown parameter: $1"; echo; _showHelp; exit 2 + fi + break; + ;; +esac; done +if [ "$1" = "--trace" ]; then + tracelog="$logdir/trace__$2__$3__`date +%Y-%m-%d__%H-%M-%S`.log" + exec > >(tee -a ${tracelog} ) + exec 2> >(tee -a ${tracelog} >&2) + echo "TRACELOG was triggered." + echo "TIME : $(date)" + echo "COMMAND: $0 $*" + echo "LOG : $tracelog" + + # set -vx + shift 1 +fi + +cat <<ENDOFHEADER +$line + + + - - - ---===>>> CERT MANAGER - v$_version <<<===--- - - - + +$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 + +_testUser +_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 + + _testFqdncount $CM_fqdn $* + + 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 <<--" + _debuglog ">>> START public_$action $CM_fqdn $*" + eval "public_$action $*" + _debuglog ">>> DONE public_$action $CM_fqdn $*" + +else + self=$( basename $0 ) + cat <<EOF + +HELP + + The basic syntax is + $self [OPTIONS] ACTION [FQDN] [ALIAS_1 [.. ALIAS_N]] + +OPTIONS + --trace + the output additionally will be written into a tracelog file + below $logdir. + --force + Use "--force" prameter for renewal of certificates + +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 certificate data and show basic certificate data + (issuer, subject, aliases, ending date) + + transfer FQDN + Transfer cert from acme.sh internal cache to our output dir again. + It is done during add or renew. With transfer command you can repeat it. + +ACTIONs for ALL certs + + list + list all certificates including creation and renew date + + list-old + list all certificates older 65 and older 90 days and exit. + Exitcodes: + 0 - all certs are up to date. + 1 - certificates to renew were found + 2 - outdatedt certificates were found + + 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 -- GitLab