Letsencrypt - Need assistance with http and nginx

Discuss your pilot or production implementation with other Zimbra admins or our engineers.
User avatar
JDunphy
Outstanding Member
Outstanding Member
Posts: 901
Joined: Fri Sep 12, 2014 11:18 pm
Location: Victoria, BC
ZCS/ZD Version: 9.0.0_P39 NETWORK Edition

Re: Letsencrypt - Need assistance with http and nginx

Post by JDunphy »

Basically this is what you would do the first time and acme.sh will handle this automatically in the future.
So paste this into your ~/.acme.sh/deploy directory as zimbra.sh

Code: Select all

#!/bin/bash

# Zimbra Assumptions:
#    1) acme.sh is installed as Zimbra
#    2) see: https://wiki.zimbra.com/wiki/JDunphy-Letsencrypt

########  Public functions #####################

#domain keyfile certfile cafile fullchain
zimbra_deploy() {
  _cdomain="$1"
  _ckey="$2"
  _ccert="$3"
  _cca="$4"
  _cfullchain="$5"

  _debug _cdomain "$_cdomain"
  _debug _ckey "$_ckey"
  _debug _ccert "$_ccert"
  _debug _cca "$_cca"
  _debug _cfullchain "$_cfullchain"

  # Zimbra's javastore still needs DST Root CA X3 to verify on some versions
  _IdentTrust="$(dirname "$_cca")/../IdentTrust.pem"
  _debug _IdentTrust "$_IdentTrust"

  # grab it if we don't have it
  if [ ! -f "$_IdentTrust" ]; then
     _debug No "$_IdentTrust"
     wget -q "https://ssl-tools.net/certificates/dac9024f54d8f6df94935fb1732638ca6ad77c13.pem" -O "$_IdentTrust" || return 1
  fi

  # append Intermediate 
  cat "$_cfullchain" "$(dirname "$_cca")/../IdentTrust.pem" > "${_cca}.real"
  /opt/zimbra/bin/zmcertmgr verifycrt comm "$_ckey" "$_ccert" "${_cca}.real" || return 1

  #if it verifies we can deploy it
  logger -p local2.info NETWORK "Certificate has been Renewed for $_cdomain"
  cp -f "$_ckey" /opt/zimbra/ssl/zimbra/commercial/commercial.key
  /opt/zimbra/bin/zmcertmgr deploycrt comm "$_ccert" "${_cca}.real" || return 1

  # %%% ldap wasn't being restarted leading to failed communication in the future if we hadn't done a restart.
  # Adding a ldap restart was not tested so perhaps. Reload is restart when not defined by zimbra with
  # exception of ldap which they didn't provide a reload.
  #/opt/zimbra/bin/ldap restart
  #/opt/zimbra/bin/zmmailboxdctl reload
  #/opt/zimbra/bin/zmproxyctl reload
  #/opt/zimbra/bin/zmmtactl reload

  /opt/zimbra/bin/zmcontrol restart
  return 0
}
Feel free to only restart what you need but the script will do a zmcontrol restart by default.
You would then deploy your new certs like this but because you had to run this as root, double check zimbra has read permission to the folder or chown ownership

Code: Select all

# su - zimbra
% 
So if I was doing this from scratch as you had listed it, it would look like this after pasting in that deploy script. First stop the nginx proxy for zimbra which is listening on port 80

Code: Select all

# ./.acme.sh/acme.sh --issue --standalone -d mymail.DOMAIN.COM
# cd .acme.sh
# chown -R 755 mymail.DOMAIN.COM
# ./.acme.sh/acme.sh --deploy --deploy-hook zimbra -d mymail.DOMAIN.COM
You have a few extra hoops to worry about because it needs to be run as root to listen on port 80 with standalone. If you look inside the mymail.DOMAIN.COM directory you will find a file called mymail.DOMAIN.COM.conf ... that is where the deploy hook and validation method are defined. That allows you in the future to do something like this which is what acme.sh does by default and adds an entry to your crontab for the user that installed acme.sh during install.

Code: Select all

# su - zimbra
%  "/opt/zimbra/.acme.sh"/acme.sh --cron --home "/opt/zimbra/.acme.sh"
That would renew and install new certs approx every 60 days unless you use the --force option to do it sooner.

The problem you are going to have is listening on port 80 is root and you need to install the certs as zimbra so you will need to compensate for this issue so the above would not renew automatically as I have typed it above. What I do is use the challengeAlias because one of my domains is handled by me directly and doesn't have a DNS api... Get any domain and put it with cloudflare or other DNS provider that has an API and is supported by acme.sh. I use cloudflare as pricing is wholesale cost for .com's $8 or so.... I then use this domain for any other domains we have to support. The only thing we do is add a CNAME entry for the "real" domains that we manage ourselves that points to this cloudflare domain that we are using the challengeAlias with. You would also add your API keys the first time to the account.conf so the cloudflare script dns_cf has permission to add/delete TXT records.

The advantage of the DNS method is that I don't have to take an outage with zimbra while your are trying to verify the certificate and you can do it as the zimbra user, and acme.sh will just work automatically renew every 60 days for new cert and installation. So my process looks like this the first time after setting up that CNAME for example.com and example2.com pointing to gsans1.com (BIND syntax below)

Code: Select all

; letencrypt - dns alias
; zimbra
_acme-challenge           IN CNAME _acme-challenge.gsans1.com.
_acme-challenge.mail      IN CNAME _acme-challenge.gsans1.com.
_acme-challenge.tmail     IN CNAME _acme-challenge.gsans1.com.
You can cookie cutter this CNAME and post it for both example.com and example2.com or as many other domains you manage or don't have a dns api for TXT record insertion/deletion and want letsencrypt certs for. When you run acme.sh, it will use the dns_cf.sh script in the dns directory and add the TXT records and remove them automatically in gsans1.com as letsencrypt verifies example2.com and example1.com. So my process looks like this after making sure my account.conf has my API key information for cloudflare and I have copied the deploy script the very first time. If you don't manage/host your DNS zones directly but use a DNS provider it is even simpler... just use --dns dns_cf or whatever your provider is... forget the --challenge-alias gsans1.com stuff.

Code: Select all

# su - zimbra
% cd ~/.acme.sh
% ./acme.sh --issue --dns dns_cf --challenge-alias gsans1.com -d mail.example.com -d mail.example2.com 
% ./acme.sh --issue --deploy --deploy-hook zimbra -d mail.example.com
Then zimbra's cron does it automatically in the future with this command. Not necessary to think about deploy, issue, etc.

Code: Select all

% "/opt/zimbra/.acme.sh"/acme.sh --cron --home "/opt/zimbra/.acme.sh"
PS... the challengeAlias has one additional feature... You can get letsencrypt certs for servers that are in RFC1918 space given gsans1.com is external but the -d domain.example.com have CNAMES pointing to that. I do that for my home servers.. Things like plex or test servers. The other challenge/validation methods can't do that unless you open up the service to the internet and write some creative FW rules and port mapping.

HTH,

Jim
Last edited by JDunphy on Wed Jun 03, 2020 3:12 pm, edited 1 time in total.
User avatar
JDunphy
Outstanding Member
Outstanding Member
Posts: 901
Joined: Fri Sep 12, 2014 11:18 pm
Location: Victoria, BC
ZCS/ZD Version: 9.0.0_P39 NETWORK Edition

Re: Letsencrypt - Need assistance with http and nginx

Post by JDunphy »

For completeness, acme.sh has other modes to help with validation that don't require port conflict resolution like --standalone. In some modes, you paste a known string or a file at a location and your webserver or inverse proxy would handle the validation for you.
He keeps adding more modes so I will just list the reference here. This changes often as the acme protocol changes or the community has new ideas.

https://github.com/acmesh-official/acme.sh

Supported modes.

Code: Select all

Webroot mode
Standalone mode
Standalone tls-alpn mode
Apache mode
Nginx mode
DNS mode
DNS alias mode
Stateless mode
Not bad for a bash script eh? :-)
User avatar
cyber7
Advanced member
Advanced member
Posts: 192
Joined: Sat Sep 13, 2014 1:14 am
Location: Cape Town
ZCS/ZD Version: Release 9.0.0_GA_3924.RHEL7_64_2020
Contact:

Re: Letsencrypt - Need assistance with http and nginx

Post by cyber7 »

Thank you for the detailed information, Jim. I will look into this once my kids are to bed...
User avatar
JDunphy
Outstanding Member
Outstanding Member
Posts: 901
Joined: Fri Sep 12, 2014 11:18 pm
Location: Victoria, BC
ZCS/ZD Version: 9.0.0_P39 NETWORK Edition

Re: Letsencrypt - Need assistance with http and nginx

Post by JDunphy »

It always seems difficult the first time. The thing to remember is its 2 steps with acme.sh. You issue your certificates and if you hare happy with the results you can deploy them. It can be done in one step later but think 2 steps the first time you do it.

If anything is going to mess up, it will be the deploy. The original posting was long because it documented the 3-4 steps we do with zimbra installation. I also put those steps into a standalone shell script I initially wrote which is in my github area and pauses before each step to let one watch... whereas the acme.sh deploy zimbra.sh script just does it and even builds the chain and pulls any missing certs for you the first time.

Permissions are really important so keep this in mind if you use any other non zimbra accounts to install acme.sh or issue your certificates.

There is a bug in zmcertmgr with cwd that I tried and failed to get Zimbra to fix which can be tripped if permissions are not correct from ~/.acme.sh for the zimbra user. It will verify but once you try to install it, the bug is tripped and it will break your system. Over the years, people worked around this by copying certs to /tmp which mitigated that bug... The acme.sh zimbra.sh deploy script assumes everything was run as zimbra. The standalone bash script I initially wrote that you run as zimbra does a cp first of ~/.acme.sh before trying to verify and install it with zmcertmgr. So in theory, a chown -R zimbra ~/.acme.sh would work if you are mixing and matching accounts to create the certificates and installing them (always zimbra user).

Its so simple once it works. :-) Take your time.

BTW, I have cloudflare which is free for dns hosting email me transparency notification reports on my certs. PKI isn't great... any CA can sign one of your certs and should a bad actor convince them, they would have a valid cert for your domain. If that CA was compromised, you wouldn't know. Another reason, I like short lived certificates because of this problem with PKI. With letsencrypt they log all the requests so the transparency notification reports alert me to new certs issued for my domains. I expect to see a report after I renew them. Great feature and more CA are now offering that service to log certs that are signed by them. There is also a TXT record you can create that tells browsers don't used this certificate unless it was signed by your CA. either ---> CAA 128 issue "letsencrypt.org" or IN TYPE257 \# 22 000569737375656C657473656E63727970742E6F7267 for older version of BIND.

Jim
User avatar
cyber7
Advanced member
Advanced member
Posts: 192
Joined: Sat Sep 13, 2014 1:14 am
Location: Cape Town
ZCS/ZD Version: Release 9.0.0_GA_3924.RHEL7_64_2020
Contact:

Re: Letsencrypt - Need assistance with http and nginx

Post by cyber7 »

Hi Jim

thank you for all the detailed answers. What I did to get this going was:

from ZIMBRA:

Code: Select all

zmproxyctl stop
from ROOT:

Code: Select all

acme.sh --issue --standalone -d mymail.DOMAIN.COM
I then copied all the contents in /root/.acme.sh/mymail.DOMAIN.COM/ to /opt/zimbra/.acme/mymail.DOMAIN.COM/

from ZIMBRA:

Code: Select all

zmproxyctl start
from ZIMBRA

Code: Select all

zmproxyctl stop
./.acme.sh/acme.sh --deploy --deploy-hook zimbra -d mymail.DOMAIN.COM
Question:
1. Which certificates was actually used? The ones from ROOT or from ZIMBRA?
2. The ZIIMBRA user now has a crontab entry of:

Code: Select all

27 0 * * * "/opt/zimbra/.acme.sh"/acme.sh --cron --home "/opt/zimbra/.acme.sh" > /dev/null
is this right?
3. How do you think I will have to renew the CERT after 60 days? Do you think I need to remove the ZIMBRA CRON entry and create a script running from root's cron that looks like this:

Code: Select all

su - zimbra -c "/opt/zimbra/bin/zmproxyctl stop"
/root/.acme/acme.sh --issue --standalone -d mymail.DOMAIN.COM
cp /root/.acme.sh/mymail.DOMAIN.COM/* /opt/zimbra/.acme/mymail.DOMAIN.COM/*
su - zimbra -c "/opt/zimbra/.acme.sh/acme.sh --deploy --deploy-hook zimbra -d mymail.DOMAIN.COM"
which basically duplicates all the steps I took to get the certificates?

Thank you for your valuable time and effort getting me sorted
kind regards
cyber7 (aka Aubrey kloppers, Cape Town, South Africa)
User avatar
JDunphy
Outstanding Member
Outstanding Member
Posts: 901
Joined: Fri Sep 12, 2014 11:18 pm
Location: Victoria, BC
ZCS/ZD Version: 9.0.0_P39 NETWORK Edition

Re: Letsencrypt - Need assistance with http and nginx

Post by JDunphy »

cyber7 wrote: Question:
1. Which certificates was actually used? The ones from ROOT or from ZIMBRA?
2. The ZIIMBRA user now has a crontab entry of:

Code: Select all

27 0 * * * "/opt/zimbra/.acme.sh"/acme.sh --cron --home "/opt/zimbra/.acme.sh" > /dev/null
is this right?
3. How do you think I will have to renew the CERT after 60 days? Do you think I need to remove the ZIMBRA CRON entry and create a script running from root's cron that looks like this:

Code: Select all

su - zimbra -c "/opt/zimbra/bin/zmproxyctl stop"
/root/.acme/acme.sh --issue --standalone -d mymail.DOMAIN.COM
cp /root/.acme.sh/mymail.DOMAIN.COM/* /opt/zimbra/.acme/mymail.DOMAIN.COM/*
su - zimbra -c "/opt/zimbra/.acme.sh/acme.sh --deploy --deploy-hook zimbra -d mymail.DOMAIN.COM"
which basically duplicates all the steps I took to get the certificates?

Thank you for your valuable time and effort getting me sorted
kind regards
cyber7 (aka Aubrey kloppers, Cape Town, South Africa)
Hi Aubrey,

I think it was the zimbra cert that was installed given you su - to the zimbra account before executing the zimbra copy of acme.sh in the zimbra directory. That acme.sh script does have this option --cert-home which might allow you to use the root accounts certificate if that is what you want but I have not tried that option.

I don't think the default acme.sh zimbra cron entry will renew in 60 days properly because it would not shutdown nginx and it would need to run --standalone as root. Almost as easy to put the 2-3 commands in a script as you have shown and execute it directly from cron for renewal. acme.sh does have a --pre-hook option you might be able to use to do some of that housekeeping before certificate issue if you want to use that --cron option of the script.

With the DNS method, we only use --issue even for renewals. At one time, we did --renew but that was deprecated. I wonder if that applies to you or you need to replace --issue with ---renew using the standalone mode. I don't know.

A few ideas to help debug your renewal method... comment out the zimbra commands or put echo in front of them them in the deploy/zimbra.sh script. You can then practice issue/renewals of the certs using your script without installing them and observe the pathnames, etc. If you enable the debug mode, the zimbra.sh script will tell you what pathnames were used. The certificates are good for 90 days but renew in 60 unless you use the --force option. acme.sh also has a --staging option to help you debug and issue/renew certificates. The default only allows you so many issues per day of new certificates but the staging option lets you practice to get your scripts working and verified.

run this command to see when your next renewal will be tried

Code: Select all

% ./acme.sh --list
updates of the acme.sh script are done like this

Code: Select all

./acme.sh --upgrade
Jim
Post Reply