diff --git a/cm.sh b/cm.sh index b9b4eb3adc34680631de43cd8935f895572e8fbe..08a18b2686a8a445c279a24d5524ba0f5b38ac83 100755 --- a/cm.sh +++ b/cm.sh @@ -15,6 +15,7 @@ # 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 # ====================================================================== @@ -179,6 +180,17 @@ function _gencsr(){ 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(){ @@ -232,6 +244,12 @@ function _setenv(){ # 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 @@ -244,6 +262,15 @@ function _setenv(){ 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" @@ -270,12 +297,24 @@ function public_add(){ # # pulic function ADD OR RENEW certificate # -function public_add-or-renew(){ +function public_ensure(){ _requiresFqdn _certExists if [ $? -eq 0 ]; then - _wd "--- cert was found ... renew it (ignore --force - it comes from acme.sh)" - public_renew $* + _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 $* @@ -290,7 +329,8 @@ function public_delete(){ _certMustExist # TODO: revoke it too?? - # $ACME --revoke -d ${CM_fqdn} + _wd "--- revoke cert" + $ACME --revoke -d ${CM_fqdn} _wd "--- delete ACME.SH data" $ACME --remove -d ${CM_fqdn} $ACME_Params @@ -426,15 +466,15 @@ function public_show(){ _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:)" + 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:|DNS:)" - _certMatching + openssl x509 -noout -text -in ${CM_outfile_cert} | grep -E "(Issuer:|Subject:|Not\ |DNS:)"| sed "s#^\ *##g" } @@ -506,13 +546,17 @@ The ACTIONs for SINGLE certificate handlings are: 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") + 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. - (*) it doesn't verify the DNS aliases + 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 @@ -522,7 +566,8 @@ The ACTIONs for SINGLE certificate handlings are: and update files in ${CM_diracme} show FQDN - show place of csr + certificate data and show certificate + show place of csr + certificate data and show basic certificate data + (issuer, subject, aliases, ending date) ACTIONs for ALL certs diff --git a/inc_config.sh.dist b/inc_config.sh.dist index 329204d09833d81e1bf08c773bc697ca14865ad3..3f69189bccc0446fd8d1e9aac545e22c4f11c7ce 100644 --- a/inc_config.sh.dist +++ b/inc_config.sh.dist @@ -35,4 +35,9 @@ export ACME=../acme.sh/acme.sh # place for cnf + csr files # export CM_dircsr="./csr" +# check domain names before creating a new certificate +# It is used for faster rejection of a hostname or alias for which you +# have no permission +# export CM_certmatch="\.example\.com" + # ---------------------------------------------------------------------- diff --git a/readme.md b/readme.md index 893c7878505fbd50e974c88d16cfde2cdfe7be14..60bb6effddf1137f98df539fa17428a45fd84e15 100644 --- a/readme.md +++ b/readme.md @@ -16,6 +16,7 @@ license: GNU GPL 3.0 <http://www.gnu.org/licenses/gpl-3.0.html> * set path to acme.sh script; the default is a relative path for the suggested contellation below. * optional: set custom target for generated certificates * optional: for testing enable Let's Encrypt stage server to prevent running into weekly limits during tests + * optional: set a filter that must match to new certificate and all aliases * templates/csr.txt * set location, company and department ... remark: (currently?) it is removed by LE @@ -72,13 +73,17 @@ The ACTIONs for SINGLE certificate handlings are: same certificate. It updates files in ./certs - 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") + 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. - (*) it doesn't verify the DNS aliases + 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 @@ -88,7 +93,8 @@ The ACTIONs for SINGLE certificate handlings are: and update files in ./certs show FQDN - show place of csr + certificate data and show certificate + show place of csr + certificate data and show basic certificate data + (issuer, subject, aliases, ending date) ACTIONs for ALL certs @@ -98,6 +104,7 @@ ACTIONs for ALL certs renew-all renew all certificates (fast mode - without --force) and update files in ./certs + It is useful for a cronjob. other ACTIONs @@ -105,6 +112,7 @@ other ACTIONs check of health with current setup and requirements. This command is helpful for initial setups. + DEBUG: Using Let's Encrypt STAGE environment ... DEBUG: You can test and mess around. Do not use certs in production.