Search code examples
bashsslautomationcpanelnamecheap

Auto Generate and Update SSL on Shared Hosting


I am trying to auto generate SSL certs and upload them to my shared hosing on namecheap.com

The host does not offer any way to auto manage ssl certs without paying a lot of money.

I am trying to get this script working

https://catelin.net/2018/03/24/fully-automate-ssl-tls-certificate-renewal-with-cpanel/

The idea is to run bash scrips on your own linux box that will update the ssl certs through cpanel.

I am having difficulty with this section of code. In my actual code I have updated the server name info. The main problem is that I have very little experience writing in bash script.

certificate=$(echo |openssl s_client -servername yourserver.com -connect yourserver.com:443 2>/tmp/cert.tmp|openssl x509 -checkend $[86400 * $RENEW] -enddate)
if [ "$certificate" == "" ]; then
  echo "Error: unable to check certificate"
else
  if [[ $certificate =~ (.*)Certificate will expire ]]; then
    echo $certificate
    ...

I am getting an error here (my first of many errors I am sure...)

./certupdate.sh: line 19: syntax error in conditional expression
./certupdate.sh: line 19: syntax error near `will'
./certupdate.sh: line 19: `    if [[ $certificate =~ (.*)Certificate will expire ]]; then'

Any help would be great.

Or, if someone has a better idea on how to update the ssl certs that would be even better. Something in all PHP would be great as I am more familiar with that.


Solution

  • The shell parses each line into tokens by splitting on whitespace. The syntax of the [[ built-in with =~ requires one token on each side. You can prevent splitting on whitespace by putting backslashes in front of every whitespace character which is not a token separator, or quoting the sequence which should be a single token.

      if [[ $certificate =~ (.*)"Certificate will expire" ]]; then
    

    That aside, you really don't need a regular expression here. (And if you do use one, the parentheses around .* are superfluous. In fact the whole .* is superfluous.)

      if [[ $certificate = *"Certificate will expire"* ]]; then
    

    That aside, the script you are trying to copy has a number of other issues, albeit more minor ones. Probably just try to find a better blog to copy/paste from.

    Here's a quick refactoring to hopefully make the script more idiomatic, but I might have missed some issues, and don't have any way to test this.

    #!/bin/bash
    
    # Don't use uppercase for private variables
    renew=22
    
    # For diagnostic messages
    me=${0##*/}
    
    # Parametrize domain name
    dom="yourserver.com"
    email="[email protected]"
    
    # Don't echo so much junk
    # (I will silently drop the other junk output without comment)
    # echo "======================================="
    # Fix date formatting, print diagnostic to stderr
    date "+$me: %c START" >&2
    # Use a unique temp file to avoid symlink attacks and concurrency problems
    t=$(mktemp -t letsencryptCA.XXXXXXXXXX.crt) || exit
    # Clean it up when we are done
    trap 'rm -f "$t"' ERR EXIT
    wget -O "$t" https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt
    
    # Avoid obsolete $[ ... ] math syntax
    # Avoid superfluous echo |
    # Don't write stderr to a junk file
    # Maybe add 2>/dev/null on the first openssl if this is too noisy
    certificate=$(openssl s_client -servername "$dom" -connect "$dom":443 </dev/null | 
                  openssl x509 -checkend $((86400 * $renew)) -enddate)
    # Indentation fixes
    if [ "$certificate" == "" ]; then
        # Error messages indicate script's name, and go to standard error
        # Include domain name in error message
        # Fixed throughout below
        echo "$me: Error: unable to check $dom certificate" >&2
        # Exit with an error, too
        exit 123
    else
        # Quote string
        # Move this outside the conditional, to avoid repeated code
        echo "$certificate"
    
        # Fix comparison
        if [[ $certificate = *"Certificate will expire"* ]]; then
    
            echo "$me: $dom certificate needs to be renewed" >&2
        
            # No idea here, assume this is okay
            # Wrap horribly long command though
            certbot certonly --non-interactive --staple-ocsp \
                --email "$email" -d "$dom" -d "www.$dom" \
                --agree-tos --manual \
                --manual-auth-hook /path/toyour/scripts/letsencryptauth.sh \
                --manual-cleanup-hook /path/toyour/scripts/letsencryptclean.sh
            echo "$me: $dom cert process completed, now uploading it to CPanel" >&2
        
            # Weird indentation fixed again
            USER='cpanel username' PASS='cpanelpassword' EMAIL="$email" \
            /usr/bin/php /path/toyour/scripts/sslic.php "$dom" \
                /etc/letsencrypt/live/"$dom"/cert.pem \
                /etc/letsencrypt/live/"$dom"/privkey.pem "$t"
            echo "$me: $dom upload to cpanel process complete" >&2
        
        else
            echo "$me: $dom cert does not need to be renewed" >&2
        fi
    fi
    # Fix date formatting, print diagnostic to stderr
    date "+$me: %c END" >&2
    

    The %c format specifier for date includes the year, where the original code omitted it. I consider this change a feature rather than a bug.

    There are still a lot of hard-coded paths etc which should probably be parametrized better.

    The stderr output from openssl is modest enough that I don't think we absolutely need to discard it; on the other hand, dumping it in a temporary file will almost certainly hide useful diagnostics when something actually goes wrong (network down or whatever).

    tripleee$ openssl s_client -servername www.stackoverflow.com \
    >   -connect www.stackoverflow.com:443 </dev/null >/dev/null
    depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
    verify return:1
    depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
    verify return:1
    depth=0 CN = *.stackexchange.com
    verify return:1