diff --git a/harden b/harden index 28cd969..6048157 100755 --- a/harden +++ b/harden @@ -98,7 +98,7 @@ function install_packages { # and store the ones that are indeed absent in another local array. # Then it will install the packages that are missing by invoking the install_packages function. function check_dependencies { - local dependencies=(curl git sudo vim ssh docker docker-compose wget) # Declare dependencies as a local array + local dependencies=(curl git sudo vim ssh docker-ce docker.io docker docker-compose wget fail2ban) # Declare dependencies as a local array #> see what to do with name differences between distros if any <# local missing_dependencies=() # Declare missing_dependencies as a local array for dependency in "${dependencies[@]}"; do # Loop through the dependencies array @@ -182,21 +182,21 @@ function getCorrectKernelSecurityModule { local distro # Declare distro as a local variable distro="$(get_distro)" # Get the distribution name case "$distro" in # Use case to check for the distribution name - "Ubuntu") # If the distribution is Debian - apt install apparmor-profiles -y # Install apparmor + "Ubuntu") # If the distribution is Ubuntu + apt install apparmor-profiles apparmor-utils apparmor-profiles-extra -y # Install apparmor printf "%s" "apparmor" # Output apparmor ;; "Debian GNU/Linux") # If the distribution is Debian - apt install apparmor apparmor-utils auditd + apt install apparmor apparmor-utils auditd python3-apparmor -y # Install apparmor printf "%s" "apparmor" # Output apparmor ;; "CentOS Linux" | "Fedora" | "Red Hat Enterprise Linux Server") # If the distribution is CentOS, Fedora or RHEL - dnf install selinux -y # Install selinux + dnf install selinux container-selinux -y # Install selinux printf "%s" "selinux" # Output selinux ;; "openSUSE Leap") # If the distribution is OpenSUSE - zypper install libapparmor apparmor-profiles apparmor-utils apparmor-parser yast2-apparmor apparmor-docs -y # Install apparmor + zypper install -t pattern apparmor -y # Install apparmor printf "%s" "apparmor" # Output apparmor ;; *) @@ -217,8 +217,7 @@ function firewallInit { sudo ufw default deny incoming # Deny incoming connections sudo ufw allow 22100/tcp # Allow ssh connections on port 22100 sudo ufw enable # Enable the firewall - sudo systemctl enable ufw # Enable the firewall on boot - sudo systemctl start ufw # Start the firewall + sudo systemctl enable --now ufw # Enable and start the firewall on boot ;; firewalld) sudo systemctl enable --now firewalld # Enable the firewall on boot and start it @@ -238,9 +237,12 @@ function kernelSecurityModuleInit { case "$kernelSecurityModule" in apparmor) sudo systemctl enable --now apparmor # Enable the kernel security module on boot and start it + sudo aa-enforce /etc/apparmor.d/* # Enforce all apparmor profiles ;; selinux) sudo systemctl enable --now selinux # Enable the kernel security module on boot and start it + printf "%s" "{\"selinux-enabled\":true}" | sudo tee /etc/docker/daemon.json # Enable selinux in docker + sudo setenforce 1 # Enforce selinux ;; *) printf "%s" "Unsupported kernel security module" @@ -262,17 +264,106 @@ function dockerInit { # No need to pull the docker image as the run command will do it automatically # Run the docker image in the background, # with the restar always option and the name of the docker image - docker run -d --restart always --name "$dockerImage" "$dockerImage" + # The --security-opt=no-new-privileges option will prevent the docker image from gaining new privileges + # The --cap-drop all option will drop all capabilities from the docker image + # The --cap-add NET_ADMIN option will add the NET_ADMIN capability to the docker image + docker run -d --restart always --name "$dockerImage" --security-opt=no-new-privileges --cap-drop all --cap-add NET_ADMIN + "$dockerImage" done } +# The apparmorConfig function will set up and configure apparmor with sane defaults. +function apparmorConfig { + # Create a new apparmor profile for the docker daemon + sudo aa-genprof docker + # Enable the apparmor profile for the docker daemon + sudo aa-enforce docker + # Reload the apparmor profiles + sudo systemctl reload apparmor +} + +# The selinuxConfig function will set up and configure selinux with sane defaults. +function selinuxConfig { + # Set the selinux boolean to allow docker to use the network + sudo setsebool -P docker_connect_any 1 +} + +# This function will create a new apparmor profile for every docker image installed on the system. +function apparmorProfiles { + # Get all the docker images installed on the system and store them in the dockerImages array + local dockerImages=("$(docker images --format "{{.Repository}}")") + # Loop through the dockerImages array + for dockerImage in "${dockerImages[@]}"; do + # Create a new apparmor profile for the docker image + sudo aa-genprof "$dockerImage" + # Enable the apparmor profile for the docker image + sudo aa-enforce "$dockerImage" + done + # Reload the apparmor profiles + sudo systemctl reload apparmor +} + +function configureFail2ban { +FAIL2BAN_LOCAL=$(cat <<'EOF' +[Definition] +logtarget = /var/log/fail2ban/fail2ban.log +allowipv6 = auto +EOF +) +printf "%s" "$FAIL2BAN_LOCAL" | sudo tee /etc/fail2ban/fail2ban.local +FAIL2BAN_SSH_JAIL_LOCAL=$(cat <<'EOF' +[sshd] +enabled = true +filter = sshd +banaction = ufw +backend = systemd +maxretry = 3 +# 3 failed attempts in 600 seconds = 10 minutes +findtime = 1d +bantime = 1d +ignoreip = 127.0.0.1/8 +EOF +) +printf "%s" "$FAIL2BAN_SSH_JAIL_LOCAL" | sudo tee /etc/fail2ban/jail.local/sshd.local +FAIL2BAN_FILTER=$(cat <<'EOF' +[Definition] +failregex = ^.*DROP_.*SRC= DST=.*$ +journalmatch = _TRANSPORT=kernel +EOF +) +printf "%s" "$FAIL2BAN_FILTER" | sudo tee /etc/fail2ban/filter.d/fwdrop.local +HARDEN_FAIL2BAN_SERVICE=$(cat <<'EOF' +[Service] +PrivateDevices=yes +PrivateTmp=yes +ProtectHome=read-only +ProtectSystem=strict +ReadWritePaths=-/var/run/fail2ban +ReadWritePaths=-/var/lib/fail2ban +ReadWritePaths=-/var/log/fail2ban +ReadWritePaths=-/var/spool/postfix/maildrop +ReadWritePaths=/run/xtables.lock +CapabilityBoundingSet=CAP_AUDIT_READ CAP_DAC_READ_SEARCH CAP_NET_ADMIN CAP_NET_RAW +EOF +) +printf "%s" "$HARDEN_FAIL2BAN_SERVICE" | sudo tee /etc/systemd/system/fail2ban.service.d/override.conf +sudo systemctl enable --now fail2ban +printf "%s" "LogLevel VERBOSE" | sudo tee -a /etc/ssh/sshd_config +sudo systemctl restart sshd +} + # The main function will call the check_dependencies function and exit if it fails. # It will also output a message to the user to let them know that the script has finished. function main { check_dependencies || exit 1 # Check dependencies and exit if it fails - harden_ssh || exit 1 # Harden ssh and exit if it fails + hardenSSH || exit 1 # Harden ssh and exit if it fails firewallInit || exit 1 # Initialize the firewall and exit if it fails kernelSecurityModuleInit || exit 1 # Initialize the kernel security module and exit if it fails + configureFail2ban || exit 1 # Initialize fail2ban and exit if it fails + dockerInit || exit 1 # Initialize docker and exit if it fails + apparmorConfig # Configure apparmor + apparmorProfiles # Create apparmor profiles for all docker images + selinuxConfig # Configure selinux # If number of arguments is greater than 0 # Call the dockerInit function with the arguments passed to the script # Else exit with error code 1 @@ -287,64 +378,8 @@ function main { # exit 1 # Exit with error code 1 # fi # } -# -# # The getArgs function will get the arguments passed to the script and store them in an array. -# # It will also check if the arguments are valid and exit if they are not. -# function getArgs { -# local args=() # Declare args as a local array -# while [[ $# -gt 0 ]]; do # Loop through the arguments -# case "$*" in # Use case to check for the arguments -# --help | -h) # If the argument is --help or -h -# printf "%s" "Usage: $SCRIPT_NAME [OPTION]..." -# ;; -# --create-user | -cu) # If the argument is --create-user or -cu -# args+=("$1") # Add the argument to the args array -# shift # Shift the arguments -# case "$*" in -# --username=* | -u=*) # If the argument is --username=* or -u=* -# args+=("$1") # Add the argument to the args array -# shift # Shift the arguments -# ;; -# --password=* | -p=*) # If the argument is --password=* or -p=* -# args+=("$1") # Add the argument to the args array -# shift # Shift the arguments -# ;; -# *) -# printf "%s" "Invalid argument: $1" -# exit 1 -# ;; -# esac -# create_user "${args[@]}" # Call the create_user function with the args array as arguments -# ;; -# --harden-ssh | -hs) # If the argument is --harden-ssh or -hs -# args+=("$1") # Add the argument to the args array -# shift # Shift the arguments -# harden_ssh "${args[@]}" # Call the harden_ssh function with the args array as arguments -# ;; -# esac -# done -# printf "%s" "${args[@]}" # Output the args array -# } -# -# # The createUser function will create a new user with the username and password passed as arguments. -# function createUser { -# # Declare username as a local variable and assign it the first argument passed to the function -# local username="$1" -# # Declare password as a local variable and assign it the second argument passed to the function -# local password="$2" -# # Check if the user already exists and exit if they do -# if id -u "$username" &> /dev/null; then -# printf "%s" "User already exists" -# exit 1 -# fi -# # Create the user and add them to the sudo group -# useradd -m -G sudo "$username" -# # Set the user's password using printf to avoid the password being echoed to the terminal -# printf "%s" "$username:$password" | chpasswd -# } # Call the main function main "$@" -# am_i_root exit 0 # The right and proper way to exit a script