Update: There’s a new post that covers this topic and more over here.
Things we want done on a newly spun up server instance running Ubuntu.
TLDR version
-
Update and upgrade apt packages
-
Create a new user (
deploy
) for SSH access and deployments -
Setup logging in as
deploy
user using your local SSH public key -
Add
deploy
user to sudoers -
Setup unattended upgrades for the OS
-
Setup firewall (UFW) for the OS
The code for this post is available at this GitHub repository. The instructions for running the same is also available in it. This post aims to explain the process in a verbose fashion.
Update and upgrade apt packages
This one is fairly self explanatory. We want to ensure that all the packages on the system are updated to their latest version.
- name: Update and upgrade apt packages
apt:
upgrade: yes
update_cache: yes
cache_valid_time: 86400 # Update the apt cache if its older than cache_valid_time.
Ansible apt module documentation
Create a new user for SSH access and deployments
We want to create a new user with sudo
access (done later on in the process). This will be the user we use to login to the instance and perform any administrative actions once the hardening process is done.
- name: Create a deploy user
user:
name: deploy
shell: /bin/bash
password: $6$kcambP/ZjEfvk0d$0iBhHo9C0jUG2x2GaftkKEvCoffgv2Krb1MQSXlgwH3718pT7GaxZsXkmb0puMa.z3zQAzWxiYtPLVO9gYIXn0
Note that the password for the user must be hashed. There are several ways in which you can create a password hash. I personally prefer using the mkpasswd utility like so
mkpasswd --method=SHA-512 --stdin
You will be prompted to enter a new password. Once you hit return, the hash for the password you entered will be printed on screen.
Setup logging in as deploy user using your local SSH public key
Once we finish the hardening process, we want to be able to login to the instance using our local SSH public key. Like so
ssh deploy@<instance_ip_address>
As a prerequisite, setup a new SSH key-pair that you want to be able to use with all of your newly created instances. You can follow the steps oultined in this GitHub help page to do so. Assuming you generated a new key-pair as remote_machines
and remote_machines.pub
,
- name: Set authorized key taken from file
authorized_key:
user: deploy
state: present
key: "{{ lookup('file', '/home/<your_username>/.ssh/remote_machines.pub') }}"
Note, replace <your_username>
with the appropriate value.
Set permissions on the authorized_keys
file (on the remote machine), to allow only the file owner to read the file.
- name: Set file permissions on /home/deploy/.ssh/authorized_keys
file:
path: /home/deploy/.ssh/authorized_keys
owner: deploy
group: deploy
mode: 0400
Ansible authorized_key module documentation
Ansible file module documentation
Linux file permissions documentation
Add deploy
user to sudoers
To allow deploy
user, created above, to have sudo
privileges, we must add the user to the sudoers
list
- name: Add deploy to sudoers
lineinfile:
path: /etc/sudoers
state: present
regexp: "^%deploy"
line: "%deploy ALL=(ALL) ALL"
validate: "/usr/sbin/visudo -cf %s"
For additional security, we remove sudo
privileges granted by default to certain group
s
- name: Disable admin group in sudoers
lineinfile:
path: /etc/sudoers
state: absent
regexp: "^%admin"
line: "#%admin ALL=(ALL) ALL"
validate: "/usr/sbin/visudo -cf %s"
- name: Disable sudo group in sudoers
lineinfile:
path: /etc/sudoers
state: absent
regexp: "^%sudo"
line: "#%sudo ALL=(ALL) ALL"
validate: "/usr/sbin/visudo -cf %s"
And disallow login as root using password
- name: Disable root login via password
lineinfile:
path: /etc/ssh/sshd_config
state: present
regexp: "^PermitRootLogin"
line: "PermitRootLogin prohibit-password"
And allow logins only using valid SSH keys
- name: Disable PasswordAuthentication
lineinfile:
path: /etc/ssh/sshd_config
state: present
regexp: "^PasswordAuthentication"
line: "PasswordAuthentication no"
Ansible lineinfile module documentation
Setup unattended upgrades for the OS
We want all security updates to be installed automatically. We use the Automatic Updates package from Ubuntu to do this for us.
- name: Install unattended-upgrades package
apt:
name: unattended-upgrades
update_cache: yes
- name: Enable periodic updates
copy:
src: 10periodic
dest: /etc/apt/apt.conf.d/10periodic
owner: root
group: root
mode: 0644
We’re basically copying a file with our required configuration to the remote system. The contents of the file are as such
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";
By default, only security updates are configured to be automatically applied. We can also control how frequently apt checks for updates. Read more about the available configuration options on the official Ubuntu help page for the same.
Setup firewall (UFW) for the OS
We want to be able to completely control how other remote machines can talk to our instance. On a fresh machine, the only port that needs to opened is port 22
, that allows SSH access. When you install other dependencies later on (perhaps Nginx), make sure to open ports required by those applications.
- name: Enable ufw access for OpenSSH
ufw:
rule: allow
name: OpenSSH
- name: Enable ufw
ufw:
state: enabled
Note: UFW is disabled by default and we must explicitly enable it.
Ansible UFW module documentation
UFW help page on Ubuntu Community Wiki
Bonus points
You can setup the instance to be able to pull your private Git repositories. The way to go about this is to first create a new SSH key that will have access (granted using the UI on GItHub/GitLab) to your GitHub/GitLab/other projects. And then copy said SSH key-pair to this new instance.
Note, refer to this GitHub help page for creating SSH Keys. The post assumes that the new key-pair created are named as deploy
and deploy.pub
.
- name: Copy deploy ssh private key
copy:
src: deploy
dest: /home/deploy/.ssh/deploy
owner: deploy
group: deploy
mode: 0600
- name: Copy deploy ssh public key
copy:
src: deploy.pub
dest: /home/deploy/.ssh/deploy.pub
owner: deploy
group: deploy
mode: 0644
You can also configure the ssh program on the instance to use this specific key, when dealing with your GitHub/GitLab projects, by creating a config file for the same.
- name: Copy ssh config file
copy:
src: ssh_config
dest: /home/deploy/.ssh/config
owner: deploy
group: deploy
mode: 0644
The contents of the file specify which key-pair to use and when
Host gitlab.com
HostName gitlab.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/deploy
And that’s it. Moving to an automated workflow is hard in the beginning. Start small, and gradually move on to more complicated automated flows. I will be posting examples for automating deployments of postgres, elasticsearch, docker and for obtaining SSL certificates (Let’s Encrypt) for your machines, in future posts.
Bookmarked. This is a really nice and clean post. It can be used as a cheatsheet.
Really looking forward to the future posts about automating deployments (especially the elasticsearch one)
Thanks for posting this. :)
Thank you for the kind words. I haven't done an elasticsearch one yet. Figured I'd cover the basics first. There's a new post at https://able.bio/jibiabraham/obtaining-ssl-certificates-using-nginx-and-ansible--3585rdl if you're interested in more