Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

🏠 Back to Blog

System Hardening (15%)

Principle of Least Privilege

  • Ensure that people or bots only have access to what is needed, and nothing else.

Limit access to nodes

Managing Local Users and Groups

  • Commands to be aware of: id who last groups useradd userdel usermod groupdel
  • Files to be aware of: /etc/passwd /etc/shadow /etc/group
  • Disable logins for users and set their login shell to /bin/nologin
  • Remove users from groups they do not need to belong to

Securing SSH

  • Set the following in sshd_config
PermitRootLogin no
PasswordAuthentication no

Using sudo

  • The /etc/sudoers file controls and configures the behavior of the sudo command. Each entry follows a structured syntax. Below is a breakdown of the fields and their meanings:
# Example Lines
# ----------------------------------
# User/Group       Host=Command(s)
admin             ALL=(ALL) NOPASSWD: ALL
%developers       ALL=(ALL) ALL
john              ALL=(ALL:ALL) /usr/bin/apt-get

# Field Breakdown

admin             ALL=(ALL) NOPASSWD: ALL
|                 |   |       |         |
|                 |   |       |         +---> Command(s): Commands the user/group can execute.
|                 |   |       +------------> Options: Modifiers like `NOPASSWD` (no password required).
|                 |   +--------------------> Runas: User/Group the command can be run as.
|                 +------------------------> Host: On which machine this rule applies (`ALL` for any).
+-----------------------------------------> User/Group: The user or group this rule applies to.

# Examples Explained

1. Allow `admin` to execute any command without a password:
   admin ALL=(ALL) NOPASSWD: ALL

Remove Packages Packages

  • This one is self-explanatory. Don’t have unnecessary software installed on your nodes.

Restrict Kernel Modules

  • Kernel modules are ways of extending the kernel to enable it to understand new hardware. They are like device drivers.
  • modprobe allows you to load a kernel module
  • lsmod allows you to view all loaded modules
  • You can blacklist modules by adding a new entry to /etc/modprobe.d/blacklist.conf
    • The entry should be in the format blacklist <module name>
    • Example: echo "blacklist sctp" >> /etc/modprobe.d/blacklist.conf
  • You may need to reboot the system after disabling kernel modules or blacklisting them

Disable Open Ports

  • Use netstat -tunlp or to list listening ports on a system
  • Stop the service associated with the open port or disable access with a firewall
    • Common firewalls you can use are iptables or ufw
      • Run ufw status to list the current status of the UFW firewall
      • Allow all traffic outbound: ufw default allow outgoing
      • Deny all incoming: ufw default deny incoming
      • Allow SSH from 172.16.154.24: ufw allow from 172.16.154.24 to any port 22 proto tcp

Tracing Syscalls

  • There are several ways to trace syscalls in Linux.

strace

  • strace is included with most Linux distributions.
  • To use strace, simply add it before the binary that you are running:
    strace touch /tmp/test
    
  • You can also attach strace to a running process like this:
    strace -p <PID>
    

AquaSec Tracee

  • tracee is an open source tool created by AquaSec
  • Uses eBPF (extended Berkely Packet Filter) to trace syscalls on a system. eBPF runs programs directly within the kernel space without loading any kernel modules. As a result, tools that use eBPF are more efficient and typically use less resources.
  • tracee can be run by using the binaries or as a container

Restricting Access to syscalls with seccomp

  • seccomp can be used to restrict a process’ access to syscalls. It allows access to the most commonly used syscalls, while restricting access to syscalls that can be considered dangerous.

  • To see if seccomp is enabled:

    grep -i seccomp /boot/config-$(uname -r)
    
  • seccomp can operate in 1 of 3 modes:

    • mode 0: disabled
    • mode 1: strict (blocks nearly all syscalls, except for 4)
    • mode 2: selectively filters syscalls
    • To see which mode the process is currently running in: grep -i seccomp /proc/1/status where ‘1’ is the PID of the process
  • seccomp profiles

    • Kubernetes provides a default seccomp profile, that can be either restrictive or permissive, depending on your configuration
    • You can create custom profiles to fine-tune seccomp and which syscalls it blocks or allows within a containers
    • Example seccomp profile for mode 1:
      {
        "defaultAction": "SCMP_ACT_ERRNO",
        "archMap": [
          { "architecture": "SCMP_ARCH_X86_64", "subArchitectures": [] }
        ],
        "syscalls": [
          {
            "names": ["read", "write", "exit", "sigreturn"],
            "action": "SCMP_ACT_ALLOW"
          }
        ]
      }
      
  • To apply a seccomp profile to a pod:

    apiVersion: v1
    kind: Pod
    metadata:
      name: audit-pod
      labels:
        app: audit-pod
    spec:
      securityContext:
        seccompProfile:
          type: Localhost
          localhostProfile: profiles/audit.json #this path is relative to default seccomp profile location (/var/lib/kubelet/seccomp)
      containers:
      - name: test-container
        image: hashicorp/http-echo:1.0
        args:
        - "-text=just made some syscalls!"
        securityContext:
          allowPrivilegeEscalation: false
    

Restrict access to file systems

AppArmor

  • AppArmor can be used to limit a containers’ access to resources on the host. Why do we need apparmor if we have traditional discretionary access controls (file system permissions, etc.)? With discretionary access control, a running process will inherit the permissions of the user who started it. Likely more permissions than the process needs. AppArmor is a mandatory access control implementation that allows one to implement fine-grained controls over what a process can access or do on a system.

  • AppArmor runs as a daemon on Linux systems. You can check it’s status using systemctl: systemctl status apparmor

    • If apparmor-utils is installed, you can also use aa-status
  • To use AppArmor, the kernel module must also be loaded. The check status: cat /sys/module/apparmor/parameters/enabled Y = loaded

  • AppArmor profiles define what a process can and cannot do and are stored in /etc/apparmor.d/. Profiles need to be copied to every worker node and loaded.

  • Every profile needs to be loaded into AppArmor before it can take effect

    • To view loaded profiles, run aa-status
  • To load a profile: apparmor_parser -r -W /path/to/profile

    • If apparmor-utils is installed, you can also use aa-enforce to load a profile
  • Profiles are loaded in ‘enforce’ mode by default. To change the mode to ‘complain’: apparmor_parser -C /path/to/profile

    • If apparmor-utils is installed, you can also use aa-complain to change the mode
  • To view loaded apparmor profiles:

      kubeadmin@kube-controlplane:~$ sudo cat /sys/kernel/security/apparmor/profiles
      cri-containerd.apparmor.d (enforce)
      wpcom (unconfined)
      wike (unconfined)
      vpnns (unconfined)
      vivaldi-bin (unconfined)
      virtiofsd (unconfined)
      rsyslogd (enforce)
      vdens (unconfined)
      uwsgi-core (unconfined)
      /usr/sbin/chronyd (enforce)
      /usr/lib/snapd/snap-confine (enforce)
      /usr/lib/snapd/snap-confine//mount-namespace-capture-helper (enforce)
      tcpdump (enforce)
      man_groff (enforce)
      man_filter (enforce)
      ....
    

    or:

      root@controlplane00:/etc/apparmor.d# aa-status
      apparmor module is loaded.
      33 profiles are loaded.
      12 profiles are in enforce mode.
         /home/rtn/tools/test.sh
         /usr/bin/man
         /usr/lib/NetworkManager/nm-dhcp-client.action
         /usr/lib/NetworkManager/nm-dhcp-helper
         /usr/lib/connman/scripts/dhclient-script
         /usr/sbin/chronyd
         /{,usr/}sbin/dhclient
         lsb_release
         man_filter
         man_groff
         nvidia_modprobe
         nvidia_modprobe//kmod
      21 profiles are in complain mode.
         avahi-daemon
         dnsmasq
         dnsmasq//libvirt_leaseshelper
         identd
         klogd
         mdnsd
         nmbd
         nscd
         php-fpm
         ping
         samba-bgqd
         samba-dcerpcd
         samba-rpcd
         samba-rpcd-classic
         samba-rpcd-spoolss
         smbd
         smbldap-useradd
         smbldap-useradd///etc/init.d/nscd
         syslog-ng
         syslogd
         traceroute
      0 profiles are in kill mode.
      0 profiles are in unconfined mode.
      4 processes have profiles defined.
      2 processes are in enforce mode.
         /usr/sbin/chronyd (704)
         /usr/sbin/chronyd (708)
      2 processes are in complain mode.
         /usr/sbin/avahi-daemon (587) avahi-daemon
         /usr/sbin/avahi-daemon (613) avahi-daemon
      0 processes are unconfined but have a profile defined.
      0 processes are in mixed mode.
      0 processes are in kill mode.
    
  • AppArmor defines profile modes that determine how the profile behaves:

    • Modes:
      • Enforced: Action is taken and the application is allowed/blocked from performing defined actions. Events are logged in syslog.
      • Complain: Events are logged but no action is taken
      • Unconfined: application can perform any task and no event is logged
  • AppArmor Tools

    • Can be used to generate apparmor profiles
    • To install: apt install -y apparmor-utils
    • Run aa-genprof to generate a profile: aa-genprof ./my-application
  • Before applying an AppArmor profile to a pod, you must ensure the container runtime supports AppArmor. You must also ensure AppArmor is installed on the worker node and that all necessary profiles are loaded.

  • To apply an AppArmor profile to a pod, you must add the following security profile (K8s 1.30+):

    securityContext:
      appArmorProfile:
        type: <profile_type>
        localhostProfile: <profile_name>
    
    • <profile_type> can be one of 3 values: Unconfined, RuntimeDefault, or Localhost
      • Unconfined means the container is not restricted by AppArmor
      • RuntimeDefault means the container will use the default AppArmor profile
      • Localhost means the container will use a custom profile

Deep Dive into AppArmor Profiles

AppArmor profiles define security rules for specific applications, specifying what they can and cannot do. These profiles reside in /etc/apparmor.d/ and are loaded into the kernel to enforce security policies.

  • Each profile follows these structure:
profile <profile_name> <executable_path> {
    <rules>
}
  • Example profile, a profile for nano:
profile nano /usr/bin/nano {
    # Allow reading any file
    file,

    # Deny writing to system directories
    deny /etc/* rw,
}
  • Types of AppArmor rules:
    • File Access Rules:
          /home/user/data.txt r       # Read-only access
          /etc/passwd rw              # Read & write access
          /tmp/ rw                    # Full access to /tmp
      
    • Network Access Rules:
          network inet tcp,           # Allow TCP connections
          network inet udp,           # Allow UDP connections
          network inet dgram,         # Allow datagram connections
      
    • Capability Rules:
    deny capability sys_admin,       # Deny sys_admin capability
    deny capability sys_ptrace,      # Deny sys_ptrace capability
    

Linux Capabilities in Pods

  • For the purpose of performing permission checks, traditional UNIX implementations distinguish two categories of processes: privileged processes (whose effective user ID is 0, referred to as superuser or root), and unprivileged processes (whose effective UID is nonzero). Privileged processes bypass all kernel permission checks, while unprivileged processes are subject to full permission checking based on the process’s credentials (usually: effective UID, effective GID, and supplementary group list).
  • Starting with Linux 2.2, Linux divides the privileges traditionally associated with superuser into distinct units, known as capabilities, which can be independently enabled and disabled. Capabilities are a per-thread attribute.
  • Capabilities control what a process can do
  • Some common capabilities
    • CAP_SYS_ADMIN
    • CAP_NET_ADMIN
    • CAP_NET_RAW
  • To view the capabilities of a process:
    • getcap - Check the capabilities of a binary - getcap <path to bin>
    • getpcaps - Check the capabilities of a process - getpcaps <pid>