Our server has root access with a password.
ssh root@x.x.x.x
It’s default, it’s how it’s been provisioned.
It’s not good.
Fortifying The Remote Access​
First of all, the root user is a dangerous user.​
Anyone accessing our VPS with root user has unrestricted access, can create or delete anything on the filesystem, start or stop any service. With not necessarily bad intentions, you could break stuff accidentally.
Root user is so obvious bots are attacking constantly (we had around 1752 access attempts in 12h from starting the server).
Secondly, password access is a vulnerability.​
Scripts can rapidly re-try various passwords until they find it. Passwords can be guessed if you are not using random characters. Passwords can be sniffed with keyloggers.
Thirdly, the port itself is a danger.​
Although there are over 120k ports in your server, only three of them account for 65% of all attacks. And one of them is port 22, the ssh port.
That’s a lot of topics, so I’ll break it down into multiple posts.
How To Turn My SSH Access Into a Fortress?​
Let’s say you have secret documents from ESA, NASA and Pentagon. You have to protect it from Russian hackers.
So you go to contabo.com and buy a VPS for 4,50€. After 5 minutes it’s up and ready. You SSH inside with your root access and place the secret file there.
esa_nasa_pentagon_db_dump.sql
Close The Doors​
For all the reasons mentioned above, you need to close the doors. We are using ansible (https://docs.ansible.com/) to provision our servers.
We have a playbook called close_the_doors.yaml and it looks like this:
- hosts: all
become: true
vars:
users:
- name: gg
email: gg@gransoftware.de
public_key: "
,,__ oink
c'' )? oink
''''
"
- name: do
email: do@gransoftware.de
public_key: "wink wink :)"
tasks:
- name: Create users
ansible.builtin.user:
name: "{{ item.name }}"
comment: "{{ item.email }}"
group: sudo
append: true
loop: "{{ users }}"
- name: Ensure .ssh directory exists
ansible.builtin.file:
path: "/home/{{ item.name }}/.ssh"
state: directory
mode: '0700'
owner: "{{ item.name }}"
loop: "{{ users }}"
- name: Copy public keys
ansible.builtin.copy:
content: "{{ item.public_key }}"
dest: "/home/{{ item.name }}/.ssh/authorized_keys"
mode: '0600'
owner: "{{ item.name }}"
loop: "{{ users }}"
- name: Disable root login
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PermitRootLogin'
line: 'PermitRootLogin no'
- name: Allow no password for sudo group in sudoers
ansible.builtin.lineinfile:
path: /etc/sudoers
line: '%sudo ALL=(ALL:ALL) NOPASSWD:ALL'
validate: 'visudo -cf %s'
- name: Disable PasswordAuthentication
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PasswordAuthentication'
line: 'PasswordAuthentication no'
- name: Enable PubkeyAuthentication
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#PubkeyAuthentication'
line: 'PubkeyAuthentication yes'
- name: Change default SSH port
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?Port'
line: 'Port 2222'
notify: Restart sshd
handlers:
- name: Restart sshd
ansible.builtin.service:
name: sshd
state: restarted
Disable root Access​
To close the doors we need to disallow root login.
Most likely, the default sshd settings of your server (/etc/ssh/sshd_config
) look like this:
...
#LoginGraceTime 2m
PermitRootLogin yes # <--- this line allows to use ssh root@0.0.0.0
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10
...
Ansible has this lineinfile module that allows us to:
- make sure that a defined line exists in a file,
- or find lines with regex and replace them with the defined line.
- name: Disable root login
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PermitRootLogin'
line: 'PermitRootLogin no'
Once it's done, no amount of ssh root@x.x.x.x
will let them bots in.
Disable Password Authentication​
Then we disable passwords for all users, forcing them to use keys. The default sshd_config may look like this:
...
#PubkeyAuthentication yes
...
PasswordAuthentication yes
...
In order to make the remote access to VPS more secure, we'll disable password authentication and force users to use keys.
- name: Disable PasswordAuthentication
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^PasswordAuthentication'
line: 'PasswordAuthentication no'
- name: Enable PubkeyAuthentication
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#PubkeyAuthentication'
line: 'PubkeyAuthentication yes'
From now on, no one can SSH into the VPS using root user or passwords.
All changes in the /etc/ssh/sshd_config
file require restart of the SSH Daemon. More about this in a bit.
Create Custom Users​
Effectively, you would lose access to your esa_nasa_pentagon_db_dump
file. To maintain access to the VPS, we’ll create users in the same playbook.
Use vars to create a list of users that you want to give access to. Those are defined by a name, email and a public key. I won’t explain how to generate keys.
vars:
users:
- name: John Doe
email: jd@gransoftware.de
public_key: "RSA or whatever you use"
- name: Jane Doe
email: jd2@gransoftware.de
public_key: "RSA or whatever you use"
Storing public keys in git is fine, so we’ll simply hardcode them. And by using another handy module from Ansible we can easily create users on the machine (you don’t have to loop if you need only one user).
It’s important to add them to the sudo group so they can operate the VPS. If your users are in multiple groups, then set the flag “append” to true.
Now that users are created, it doesn't mean yet that they can login via SSH. To allow this, we have to create the ~/.ssh/authorized_keys
files for each user. Sooo, another module and another loop.
- name: Ensure .ssh directory exists
ansible.builtin.file:
path: "/home/{{ item.name }}/.ssh"
state: directory
mode: '0700'
owner: "{{ item.name }}"
loop: "{{ users }}"
- name: Copy public keys
ansible.builtin.copy:
content: "{{ item.public_key }}"
dest: "/home/{{ item.name }}/.ssh/authorized_keys"
mode: '0600'
owner: "{{ item.name }}"
loop: "{{ users }}"
Important! Make sure that the directory has a mode 700 and that the user owns the directory.
Once the directory is created, you use the []copy module](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/copy_module.html) to create a file (confusing name, aint it?) with the public key.
Change Default SSH Port​
The best way to win a fight is to avoid it. And since the majority of bots and scripts attack port 22, we’ll simply change it and mitigate the huge swaths of potential attacks by doing so.
- name: Change default SSH port
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?Port'
line: 'Port 2222'
notify: Restart sshd # <-- this will trigger a handler. See below 👇
I know, the /etc/ssh/sshd_config
file got really pulverized. But it’s necessary to fortify remote access to the VPS.
For the changes in this file to take effect, you need to restart the sshd. Ansible lets you do that by defining a handler
and by using notify
to trigger it by name after the task is done.
handlers:
- name: Restart sshd
ansible.builtin.service:
name: sshd
state: restarted
Now fire it up
ansible-playbook close_the_doors.yaml
And sleep well, knowing that the KGB won’t steal your Western secrets.
That's All Folks​
That’s it for this week’s Weekly Sheetty Scoop. Thanks for reading!
But don’t just read. Try it out now. It only takes 1 minute: sheetty.com And with a 90 day trial, you can’t go wrong.