DevOps Series Hardening of Parabola

0
4357

Every computer offers security measures to isolate it from outside attacks. In this 21st article in the DevOps series, we will learn how to harden, automate and verify a Parabola GNU/Linux-libre system using Ansible.

Parabola GNU/Linux-libre is a free software GNU/Linux distribution based on packages from Arch Linux, but without the binary blobs. It respects the freedom of users and is endorsed by the Free Software Foundation. It is extremely lightweight and simple in design. The distribution follows a rolling release, but also provides installation media in the ISO format. The Pacman package manager is used, and the software packages are available for i686, x86_64 and armv7h architectures. The Linux-libre kernel is used instead of the generic Linux kernel. The official IRC channel is #parabola on irc.freenode.net, and the website of the project is https://www.parabola.nu/.

Setup

A Parabola GNU/Linux-libre (x86_64) 2018.06.02 ISO is used to set up a guest virtual machine (VM) with KVM/QEMU. The host system is also a Parabola GNU/Linux-libre x86_64 system, and Ansible is installed using the distribution package manager. The version of Ansible used is 2.6.0, as is indicated below:

$ ansible --version

ansible 2.6.0

config file = /etc/ansible/ansible.cfg

configured module search path = [‘/home/guest/.ansible/plugins/modules’, ‘/usr/share/ansible/plugins/modules’]

ansible python module location = /usr/lib/python3.6/site-packages/ansible

executable location = /usr/bin/ansible

python version = 3.6.5 (default, May 11 2018, 04:00:52) [GCC 8.1.0]

The Ansible inventory, playbook and configuration files are created on the host system as follows:

ansible/inventory/kvm/

/playbooks/configuration/

/files/

The inventory/kvm/inventory file contains the following:

parabola ansible_host=192.168.122.128 ansible_connection=ssh ansible_user=parabola ansible_password=password

The sudo package needs to be installed in the guest Parabola VM. A parabola user is created in the guest VM, and sudo access is provided for this user with the visudo command. You also need to ensure that the ssh daemon is started in the guest VM using the following command:

$ sudo systemctl start sshd

You should add an entry in the /etc/hosts file on the host system for the Parabola guest VM as indicated below:

192.168.122.128 parabola

You can now test connectivity from Ansible to the Parabola guest VM using the following Ansible commands:

$ ansible -i inventory/kvm/inventory parabola -m ping

parabola | SUCCESS => {

“changed”: false,

“ping”: “pong”

}

Passwords

The pwgen utility is useful to generate passwords. It has a number of options that you can use to generate strong passwords with a combination of numerals, symbols and characters. The Ansible playbook to install pwgen is given below for reference:

- name: Harden Parabola

hosts: parabola

become: yes

become_method: sudo

gather_facts: yes

tags: [security]

tasks:

- name: Install pwgen

package:

name: pwgen

state: latest

/etc file permissions

/etc/shadow and /etc/passwd should have restricted file permissions. The /etc/shadow file should be owned by the root user and should not be accessible by any other user. The /etc/passwd file should similarly be owned by the root user and have restricted permissions. The following part of the Ansible playbook checks the required permissions of these two files:

- name: File permissions for /etc/shadow

stat:

path: /etc/shadow

register: shadow

- assert:

that:

- “shadow.stat.pw_name == ‘root’”

- “shadow.stat.readable == true”

- “shadow.stat.rgrp == false”

- “shadow.stat.roth == false”

- “shadow.stat.writeable == true”

- “shadow.stat.wgrp == false”

- “shadow.stat.woth == false”

- “shadow.stat.xgrp == false”

- “shadow.stat.xoth == false”

- “shadow.stat.xusr == false”

- name: File permissions for /etc/passwd

stat:

path: /etc/passwd

register: passwd

- assert:

that:

- “passwd.stat.pw_name == ‘root’”

- “passwd.stat.readable == true”

- “passwd.stat.rgrp == true”

- “passwd.stat.roth == true”

- “passwd.stat.writeable == true”

- “passwd.stat.wgrp == false”

- “passwd.stat.woth == false”

- “passwd.stat.xgrp == false”

- “passwd.stat.xoth == false”

- “passwd.stat.xusr == false”

Enforcing strong passwords

The password policy can be defined in the /etc/pam.d/passwd file. For example, the following constraints are enforced when creating new passwords using the Ansible playbook given below:

  • In case of error, prompt twice for password.
  • The minimum length of the password should be ten characters.
  • At least six characters should be different from the old password.
  • There should be at least one digit.
  • There should be at least one upper case character.
  • There should be at least one other character.
  • There should be at least one lower case character.
- name: Update /etc/pam.d/passwd

lineinfile:

path: /etc/pam.d/passwd

insertbefore: ‘^password.*required.*pam_unix.so’

line: ‘password required pam_cracklib.so retry=2 minlen=10 difok=6 dcredit=-1 ucredit=-1 ocredit=-1 lcredit=-1’

Disk encryption

It is recommended that your disk be encrypted, and you use a strong passphrase that can be provided as input when booting the system. The Parabola wiki has useful documentation on disk encryption at https://wiki.parabola.nu/Disk_encryption. You can verify that the disk is encrypted using the following Ansible code snippet:

- name: Check disk is encrypted

shell: lsblk /dev/sda | grep / | grep crypt

register: encrypted

- assert:

that:

- “encrypted.rc == 0”

Mount options

The file systems that are mounted should have restrictive options, and use only what is required. The following commands check the nosuid, nodev and noexec options for the /tmp and /dev/shm directories:

- name: Check /tmp

shell: mount | grep /tmp | grep -E ‘nosuid.*nodev’

register: tmp

- assert:

that:

- “tmp.rc == 0”

- name: Check /dev/shm

shell: mount | grep /dev/shm | grep -E ‘nosuid.*nodev.*noexec’

register: tmp

- assert:

that:

- “tmp.rc == 0”

File access permissions

Access to /boot and /etc/iptables should be restricted to only users with privileged access. The following playbook snippet updates the 0700 permission for both the /boot and /etc/iptables directories.

- name: Change file mode for /boot, /etc/iptables

file:

path: “{{ item }}”

mode: 0700

with_items:

- /boot

-/etc/iptables

Lock out user

You can lock a user account after a certain number of failed logins by updating the /etc/pam.d/system-login file as shown below:

- name: Update /etc/pam.d/system-login

lineinfile:

path: /etc/pam.d/system-login

regexp: ‘^auth.*required.*pam_tally.so’

line: ‘auth required pam_tally.so deny=2 unlock_time=600 onerr=succeed file=/var/log/faillog’

The account can only be unlocked after a certain time (unlock_time) or by the root user.

Limit the processes

In order to prevent Denial of Service (DoS) attacks, it is a good practice to limit the number of processes that a user can run. This policy can be updated in the /etc/security/limits.conf file as shown below:

- name: Update /etc/security/limits.conf

lineinfile:

path: /etc/security/limits.conf

insertbefore: ‘# End of file’

line: “{{ item }}”

with_items:

- ‘* soft nproc 100’

- ‘* hard nproc 200’

Restrict root login

You can restrict root login by allowing only users who belong to the wheel group to log in to root using su. The same needs to be updated in the /etc/pam.d/su and /etc/pam.d/su-l files as shown below:

- name: Update /etc/pam.d/su

lineinfile:

path: /etc/pam.d/su

regexp: ‘^#auth required pam_wheel.so use_uid’

line: ‘auth required pam_wheel.so use_uid’

- name: Update /etc/pam.d/su-l

lineinfile:

path: /etc/pam.d/su-l

regexp: ‘^#auth required pam_wheel.so use_uid’

line: ‘auth required pam_wheel.so use_uid’

Restrict access to kernel log

The output of dmesg can provide useful information to attackers and hence one needs to restrict access to it. The ansible/files/ folder has both 50-dmesg-restrict.conf and 50-kptr-restrict.conf files, which need to be copied to etc/sysctl.d for the above functionality.

$ cat files/50-kptr-restrict.conf

kernel.kptr_restrict = 1

$ cat files/50-dmesg-restrict.conf

kernel.dmesg_restrict = 1

- name: Restrict access to kernel logs

copy:

src: ../../files/50-dmesg-restrict.conf

dest: /etc/sysctl.d/50-dmesg-restrict.conf

owner: root

group: root

mode: 0644

- name: Restrict access to kernel pointers in procfs

copy:

src: ../../files/50-kptr-restrict.conf

dest: /etc/sysctl.d/50-kptr-restrict.conf

owner: root

group: root

mode: 0644

Disable root login

The root SSH login should be disabled, by default. A remote user should only be able to log in to the system, and sudo if they have been given permissions. The same can be accomplished by setting PermitRootlogin no in /etc/ssh/sshd_config as shown below:

- name: Disable root login

lineinfile:

path: /etc/ssh/sshd_config

regexp: ‘^#PermitRootLogin’

line: ‘PermitRootLogin no’

The console logins for root can also be disabled by commenting them out in /etc/securetty file.

- name: Disable root console logins

replace:

path: /etc/securetty

regexp: ‘^(?!#)(.*)’

replace: ‘# \1’

Execution

The entire playbook is provided below for reference:

---

- name: Harden Parabola

hosts: parabola

become: yes

become_method: sudo

gather_facts: yes

tags: [security]

tasks:

- name: Install pwgen

package:

name: pwgen

state: latest

- name: File permissions for /etc/shadow

stat:

path: /etc/shadow

register: shadow

- assert:

that:

- “shadow.stat.pw_name == ‘root’”

- “shadow.stat.readable == true”

- “shadow.stat.rgrp == false”

- “shadow.stat.roth == false”

- “shadow.stat.writeable == true”

- “shadow.stat.wgrp == false”

- “shadow.stat.woth == false”

- “shadow.stat.xgrp == false”

- “shadow.stat.xoth == false”

- “shadow.stat.xusr == false”

- name: File permissions for /etc/passwd

stat:

path: /etc/passwd

register: passwd

- assert:

that:

- “passwd.stat.pw_name == ‘root’”

- “passwd.stat.readable == true”

- “passwd.stat.rgrp == true”

- “passwd.stat.roth == true”

- “passwd.stat.writeable == true”

- “passwd.stat.wgrp == false”

- “passwd.stat.woth == false”

- “passwd.stat.xgrp == false”

- “passwd.stat.xoth == false”

- “passwd.stat.xusr == false”

- name: Update /etc/pam.d/passwd

lineinfile:

path: /etc/pam.d/passwd

insertbefore: ‘^password.*required.*pam_unix.so’

line: ‘password required pam_cracklib.so retry=2 minlen=10 difok=6 dcredit=-1 ucredit=-1 ocredit=-1 lcredit=-1’

- name: Check disk is encrypted

shell: lsblk /dev/sda | grep / | grep crypt

register: encrypted

- assert:

that:

- “encrypted.rc == 0”

- name: Check /tmp

shell: mount | grep /tmp | grep -E ‘nosuid.*nodev’

register: tmp

- assert:

that:

- “tmp.rc == 0”

- name: Check /dev/shm

shell: mount | grep /dev/shm | grep -E ‘nosuid.*nodev.*noexec’

register: tmp

- assert:

that:

- “tmp.rc == 0”

- name: Change file mode for /boot, /etc/iptables

file:

path: “{{ item }}”

mode: 0700

with_items:

- /boot

- /etc/iptables

- name: Update /etc/pam.d/system-login

lineinfile:

path: /etc/pam.d/system-login

regexp: ‘^auth.*required.*pam_tally.so’

line: ‘auth required pam_tally.so deny=2 unlock_time=600 onerr=succeed file=/var/log/faillog’

- name: Update /etc/security/limits.conf

lineinfile:

path: /etc/security/limits.conf

insertbefore: ‘# End of file’

line: “{{ item }}”

with_items:

- ‘* soft nproc 100’

- ‘* hard nproc 200’

- name: Update /etc/pam.d/su

lineinfile:

path: /etc/pam.d/su

regexp: ‘^#auth required pam_wheel.so use_uid’

line: ‘auth required pam_wheel.so use_uid’

- name: Update /etc/pam.d/su-l

lineinfile:

path: /etc/pam.d/su-l

regexp: ‘^#auth required pam_wheel.so use_uid’

line: ‘auth required pam_wheel.so use_uid’

- name: Restrict access to kernel logs

copy:

src: ../../files/50-dmesg-restrict.conf

dest: /etc/sysctl.d/50-dmesg-restrict.conf

owner: root

group: root

mode: 0644

- name: Restrict access to kernel pointers in procfs

copy:

src: ../../files/50-kptr-restrict.conf

dest: /etc/sysctl.d/50-kptr-restrict.conf

owner: root

group: root

mode: 0644

- name: Disable root login

lineinfile:

path: /etc/ssh/sshd_config

regexp: ‘^#PermitRootLogin’

line: ‘PermitRootLogin no’

- name: Disable root console logins

replace:

path: /etc/securetty

regexp: ‘^(?!#)(.*)’

replace: ‘# \1’

You can invoke the above playbook using:

$ ansible-playbook -i inventory/kvm/inventory playbooks/configuration/parabola.yml -vv -K

The -vv represents the verbosity in the Ansible output. You can use up to four v’s for a more detailed output. The -K option prompts for the sudo password for the guest Parabola user account.

You are encouraged to read the security guide and best practices for Parabola GNU/Linux-libre available at https://wiki.parabola.nu/Security for more information.

LEAVE A REPLY

Please enter your comment!
Please enter your name here