351 lines
16 KiB
Bash
Executable File
351 lines
16 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
|
|
sudo apt update # Update the package list
|
|
sudo 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 {
|
|
local dependencies=(curl git sudo vim ssh docker docker-compose wget) # 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
|
|
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/^#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
|
|
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
|
|
dnf install firewalld -y # Install firewalld
|
|
printf "%s" "firewalld" # Output firewalld
|
|
;;
|
|
|
|
"openSUSE Leap") # If the distribution is OpenSUSE
|
|
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 Debian
|
|
apt install apparmor-profiles -y # Install apparmor
|
|
printf "%s" "apparmor" # Output apparmor
|
|
;;
|
|
"Debian GNU/Linux") # If the distribution is Debian
|
|
apt install apparmor apparmor-utils auditd
|
|
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
|
|
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
|
|
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 {
|
|
local firewall
|
|
firewall="$(getCorrectFirewall)" # Get the correct firewall
|
|
case "$firewall" 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
|
|
sudo ufw enable # Enable the firewall
|
|
sudo systemctl enable ufw # Enable the firewall on boot
|
|
sudo systemctl start ufw # Start the firewall
|
|
;;
|
|
firewalld)
|
|
sudo systemctl enable --now firewalld # Enable the firewall on boot and start it
|
|
sudo firewall-cmd --permanent --add-port=22100/tcp # Allow ssh connections on port 22100
|
|
sudo firewall-cmd --reload # Reload the firewall
|
|
;;
|
|
*)
|
|
printf "%s" "Unsupported firewall"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
function kernelSecurityModuleInit {
|
|
local kernelSecurityModule
|
|
kernelSecurityModule="$(getCorrectKernelSecurityModule)" # Get the correct kernel security module
|
|
case "$kernelSecurityModule" in
|
|
apparmor)
|
|
sudo systemctl enable --now apparmor # Enable the kernel security module on boot and start it
|
|
;;
|
|
selinux)
|
|
sudo systemctl enable --now selinux # Enable the kernel security module on boot and start it
|
|
;;
|
|
*)
|
|
printf "%s" "Unsupported kernel security module"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
function dockerInit {
|
|
# Add user to docker group to avoid using sudo when running docker commands
|
|
sudo usermod -aG docker "$USER"
|
|
# Get all arguments passed to the function and store them in the dockerImages array
|
|
local dockerImages=("$@")
|
|
# 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
|
|
# 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"
|
|
done
|
|
}
|
|
|
|
# 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
|
|
firewallInit || exit 1 # Initialize the firewall and exit if it fails
|
|
kernelSecurityModuleInit || exit 1 # Initialize the kernel security module and exit if it fails
|
|
# 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
|
|
[[ $# -gt 0 ]] && dockerInit "$@" || exit 1
|
|
printf "%s" "Script finished" # Output message to the user
|
|
}
|
|
|
|
# # The am_i_root function will check if the user is root and exit if they are not.
|
|
# function am_i_root {
|
|
# if [[ $EUID -ne 0 ]]; then # Check if the user is root
|
|
# printf "%s" "Please run as root" # Output message to the user
|
|
# 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
|