Phoenix Ignited is a proud tech solutions partner of Luxauro.com. View their news and media page here: Luxauro.com
In this lightning fast tutorial we’ll go through automating the first 10 minutes on an Ubuntu server (aka securing an Ubuntu server). The playbook includes installing various essential packages, creating users, copying users’ ssh pub keys to the remote servers, installing and configuring ufw and fail2ban, and hardening the ssh configuration. It requires that you have a remote sudo user on a remote machine with SSH Keys configured. You can see my tutorial here on configuring SSH Keys: https://phoenixignited.tech/setup-ssh-keys/
This tutorial aims to be beginner friendly without being an Intro to Ansible.
LINKS:
Official Ansible Site: https://www.ansible.com/
Ansible Docs: https://docs.ansible.com/
Installing Ansible on Ubuntu is as easy as:
sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible
Next we will setup our environment and create the necessary files:
mkdir Ansible-AIO-Ubuntu-Secure && cd Ansible-AIO-Ubuntu-Secure && touch inventory.ini && touch ansibleplay.yaml && touch Hardened-SSHD-CONFIG && touch JailConf
Next we will add our host(s) in the inventory.ini file, these are the remote machines that Ansible will control, open the inventory.ini file with vim, nano, etc. and add:
[myhosts]
Server_Name ansible_host=<server_ip> ansible_port=<port> ansible_user=<username> ansible_connection=ssh ansible_ssh_private_key_file=/path/to/private/ssh/key
Replace all of the variables with your desired values, and repeat for as many hosts as needed.
Next open the ansibleplay.yaml file and enter:
---
- name: All in One Ubuntu Server Setup Ansible Playbook
hosts: myhosts
become: yes
vars:
packages:
- vim
- git
- htop
- curl
- wget
- unzip
- build-essential
- net-tools
- tree
- fail2ban
- ufw
jail_local_src: "./JailConf" # Path to your jail.local file
custom_port: 12345 # Replace this with your custom port number
users:
- username: alice
is_admin: yes
pub_key_path: "/path/to/keys/alice.pub"
- username: bob
is_admin: no
pub_key_path: "/path/to/keys/bob.pub"
- username: charlie
is_admin: no
pub_key_path: "/path/to/keys/charlie.pub"
tasks:
## ENSURE SYSTEM IS DEBIAN BASED ##
- name: Ensure the system is Debian/Ubuntu based
debug:
msg: "This playbook is only supported on Debian/Ubuntu systems."
when: ansible_facts['os_family'] != "Debian"
failed_when: ansible_facts['os_family'] != "Debian"
## CREATE USER ACCOUNTS ##
- name: Create user accounts
user:
name: "{{ item.username }}"
state: present
groups: "{{ 'sudo' if item.is_admin else '' }}"
shell: /bin/bash
loop: "{{ users }}"
- name: Create .ssh directory for each user
file:
path: "/home/{{ item.username }}/.ssh"
state: directory
owner: "{{ item.username }}"
group: "{{ item.username }}"
mode: '0700'
loop: "{{ users }}"
- name: Copy SSH public key from file for each user
ansible.builtin.copy:
src: "{{ item.pub_key_path }}"
dest: "/home/{{ item.username }}/.ssh/authorized_keys"
owner: "{{ item.username }}"
group: "{{ item.username }}"
mode: '0600'
loop: "{{ users }}"
- name: Ensure sudo group has passwordless sudo (for admins)
lineinfile:
path: /etc/sudoers
regexp: '^%sudo'
line: '%sudo ALL=(ALL) NOPASSWD: ALL'
state: present
when: item.is_admin | bool
loop: "{{ users }}"
loop_control:
label: "{{ item.username }}"
## INSTALL COMMON PACKAGES ##
- name: Update apt cache and upgrade system packages
apt:
update_cache: yes
upgrade: dist
- name: Install commonly used software packages
apt:
name: "{{ packages }}"
state: present
update_cache: yes
## CONFIGURE FAIL2BAN ##
- name: Copy custom jail.local configuration file
ansible.builtin.copy:
src: "{{ jail_local_src }}"
dest: /etc/fail2ban/jail.local
owner: root
group: root
mode: '0644'
- name: Ensure Fail2Ban is running and enabled on boot
service:
name: fail2ban
state: started
enabled: yes
## CONFIGURE UFW ##
- name: Ensure UFW is enabled
ufw:
state: enabled
- name: Allow custom port through UFW
ufw:
rule: allow
port: "{{ custom_port }}"
proto: tcp
- name: Set UFW default policy to deny incoming
ufw:
state: enabled
default: deny
direction: incoming
- name: Set UFW default policy to allow outgoing
ufw:
state: enabled
default: allow
direction: outgoing
## HARDEN SSHD_CONFIG ##
- name: Copy the hardened SSHD_CONFIG file to the remote server
ansible.builtin.copy:
src: ./Hardened-SSHD-CONFIG # Path to your local sshd_config
dest: /etc/ssh/sshd_config # Destination path on the remote system
owner: root
group: root
mode: '0600' # Secure permissions
notify:
- Restart sshd
## REBOOT SERVER ##
- name: Reboot the server
reboot:
reboot_timeout: 600 # Wait up to 600 seconds (10 minutes) for the server to reboot
## TEST SERVER ACCESS ##
- name: Test connection after reboot
wait_for_connection:
timeout: 300 # Wait up to 300 seconds (5 minutes) for the server to become reachable again
handlers:
- name: Restart sshd
service:
name: "{{ (ansible_facts.services['sshd'] is defined) | ternary('sshd', 'ssh') }}"
state: restarted
This is our playbook, it includes various tasks including ensuring the system is Debian / Ubuntu based, creating user accounts, creating ssh directories for each user, copying ssh pub keys over to the remote server for the users, updating packages, installing and updating packages, installing fail2ban and ufw, copying over a hardened jail config file for fail2ban, configuring ufw, copying a hardened sshd file over to the remote server, and rebooting the server.
Be sure to modify any applicable variables such as usernames, pub key paths, packages to be installed, custom ports, etc.
Next we will configure our JailConf file that we will copy over to the remote server. It is pretty basic, add any applicable jails etc. Just open the JailConf file that we touched earlier and enter:
[DEFAULT]
# Ban IP for 10 minutes after 5 failed login attempts
bantime = 600
findtime = 600
maxretry = 5
# Enable SSH jail
[sshd]
enabled = true
# Jail for a custom port (e.g., port 12345 for a custom service)
[customport]
enabled = true
port = 12345
filter = customport
logpath = /var/log/custom_service.log
maxretry = 3
Now open the Hardened_SSHD_CONFIG file we created and enter:
Port <custom_port>
PermitRootLogin no
MaxAuthTries 3
MaxSessions 10
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no
KbdInteractiveAuthentication no
UsePAM yes
AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no
PrintMotd no
ClientAliveInterval 600
ClientAliveCountMax 3
LoginGraceTime 30s
UseDNS no
MaxStartups 10:30:60
AllowUsers <your_username>
# Optional hardening settings
#ChallengeResponseAuthentication no
#KerberosAuthentication no
#GSSAPIAuthentication no
#HostKeyAlgorithms ecdsa-sha2-nistp256,ssh-ed25519,rsa-sha2-512,rsa-sha2-256
#Ciphers aes256-gcm@openssh.com,aes256-ctr
#MACs hmac-sha2-512,hmac-sha2-256
#Banner /etc/issue.net
Once we have created and configured the above files we can run the ansible playbook with:
ansible-playbook -i inventory.ini ansibleplay.yaml -K
Ansible will ask you for the BECOME password, this is the sudo password for the user on the remote machine that Ansible is using.
NOTE: If you change the ssh port in the hardened sshd config file from the current ssh port you are using to connect to the remote server(s), when the server reboots you will loose connection to it. You can reconnect to it either by editing the inventory.ini file and updating the ssh port and re-running the ansible playbook or by simply running ssh -p(new port) username@host_ip.
That’s a wrap!
Walter Miely is a tech entrepreneur and CEO of Phoenix Ignited Tech You can find him on Linkedin. This material is licensed under the CC BY 4.0 License LEGAL DISCLAIMER: The content provided here is provided AS IS, and part of, or the entirety of this content may be incorrect. Please read the entireLegal Disclaimer here.
+ Ave Maria +