If you create a cloud virtual machine and run it for anything more than a few minutes, it quickly becomes apparent how much nefarious activity goes on. You will get hit with brute force attacks on all of the known ports (and some unknown ones) pretty much continuously from the first minute.

I don’t know that there is much that cloud hosts can do about this. They have known IP ranges and I would assume that attackers constantly troll those known ranges. Mostly, it’s pretty harmless stuff. It’s annoying to see how many scripts are out there trying to login as root to port 22 but if you’ve got a handle on it, they won’t get too far.

Which brings me to the topic today. I use Hetzner Cloud for a number of small, experimental projects and I create a bunch of systems on there. In general, they aren’t long lived and I don’t want to invest a lot of time securing them. I have a series of steps that I go through when I create a new server and it gets annoying to do the same configuration everytime. So I did what every lazy programmer does when confronted with tedious, repetitive tasks: I wrote a script.

Basically, what it does is use cloud-init to do a baseline security setup for an Ubuntu server. Nothing fancy: create a user so I don’t have to use root, move the default SSH port and turn on ufw. The bash script itself just lets me pass in the parameters I might want to change and then runs hcloud to create the server.

It’s not a masterwork, but it gets the job done for me. The only things you need to have are a local ssh key that has been uploaded to your Hetzner account with the same name and a local install of the hcloud cli. I wrote this on a Mac using zsh but it should work on bash as well. The script appears below:

#!/bin/bash
set -euo pipefail

# make a choice that is then handled in main
# use: choice <prompt>
# returns: global variable CHOICE

function choice {

    CHOICE=''
    local prompt="$*"
    local answer

    read -p "$prompt" answer
    case "$answer" in  
        [yY1] ) CHOICE='y';;
        [nN0] ) CHOICE='n';;
        *     ) CHOICE="$answer";;
    esac
} # end of function choice

# hcloud arguments
NAME=tiger
IMAGE=ubuntu-20.04
TYPE=cx11
SSH_KEY=id_hetzner

# cloud-config arguments
USER_NAME=demo
SSH_PUB_KEY=`cat ~/.ssh/$SSH_KEY.pub`
SSH_PORT=4444

# use getopts to get the arguments 
while getopts 'n:i:t:s:u:p:' OPTION
do
    case $OPTION in
        n) NAME="$OPTARG"
           ;;  
        i) IMAGE="$OPTARG"
           ;;  
        t) TYPE="$OPTARG"
           ;;  
        u) USER_NAME="$OPTARG"
           ;;  
        s) SSH_KEY="$OPTARG"
           ;;  
        p) SSH_PORT="$OPTARG"
           ;;
        ?) printf "Usage: %s -n server_name -i "\
           "image -t value -s ssh_key -u user_name "\
           "-p ssh_port\n" ${0##*/} >&2
           exit 2
           ;;
    esac
done

printf "Provided server parameters\n"
printf "\tName: $NAME\n"
printf "\tImage: $IMAGE\n"
printf "\tType: $TYPE\n"
printf "\tSSH Key: $SSH_KEY\n"
printf "\tUser name: $USER_NAME\n"
printf "\tSSH Port: $SSH_PORT\n"
printf "\n"

choice "Would you like to create the server? [Y/n]:"
if [ "$CHOICE" != "n" ]; then
    printf "Creating...\n"
else
    exit 0
fi

# a here-document to pass the cloud-init script
hcloud server create --name "$NAME" --image "$IMAGE" --type "$TYPE" \
                     --ssh-key "$SSH_KEY" --user-data-from-file - <<EOF
#cloud-config
users:
  - name: $USER_NAME
    ssh-authorized-keys:
      - $SSH_PUB_KEY
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    groups: sudo
    shell: /bin/bash
runcmd:
  - sed -i -e '/^#Port/s/^.*$/Port $SSH_PORT/' /etc/ssh/sshd_config
  - systemctl restart sshd
  - ufw allow $SSH_PORT/tcp
  - ufw enable
EOF