502 lines
25 KiB
Bash
Executable File
502 lines
25 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Ensure bash path is found
|
|
|
|
# shellcheck source=/etc/os-release
|
|
# use shellcheck to declare which file to source
|
|
|
|
# Using set to make the script safer
|
|
set -e # Exit on error
|
|
set -u # Exit on undefined variable
|
|
set -x # Print commands for debugging
|
|
set -a # Export all variables
|
|
set -C # Disable overwriting of files
|
|
set -o pipefail # Exit on pipe error
|
|
# set -euxaCo pipefail # All options
|
|
|
|
# Use the built-in trap command to catch error lines and signal numbers
|
|
trap 'printf "Error on line %d with signal %s" "$LINENO" "$?"' ERR # Exit on error
|
|
# Also use trap to catch interrupt signals and exit cleanly with a message to the user and a return code
|
|
trap 'printf "Interrupted on line %d with signal %s" "$LINENO" "$?"' INT SIGHUP SIGINT SIGTERM
|
|
|
|
# Get script name using parameter expansion to not spawn a new subprocess
|
|
SCRIPT_NAME="${0##*/}"
|
|
|
|
# We will be using printf instead of echo because it is more standardised.
|
|
# Also we will be using the test command's functionality as
|
|
# [[ because like this it constitutes a keyword and not a command.
|
|
# Functions will be defined as function "name" {body}" to make them
|
|
# more clear and () will not be used since using the keyword function renders them redundant
|
|
|
|
# We are taking for granted that the os-release file is in /etc as
|
|
# it has become a standard in most GNU/Linux distributions using systemd.
|
|
# We'll be using the -e flag just in case it is actually a symlink to another location.
|
|
# The get_distro function will use short if statements to check for the os-release file existence and readability.
|
|
# Then it will source it and output the distribution's name or exit in case of failure of either case.
|
|
function get_distro {
|
|
if [[ -e /etc/os-release ]] && [[ -r /etc/os-release ]]; then # Check if file exists and is readable
|
|
. /etc/os-release # Source the file
|
|
printf "%s" "$NAME" # Output the distribution's name
|
|
else # If the file does not exist or is not readable
|
|
printf "%s" "File os-release not found or not readable" # Output error message
|
|
exit 1 # Exit with error code 1
|
|
fi
|
|
}
|
|
|
|
# The get_package_manager function will take the output of the get_distro function and determine
|
|
# which is the package manager used for the most popular server distros and exit if it is not found.
|
|
function get_package_manager {
|
|
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" | "Debian GNU/Linux") # If the distribution is Ubuntu or Debian
|
|
printf "%s" "apt" # Output apt
|
|
;;
|
|
"CentOS Linux" | "Fedora" | "Red Hat Enterprise Linux Server") # If the distribution is CentOS, Fedora or RHEL
|
|
printf "%s" "dnf" # Output dnf
|
|
;;
|
|
|
|
"openSUSE Leap") # If the distribution is OpenSUSE
|
|
printf "%s" "zypper" # Output zypper
|
|
;;
|
|
*)
|
|
# If the distribution is none of the above, output unsupported distribution
|
|
# and exit with error code 1
|
|
printf "%s" "Unsupported distribution"
|
|
exit 1 # Exit with error code 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# The install_packages function will take the output of the get_package_manager function and install any
|
|
# package passed as an argument to it. It will also check if the package manager is known and exit if it is not.
|
|
function install_packages {
|
|
local package_manager # Declare package_manager as a local variable
|
|
package_manager="$(get_package_manager)" # Get the package manager
|
|
case "$package_manager" in # Use case to check for the package manager
|
|
"apt") # If the package manager is apt
|
|
printf "%s" 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selections
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
export NEEDRESTART_MODE=a
|
|
export DEBIAN_PRIORITY=critical
|
|
# Running sudo with -E will preserve the environment variables set in the script
|
|
sudo -E apt update -y && sudo apt upgrade -y # Update the package list and upgrade the packages
|
|
sudo -E apt install -y "$@" # Install the packages passed as arguments
|
|
;;
|
|
"dnf") # If the package manager is dnf
|
|
sudo dnf upgrade -y # Update the package list
|
|
sudo dnf install -y "$@" # Install the packages passed as arguments
|
|
;;
|
|
"zypper") # If the package manager is zypper
|
|
sudo zypper update -y # Update the package list
|
|
sudo zypper install -y "$@" # Install the packages passed as arguments
|
|
;;
|
|
*)
|
|
# If the package manager is not one of the above, output unsupported package manager
|
|
# and exit with error code 1
|
|
printf "%s" "Unsupported package manager"
|
|
exit 1 # Exit with error code 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# The check_dependencies function will check if the dependencies defined in a local array are not installed
|
|
# 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 { # systemd-container is for machinectl
|
|
local dependencies=(fuse-overlayfs dbus-user-session uidmap slirp4netns systemd-container at htop curl git sudo vim ssh 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
|
|
# If the dependency is not installed, add it to the missing_dependencies array
|
|
! command -v "$dependency" &> /dev/null && missing_dependencies+=("$dependency")
|
|
done
|
|
# If the missing_dependencies array is not empty, install the packages
|
|
[[ ${#missing_dependencies[@]} -ne 0 ]] && install_packages "${missing_dependencies[@]}"
|
|
}
|
|
|
|
# Global array of the service names to be restarted
|
|
services=()
|
|
|
|
# The hardenSSH function will use sed to modify the sshd_config file to have the following settings:
|
|
# - Allow ssh access to users in the sudo group only
|
|
# - Change the port to 22100 if it is available
|
|
# - Configure idle timeout to 5 minutes
|
|
# - Limit the number of authentication attempts to 3
|
|
# - Disable root login
|
|
# - Disable empty passwords
|
|
# - Disable ssh protocol 1
|
|
# - Disable password authentication and only allow public key authentication
|
|
# - Disable X11 forwarding for security reasons (X11 forwarding is not needed for ssh)
|
|
# - Disable agent forwarding to prevent ssh-agent hijacking
|
|
# Then it will store the sshd service name in the services array.
|
|
function hardenSSH {
|
|
# Check if the sshd_config file exists and is readable
|
|
# If it is, then modify it using sed and restart the sshd service
|
|
# If it is not, then output an error message and exit with error code 1
|
|
# The -i flag is used to modify the file in place
|
|
# We split the sed command into multiple lines for readability purposes
|
|
# and to avoid calling it multiple times
|
|
if [[ -e /etc/ssh/sshd_config ]] && [[ -r /etc/ssh/sshd_config ]]; then
|
|
sudo sed -i \
|
|
-e 's/^#AllowGroups.*/AllowGroups sudo/' \
|
|
-e 's/^#Port.*/Port 22100/' \
|
|
-e 's/^#ClientAliveInterval.*/ClientAliveInterval 300/' \
|
|
-e 's/^#ClientAliveCountMax.*/ClientAliveCountMax 3/' \
|
|
-e 's/^#PermitRootLogin.*/PermitRootLogin no/' \
|
|
-e 's/^#PermitEmptyPasswords.*/PermitEmptyPasswords no/' \
|
|
-e 's/^#Protocol.*/Protocol 2/' \
|
|
-e 's/^#PasswordAuthentication.*/PasswordAuthentication no/' \
|
|
-e 's/^#X11Forwarding.*/X11Forwarding no/' \
|
|
-e 's/^X11Forwarding.*/X11Forwarding no/' \
|
|
-e 's/^#AllowAgentForwarding.*/AllowAgentForwarding no/' \
|
|
/etc/ssh/sshd_config
|
|
services+=("sshd") # Add sshd to the services array
|
|
else
|
|
printf "%s" "File sshd_config not found or not readable"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
function getCorrectFirewall {
|
|
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" | "Debian GNU/Linux") # If the distribution is Ubuntu or Debian
|
|
printf "%s" 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selections
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
export NEEDRESTART_MODE=a
|
|
export DEBIAN_PRIORITY=critical
|
|
sudo -E apt install ufw -y # Install ufw
|
|
printf "%s" "ufw" # Output ufw
|
|
;;
|
|
"CentOS Linux" | "Fedora" | "Red Hat Enterprise Linux Server") # If the distribution is CentOS, Fedora or RHEL
|
|
sudo dnf install firewalld -y # Install firewalld
|
|
printf "%s" "firewalld" # Output firewalld
|
|
;;
|
|
|
|
"openSUSE Leap") # If the distribution is OpenSUSE
|
|
sudo zypper install firewalld -y # Install firewalld
|
|
printf "%s" "firewalld" # Output firewalld
|
|
;;
|
|
*)
|
|
# If the distribution is none of the above, output unsupported distribution
|
|
# and exit with error code 1
|
|
printf "%s" "Unsupported distribution"
|
|
exit 1 # Exit with error code 1
|
|
;;
|
|
esac
|
|
|
|
}
|
|
|
|
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 Ubuntu
|
|
sudo 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
|
|
printf "%s" 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selections
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
export NEEDRESTART_MODE=a
|
|
export DEBIAN_PRIORITY=critical
|
|
sudo -E apt install apparmor apparmor-profiles apparmor-profiles-extra 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
|
|
sudo dnf install selinux container-selinux -y # Install selinux
|
|
printf "%s" "selinux" # Output selinux
|
|
;;
|
|
|
|
"openSUSE Leap") # If the distribution is OpenSUSE
|
|
sudo zypper install -t pattern apparmor -y # Install apparmor
|
|
printf "%s" "apparmor" # Output apparmor
|
|
;;
|
|
*)
|
|
# If the distribution is none of the above, output unsupported distribution
|
|
# and exit with error code 1
|
|
printf "%s" "Unsupported distribution"
|
|
exit 1 # Exit with error code 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
function firewallInit {
|
|
getCorrectFirewall # Get the correct firewall installed
|
|
# Determine if ufw or firewalld is installed
|
|
whereis ufw | grep -q /ufw && currentFirewall="ufw" || currentFirewall="firewalld"
|
|
case "$currentFirewall" in
|
|
ufw)
|
|
sudo ufw default allow outgoing # Allow outgoing connections
|
|
sudo ufw default deny incoming # Deny incoming connections
|
|
sudo ufw allow 22100/tcp # Allow ssh connections on port 22100
|
|
;;
|
|
firewalld)
|
|
sudo systemctl enable firewalld # Enable the firewall on boot
|
|
sudo firewall-cmd --permanent --add-port=22100/tcp # Allow ssh connections on port 22100
|
|
;;
|
|
*)
|
|
printf "%s" "Unsupported firewall"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
function kernelSecurityModuleInit {
|
|
getCorrectKernelSecurityModule # Get the correct kernel security module installed
|
|
# Determine if apparmor or selinux is installed
|
|
whereis apparmor | grep -q /apparmor && kernelSecurityModule="apparmor" || kernelSecurityModule="selinux"
|
|
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
|
|
sudo sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config # Set selinux to enforcing
|
|
## sudo systemctl restart docker # Restart docker
|
|
## sudo restorecon -Rv /var/lib/docker # Restore the selinux context of the docker directory
|
|
## sudo restorecon -Rv /usr/bin # Restore the selinux context of the docker directory
|
|
;;
|
|
*)
|
|
printf "%s" "Unsupported kernel security module"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
function dockerInit {
|
|
# Set up rootless docker
|
|
# Enable linger for the secdep user
|
|
sudo loginctl enable-linger secdep
|
|
# Enable dbus for the secdep user
|
|
sudo machinectl shell secdep@ /bin/bash -c "systemctl --user enable --now dbus"
|
|
# Install rootless docker
|
|
sudo machinectl shell secdep@ /bin/bash -c "curl -fsSL https://get.docker.com/rootless | sh"
|
|
# Add important environment variables to the secdep user's .bashrc
|
|
sudo su secdep << 'EOF'
|
|
printf "%s\n" "export PATH=/home/$USER/bin:$PATH" >> "$HOME/.bashrc"
|
|
printf "%s\n" "export DOCKER_HOST=unix:///run/user/$UID/docker.sock" >> "$HOME/.bashrc"
|
|
EOF
|
|
# Enable the user to bind to ports below 1024
|
|
sudo setcap cap_net_bind_service=ep /home/secdep/bin/rootlesskit
|
|
# Restart docker
|
|
sudo machinectl shell secdep@ /bin/bash -c "systemctl --user restart docker"
|
|
|
|
# # Create a new docker network to dissalow communication between containers
|
|
# ##sudo docker network create --driver bridge -o "com.docker.network.bridge.enable_icc"="false" dockerNetworkNoICC
|
|
# Get all arguments passed to the function and store them in the dockerImages array
|
|
local dockerImages=("$@")
|
|
# Using -f instead of -e to check if the file exists AND that it is a regular file
|
|
[[ -f /root/docker-compose.yml ]] && sudo mv /root/docker-compose.yml /home/secdep/docker-compose.yml
|
|
[[ -f /home/admin/docker-compose.yml ]] && sudo mv /home/admin/docker-compose.yml /home/secdep/docker-compose.yml
|
|
[[ -f /home/ec2-user/docker-compose.yml ]] && sudo mv /home/ec2-user/docker-compose.yml /home/secdep/docker-compose.yml
|
|
[[ -f /home/centos/docker-compose.yml ]] && sudo mv /home/centos/docker-compose.yml /home/secdep/docker-compose.yml
|
|
[[ -f /home/fedora/docker-compose.yml ]] && sudo mv /home/fedora/docker-compose.yml /home/secdep/docker-compose.yml
|
|
[[ -f /home/ubuntu/docker-compose.yml ]] && sudo mv /home/ubuntu/docker-compose.yml /home/secdep/docker-compose.yml
|
|
# Since FileDeployment does not work and we used ScriptFileDeployment which might make the file owned by another user
|
|
# we need to make sure the file is owned by the secdep user.
|
|
# When using [[ -f /home/secdep/docker-compose.yml ]] && sudo chown secdep:secdep /home/secdep/docker-compose.yml
|
|
# it doesn't get executed somehow so we'll send the "no such file or directory" error to /dev/null
|
|
sudo chown secdep:secdep /home/secdep/docker-compose.yml > /dev/null 2>&1
|
|
# Since FileDeployment does not work and we used ScriptFileDeployment which automatically makes the file executable
|
|
# we need to make sure the file is not executable.
|
|
# when using [[ -f /home/secdep/docker-compose.yml ]] && sudo chmod -x /home/secdep/docker-compose.yml
|
|
# it doesn't get executed somehow so we'll send the "no such file or directory" error to /dev/null
|
|
sudo chmod -x /home/secdep/docker-compose.yml > /dev/null 2>&1
|
|
sudo machinectl shell secdep@ /bin/bash -c 'curl -SL https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-linux-x86_64 -o /home/secdep/bin/docker-compose'
|
|
sudo machinectl shell secdep@ /bin/bash -c 'chmod +x /home/secdep/bin/docker-compose'
|
|
sudo machinectl shell secdep@ "$(which bash)" -c '[[ -f "$HOME/docker-compose.yml" ]] && DOCKER_HOST=unix:///run/user/$UID/docker.sock /home/secdep/bin/docker-compose -f /home/secdep/docker-compose.yml up -d'
|
|
# Read the docker-compose.yml file for port mappings to add to the firewall
|
|
CMD_PORTS="cat /home/secdep/docker-compose.yml | sed '/^[[:space:]]*$/d' | grep -A1 ports | grep '[0-9]:[0-9]' | rev | cut -d':' -f1 | rev | grep -Eow '[[:digit:]]+' | tr '\n' ' '"
|
|
sudo -E runuser - secdep -c "$CMD_PORTS" > /dev/null 2>&1 && PORTS="$(sudo -E runuser - secdep -c "$CMD_PORTS")" || PORTS=""
|
|
# Loop through the ports in the PORTS variable
|
|
if [[ -n "$PORTS" ]]; then
|
|
for port in $PORTS; do
|
|
# Allow the port in the firewall
|
|
case "$currentFirewall" in
|
|
ufw)
|
|
sudo ufw allow "$port"/tcp
|
|
;;
|
|
firewalld)
|
|
sudo firewall-cmd --permanent --add-port="$port"/tcp
|
|
;;
|
|
*)
|
|
printf "%s" "Unsupported firewall"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
fi
|
|
# Portainer is a docker image that provides a web interface for docker
|
|
# which will be installed and run on port 9000 by default to make it easier to manage docker
|
|
# CMD1="docker volume create portainer_data # Create a docker volume for portainer"
|
|
# CMD2="docker run -d -p 9000:9000 --name=portainer --restart=unless-stopped -v /home/secdep/.docker/run/docker.sock:/home/secdep/.docker/run/docker.sock -v portainer_data:/data portainer/portainer-ce"
|
|
# CMD2="docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /$XDG_RUNTIME_DIR/docker.sock:/var/run/docker.sock -v ~/.local/share/docker/volumes:/var/lib/docker/volumes -v portainer_data:/data portainer/portainer-ce"
|
|
# sudo -u secdep bash -c "$CMD1" # Create a docker volume for portainer
|
|
# sudo -u secdep bash -c "$CMD2" # Run portainer
|
|
# sudo machinectl shell secdep@ /bin/bash -c "docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /$XDG_RUNTIME_DIR/docker.sock:/var/run/docker.sock -v ~/.local/share/docker/volumes:/var/lib/docker/volumes -v portainer_data:/data portainer/portainer-ce"
|
|
# Check if the dockerImages array is empty and return 0 if it is
|
|
[[ "${#dockerImages[@]}" -eq 0 ]] && return 0
|
|
# Loop through the dockerImages array
|
|
# The dockerImages array contains all the docker images to install and run
|
|
for dockerImage in "${dockerImages[@]}"; do
|
|
# If dockerImage contains a ":" character (if it is a docker image with a tag)
|
|
# then store the docker image name in the dockerImageName variable
|
|
[[ "$dockerImage" == *":"* ]] && dockerImageName="${dockerImage%:*}" || dockerImageName="$dockerImage"
|
|
# Same goes for "/"
|
|
[[ "$dockerImageName" == *"/"* ]] && dockerImageName="${dockerImageName%/*}"
|
|
# No need to pull the docker image as the run command will do it automatically
|
|
# Run the docker image in the background,
|
|
# with the restart always option and the name of the docker image
|
|
# 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_BIND_SERVICE option will add the NET_BIND_SERVICE capability to the docker image
|
|
# The --read-only option will mount the docker image as read-only
|
|
# The --tmpfs /opt option will mount the /opt directory as a tmpfs
|
|
# The --network dockerNetworkNoICC option will connect the docker image to the dockerNetworkNoICC network
|
|
# The -v /:/host option will enable the docker rootless mode
|
|
# # The --user secdep option will run the docker image as the secdep user to prevent privilege escalation
|
|
# sudo -u secdep bash -c 'mkdir -p /home/secdep/opt'
|
|
CMD="docker pull $dockerImage"
|
|
# CMD="docker run -d --restart always --name $dockerImageName --security-opt=no-new-privileges --cap-drop all --cap-add NET_BIND_SERVICE --tmpfs /home/secdep/opt -v /:/host $dockerImage"
|
|
# CMD="docker run -d --restart always --name $dockerImageName --security-opt=no-new-privileges --cap-drop all --cap-add NET_BIND_SERVICE --read-only --tmpfs /home/secdep/opt -v /:/host $dockerImage"
|
|
sudo -E runuser - secdep -c "$CMD"
|
|
done
|
|
}
|
|
|
|
# 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
|
|
# }
|
|
|
|
# Fix banaction ufw with iptables
|
|
# Does not always persist after reboot
|
|
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
|
|
)
|
|
FAIL2BAN_JAIL_LOCAL=$(cat <<'EOF'
|
|
[DEFAULT]
|
|
bantime = 1d
|
|
EOF
|
|
)
|
|
printf "%s" "$FAIL2BAN_JAIL_LOCAL" | sudo tee /etc/fail2ban/jail.local
|
|
printf "%s" "$FAIL2BAN_SSH_JAIL_LOCAL" | sudo tee /etc/fail2ban/jail.d/sshd.local
|
|
FAIL2BAN_FILTER=$(cat <<'EOF'
|
|
[Definition]
|
|
failregex = ^.*DROP_.*SRC=<ADDR> 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
|
|
)
|
|
sudo mkdir -p /etc/systemd/system/fail2ban.service.d
|
|
printf "%s" "$HARDEN_FAIL2BAN_SERVICE" | sudo tee /etc/systemd/system/fail2ban.service.d/override.conf
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now fail2ban
|
|
printf "%s" "LogLevel VERBOSE" | sudo tee -a /etc/ssh/sshd_config
|
|
}
|
|
|
|
function enableServices {
|
|
for service in "${services[@]}"; do
|
|
sudo systemctl restart "$service"
|
|
done
|
|
whereis ufw | grep -q /ufw && currentFirewall="ufw" || currentFirewall="firewalld"
|
|
|
|
# With the if block it doesn't error out at firewalld check so we do it this way
|
|
# For ufw
|
|
if [[ "$currentFirewall" == "ufw" ]]; then
|
|
# Enable the firewall
|
|
sudo ufw --force enable
|
|
# Enable and start the firewall on boot
|
|
sudo systemctl enable --now ufw
|
|
# For firewalld
|
|
elif [[ "$currentFirewall" == "firewalld" ]]; then
|
|
# Reload the firewall
|
|
sudo firewall-cmd --reload
|
|
else
|
|
printf "%s" "Somehow there is no firewall installed"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Sometimes the user is not deleted after the script is run
|
|
function deleteRemainingUsers {
|
|
# Delete possible remaining users
|
|
cat << EOF | sudo tee /root/delete_users.sh
|
|
[[ -d /home/admin ]] && sudo userdel -r admin && sudo groupdel admin
|
|
[[ -d /home/ec2-user ]] && sudo userdel -r ec2-user && sudo groupdel ec2-user
|
|
[[ -d /home/centos ]] && sudo userdel -r centos && sudo groupdel centos
|
|
[[ -d /home/fedora ]] && sudo userdel -r fedora && sudo groupdel fedora
|
|
[[ -d /home/ubuntu ]] && sudo userdel -r ubuntu && sudo groupdel ubuntu
|
|
sudo rm -f /root/delete_users.sh
|
|
EOF
|
|
sudo systemctl enable --now atd
|
|
# Use at as root because if it is run as one of the users above it will fail
|
|
sudo at now + 1 minute <<< "bash /root/delete_users.sh"
|
|
}
|
|
|
|
# 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 {
|
|
printf "%s" "$SCRIPT_NAME script started"
|
|
check_dependencies || exit 1 # Check dependencies and exit if it fails
|
|
printf "%s" "Dependencies installed"
|
|
hardenSSH || exit 1 # Harden ssh and exit if it fails
|
|
printf "%s" "SSH hardened"
|
|
firewallInit || exit 1 # Initialize the firewall and exit if it fails
|
|
printf "%s" "Firewall initialized"
|
|
kernelSecurityModuleInit || exit 1 # Initialize the kernel security module and exit if it fails
|
|
printf "%s" "Kernel security module initialized"
|
|
configureFail2ban || exit 1 # Initialize fail2ban and exit if it fails
|
|
printf "%s" "Fail2ban configured"
|
|
# selinuxConfig # Configure selinux
|
|
# Call the dockerInit function with the arguments passed to the script
|
|
dockerInit "$@" || exit 1 # Initialize docker and exit if it fails
|
|
enableServices || exit 1
|
|
deleteRemainingUsers || exit 1
|
|
printf "%s" "$SCRIPT_NAME script finished" # Output message to the user
|
|
printf "%s" "System will reboot momentarily" # Output message to the user
|
|
sudo at now + 2 minute <<< "reboot"
|
|
}
|
|
|
|
# Call the main function
|
|
main "$@"
|
|
|
|
exit 0 # The right and proper way to exit a script
|