Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the proper way to script a new nginx instance with SSL on a new Ubuntu 16.04 server?

I have this so far but I'm missing a couple of things like getting the cron job scripted. Don't want to do this as root. So I'm assuming some more could be done to set up the first user at the same time. The script would need to be idempotent (can be run over and over again without risking changing anything if it was run with the same arguments before).

singledomaincertnginx.sh:

#!/bin/bash
if [ -z "$3" ]; then
        echo use is "singledomaincertnginx.sh <server-ssh-address> <ssl-admin-email> <ssl-domain>"
        echo example: "singledomaincertnginx.sh [email protected] [email protected] some-sub-domain.mydomain.com"
        exit
fi
ssh $1 "cat > ~/wks" << 'EOF'
#!/bin/bash
echo email: $1
echo domain: $2
sudo add-apt-repository -y ppa:certbot/certbot
sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get install -y software-properties-common
sudo apt-get install -y python-certbot-nginx
sudo apt-get install -y nginx
sudo sed -i "s/server_name .*;/server_name $2;/" /etc/nginx/sites-available/default
sudo systemctl restart nginx.service
if [[ -e /etc/letsencrypt/live/$2/fullchain.pem ]]; then
  sudo certbot -n --nginx --agree-tos -m "$1" -d "$2"
fi
if [[ ! sudo crontab -l | grep certbot ]]; then
  # todo: add cron job to renew: 15 3 * * * /usr/bin/certbot renew --quiet
EOF
ssh $1 "chmod +x ~/wks"
ssh -t $1 "bash -x -e ~/wks $2 $3"
like image 210
Adam Dymitruk Avatar asked Oct 18 '17 06:10

Adam Dymitruk


5 Answers

I have this so far but I'm missing a couple of things like getting the cron job scripted.

Here's one way to complete (and correct) what you started:

if ! sudo crontab -l | grep certbot; then
    echo "15 3 * * * /usr/bin/certbot renew --quiet" | sudo tee -a  /var/spool/cron/crontabs/root >/dev/null
fi

Here's another way I prefer because it doesn't need to know the path of the crontabs:

if ! sudo crontab -l | grep certbot; then
    sudo crontab -l | { cat; echo "15 3 * * * /usr/bin/certbot renew --quiet"; } | sudo crontab -
fi

Something I see missing is how the certificate file /etc/letsencrypt/live/$domain/fullchain.pem gets created. Do you provide that by other means, or do you need help with that part?

Don't want to do this as root.

Most of the steps involve running apt-get, and for that you already require root. Perhaps you meant that you don't want to do the renewals using root. Some services operate as a dedicated user instead of root, but looking through the documentation of certbot I haven't seen anything like that. So it seems a common practice to do the renewals with root, so adding the renewal command to root's crontab seems fine to me.

I would improve a couple of things in the script to make it more robust:

  • The positional parameters $1, $2 and so on scattered around are easy to lose track of, which could lead to errors. I would give them proper names.

  • The command line argument validation if [ -z "$3" ] is weak, I would make that more strict as if [ $# != 3 ].

  • Once the remote script is generated, you call it with bash -e, which is good for safeguarding. But if the script is called by something else without -e, the safeguard won't be there. It would be better to build that safeguard into the script itself with set -e. I would go further and use set -euo pipefail which is even more strict. And I would put that in the outer script too.

  • Most of the commands in the remote script require sudo. For one thing that's tedious to write. For another, if one command ends up taking a long time such that the sudo session expires, you may have to reenter the root password a second time, which will be annoying, especially if you stepped out for a coffee break. It would be better to require to always run as root, by adding a check on the uid of the executing user.

  • Since you run the remote script with bash -x ~/wks ... instead of just ~/wks, there's no need to make it executable with chmod, so that step can be dropped.

Putting the above together (and then some), I would write like this:

#!/bin/bash

set -euo pipefail

if [ $# != 3 ]; then
    echo "Usage: $0 <server-ssh-address> <ssl-admin-email> <ssl-domain>"
    echo "Example: singledomaincertnginx.sh [email protected] [email protected] some-sub-domain.mydomain.com"
    exit 1
fi

remote=$1
email=$2
domain=$3

remote_script_path=./wks

ssh $remote "cat > $remote_script_path" << 'EOF'
#!/bin/bash

set -euo pipefail

if [[ "$(id -u)" != 0 ]]; then
    echo "This script must be run as root. (sudo $0)"
    exit 1
fi

email=$1
domain=$2

echo email: $email
echo domain: $domain

add-apt-repository -y ppa:certbot/certbot
apt-get update
apt-get upgrade -y
apt-get install -y software-properties-common
apt-get install -y python-certbot-nginx
apt-get install -y nginx
sed -i "s/server_name .*;/server_name $domain;/" /etc/nginx/sites-available/default
systemctl restart nginx.service
#service nginx restart

if [[ -e /etc/letsencrypt/live/$domain/fullchain.pem ]]; then
    certbot -n --nginx --agree-tos -m $email -d $domain
fi

if ! crontab -l | grep -q certbot; then
    crontab -l | {
        cat
        echo
        echo "15 3 * * * /usr/bin/certbot renew --quiet"
        echo
    } | crontab -
fi
EOF

ssh -t $remote "sudo bash -x $remote_script_path $email $domain"
like image 93
janos Avatar answered Oct 16 '22 11:10

janos


If you are doing this with sudo you are doing this as root

this is a simple thing to do in ansible, best do it there

to do the cron job do this:

CRON_FILE="/etc/cron.d/certbot"

if [ ! -f $CRON_FILE ] ; then

echo '15 3 * * * /usr/bin/certbot renew --quiet' > $CRON_FILE

fi

like image 38
ms4720 Avatar answered Oct 16 '22 10:10

ms4720


Are you looking for something like this:

if [[ "$(grep '/usr/bin/certbot' /var/spool/cron/crontabs/$(whoami))" = "" ]]
then
    echo "15 3 * * * /usr/bin/certbot renew --quiet" >> /var/spool/cron/crontabs/$(whoami)
fi

and the fi at the end

you can also avoid doing that much sudo by concatenating them like in:

sudo bash -c 'add-apt-repository -y ppa:certbot/certbot;apt-get update;apt-get upgrade -y;apt-get install -y software-properties-common python-certbot-nginx nginx;sed -i "s/server_name .*;/server_name $2;/" /etc/nginx/sites-available/default;systemctl restart nginx.service'
like image 30
user1747036 Avatar answered Oct 16 '22 09:10

user1747036


There are multiple ways to do this and they could be considered "proper" depending on the scenario.

One way to do it on boot time could be using cloud-init, For testing in the case of using AWS when creating the instance you could add your custom script:

enter image description here

This will allow running commands on launch of your instance, In case you would like to automate this process (infrastructure like code) you could use for example terraform

If for some reason you already have the instance up and running and just want to update on demand but not using ssh, you could use saltstack.

Talking about "Idempotency" Ansible could be also a very good tool for doing this, from the ansible glossary:

An operation is idempotent if the result of performing it once is exactly the same as the result of performing it repeatedly without any intervening actions.

There are many tools that can help you achieve this, only thing is to find the tool that adapts better to your needs/scenario.

like image 1
nbari Avatar answered Oct 16 '22 11:10

nbari


Copy-paste solution for nginx + Ubuntu

Install dependencies

sudo apt-get install nginx -y
sudo apt-get install software-properties-common -y
sudo add-apt-repository universe -y
sudo add-apt-repository ppa:certbot/certbot -y
sudo apt-get update
sudo apt-get install certbot python-certbot-nginx -y

Get SSL certificate and redirect all traffic from http to https

certbot --nginx --agree-tos --redirect --noninteractive \
        --email [email protected] \
        --domain YOUR.DOMAIN.COM

Test renewal

certbot renew --dry-run

Docs

https://certbot.eff.org/lets-encrypt/ubuntuxenial-nginx

like image 1
Max Malysh Avatar answered Oct 16 '22 10:10

Max Malysh