#!/bin/bash # curl -o waf42.sh https://waf42.com # visudo waf42mgmt ALL=(ALL) NOPASSWD: /usr/bin/geoipupdate # Check if /etc/os-release exists and source it if [ -f /etc/os-release ]; then . /etc/os-release else echo "Error: Cannot detect OS version. This script only supports AlmaLinux 9.5." exit 1 fi # Check if it's AlmaLinux 9.5 if [ "$ID" != "almalinux" ] || [ "$VERSION_ID" != "9.5" ]; then echo "This script only supports AlmaLinux 9.5. Detected: $PRETTY_NAME" exit 1 fi # Define paths and variables SRC_PATH="/usr/local/src" NGINX_STABLE_VERSION="https://nginx.org/download/nginx-1.26.3.tar.gz" NGINX_TAR_GZ="nginx-1.26.3.tar.gz" NGINX_PATH="${SRC_PATH}/nginx-1.26.3" WAF42_MGMT_PATH="/opt/waf42mgmt" WAF42_MGMT_HTDOCS="${WAF42_MGMT_PATH}/htdocs" WAF42_MGMT_SCRIPTS="${WAF42_MGMT_PATH}/scripts" WAF42_MGMT_CONF="${WAF42_MGMT_PATH}/waf42conf" WAF42_MGMT_PORT="10000" WAF42_SSL_PATH="${WAF42_MGMT_PATH}/ssl" NGINX_CONF="${WAF42_MGMT_PATH}/conf/nginx.conf" PHP_FPM_CONF="/etc/php-fpm.d/waf42mgmt.conf" PHP_FPM_SOCK="/run/php-fpm-waf42mgmt.sock" # Update AlmaLinux dnf update -y # Open WAF42 management port firewall-cmd --permanent --add-port=${WAF42_MGMT_PORT}/tcp firewall-cmd --reload # Create WAF42 directory structure mkdir -p ${WAF42_MGMT_PATH}/{sbin,conf,run,logs,htdocs,ssl,waf42conf,scripts} # Create user and group for WAF42, ensure shadow group exists groupadd waf42mgmt groupadd shadow # Create shadow group for PAM useradd -g waf42mgmt -d ${WAF42_MGMT_PATH} -s /sbin/nologin waf42mgmt usermod -aG shadow waf42mgmt # Add waf42mgmt to shadow group chmod g+r /etc/shadow # Ensure shadow group can read /etc/shadow chgrp shadow /etc/shadow # Set group to shadow chown -R waf42mgmt:waf42mgmt ${WAF42_MGMT_PATH} # Install dependencies dnf -y module reset php dnf -y module enable php:8.2 dnf -y install epel-release dnf -y update dnf -y install git gcc pcre-devel openssl-devel zlib-devel \ php php-cli php-fpm php-mysqlnd php-opcache php-xml \ php-mbstring php-curl php-zip php-gd certbot \ python3-certbot-nginx php-pear php-devel pam-devel \ policycoreutils-python-utils setsebool -P httpd_mod_auth_pam 1 setsebool -P httpd_use_sasl 1 # Maxmind geoipupdate mkdir /etc/maxmind chmod 775 /etc/maxmind/ curl -LO https://github.com/maxmind/geoipupdate/releases/download/v7.1.0/geoipupdate_7.1.0_linux_amd64.rpm rpm -Uvhi geoipupdate_7.1.0_linux_amd64.rpm rm -f geoipupdate_7.1.0_linux_amd64.rpm # Give waf42mgmt rights to Update Maxmind database echo "waf42mgmt ALL=(ALL) NOPASSWD: ${WAF42_MGMT_SCRIPTS}/geoipupdate.sh" | tee /etc/sudoers.d/geoipupdate > /dev/null echo "waf42mgmt ALL=(ALL) NOPASSWD: ${WAF42_MGMT_SCRIPTS}/geoipdelete.sh" | tee /etc/sudoers.d/geoipdelete > /dev/null echo "waf42mgmt ALL=(ALL) NOPASSWD: ${WAF42_MGMT_SCRIPTS}/reload_firewall.sh" | tee /etc/sudoers.d/reload_firewall > /dev/null chmod 0440 /etc/sudoers.d/geoipupdate chmod 0440 /etc/sudoers.d/geoipdelete chmod 0440 /etc/sudoers.d/reload_firewall visudo -c # Create geoipupdate.sh script cat << 'EOF' | sudo tee "${WAF42_MGMT_SCRIPTS}/geoipupdate.sh" > /dev/null #!/bin/bash # Define the target directory with today's date TARGET_DIR="/etc/maxmind/$(date +%Y-%m-%d)" # Ensure the directory exists and set correct permissions mkdir -p "$TARGET_DIR" chown waf42mgmt:waf42mgmt "$TARGET_DIR" chmod 755 "$TARGET_DIR" # Run geoipupdate /usr/bin/geoipupdate -f /opt/waf42mgmt/waf42conf/maxmind.conf -d "$TARGET_DIR" # Exit with the status code of geoipupdate exit $? EOF # Set the script as executable chmod +x "${WAF42_MGMT_SCRIPTS}/geoipupdate.sh" # Create geoipdelete.sh script cat << 'EOF' | sudo tee "${WAF42_MGMT_SCRIPTS}/geoipdelete.sh" > /dev/null #!/bin/bash # Check if the user provided a date argument if [ -z "$1" ]; then echo "Error: No date provided. Usage: geoipdelete.sh YYYY-MM-DD" exit 1 fi # Validate the date format (YYYY-MM-DD) if ! [[ "$1" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then echo "Error: Invalid date format. Please use YYYY-MM-DD." exit 1 fi # Define the directory path based on the provided date DATE_DIR="/etc/maxmind/$1" # Check if the directory exists and is a valid directory if [ ! -d "$DATE_DIR" ]; then echo "Error: Directory $DATE_DIR does not exist." exit 1 fi # Ensure we are not deleting anything outside the /etc/maxmind/ directory if [[ "$DATE_DIR" != /etc/maxmind/* ]]; then echo "Error: Invalid directory. Only directories inside /etc/maxmind/ can be deleted." exit 1 fi # Deleting the specified directory echo "Deleting $DATE_DIR..." rm -rf "$DATE_DIR" # Check if the deletion was successful if [ $? -eq 0 ]; then echo "Successfully deleted the directory: $DATE_DIR" else echo "Failed to delete the directory: $DATE_DIR" exit 1 fi exit 0 EOF # Set the script as executable chmod +x "${WAF42_MGMT_SCRIPTS}/geoipdelete.sh" # Create reload_firewall.sh script cat << 'EOF' | sudo tee "${WAF42_MGMT_SCRIPTS}/reload_firewall.sh" > /dev/null #!/bin/bash # Path to firewall configuration file CONFIG_FILE="/opt/waf42mgmt/waf42conf/firewall.conf" # Zone for firewall rules (change if needed) FIREWALL_ZONE="public" # Ensure firewalld is running; if not, start it if ! systemctl is-active --quiet firewalld; then echo "Firewalld is not running. Starting firewalld..." systemctl start firewalld if ! systemctl is-active --quiet firewalld; then echo "Error: Failed to start firewalld!" exit 1 fi fi # Remove global SSH service, Management Port 10000, HTTP (80) and HTTPS (443) echo "Removing global SSH service, Management Port 10000, HTTP (80) and HTTPS (443) from Firewalld..." firewall-cmd --zone="$FIREWALL_ZONE" --remove-service=ssh --permanent firewall-cmd --zone="$FIREWALL_ZONE" --permanent --remove-port=10000/tcp firewall-cmd --zone="$FIREWALL_ZONE" --permanent --remove-port=80/tcp firewall-cmd --zone="$FIREWALL_ZONE" --permanent --remove-port=443/tcp # Function to extract allowed IPs from the config file extract_ips() { local key="$1" grep -E "^$key=" "$CONFIG_FILE" | sed -E 's/^'"$key"'="([^"]+)"/\1/' | tr ',' '\n' | sed '/^$/d' } # Function to extract flag values (e.g., ENABLE_HTTP) extract_flag() { local key="$1" grep -E "^$key=" "$CONFIG_FILE" | sed -E 's/^'"$key"'="([^"]+)"/\1/' | tr -d '"' } # Read allowed IPs for SSH and Management ALLOWED_IPV4=$(extract_ips "SSH_IPV4") ALLOWED_IPV6=$(extract_ips "SSH_IPV6") MGMT_IPV4=$(extract_ips "MGMT_IPV4") MGMT_IPV6=$(extract_ips "MGMT_IPV6") # Read web access flags (for HTTP and HTTPS) ENABLE_HTTP=$(extract_flag "ENABLE_HTTP") ENABLE_HTTPS=$(extract_flag "ENABLE_HTTPS") # Get current rich rules in the zone EXISTING_RULES=$(firewall-cmd --zone="$FIREWALL_ZONE" --list-rich-rules) # Remove outdated SSH rules (per-IP) not in config echo "Removing outdated SSH allow rules..." while read -r rule; do IP=$(echo "$rule" | grep -oP 'source address="\K[^"]+') if [[ -n "$IP" ]] && [[ ! $ALLOWED_IPV4 =~ $IP ]] && [[ ! $ALLOWED_IPV6 =~ $IP ]]; then firewall-cmd --zone="$FIREWALL_ZONE" --remove-rich-rule="$rule" --permanent echo "Removed outdated SSH rule for IP: $IP" fi done <<< "$EXISTING_RULES" # Remove outdated Management Port 10000 rules not in config echo "Removing outdated Management Port 10000 allow rules..." while read -r rule; do IP=$(echo "$rule" | grep -oP 'source address="\K[^"]+') if [[ -n "$IP" ]] && [[ ! $MGMT_IPV4 =~ $IP ]] && [[ ! $MGMT_IPV6 =~ $IP ]]; then firewall-cmd --zone="$FIREWALL_ZONE" --remove-rich-rule="$rule" --permanent echo "Removed outdated Management Port 10000 rule for IP: $IP" fi done <<< "$EXISTING_RULES" # Apply new SSH IPv4 rules echo "Applying new SSH IPv4 allow rules..." for ip in $ALLOWED_IPV4; do if [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then if ! echo "$EXISTING_RULES" | grep -q "$ip"; then firewall-cmd --zone="$FIREWALL_ZONE" --add-rich-rule="rule family='ipv4' source address='$ip' service name='ssh' accept" --permanent echo "Allowed SSH access for IPv4: $ip" fi fi done # Apply new SSH IPv6 rules echo "Applying new SSH IPv6 allow rules..." for ip in $ALLOWED_IPV6; do if [[ "$ip" =~ ^[a-fA-F0-9:]+$ ]]; then if ! echo "$EXISTING_RULES" | grep -q "$ip"; then firewall-cmd --zone="$FIREWALL_ZONE" --add-rich-rule="rule family='ipv6' source address='$ip' service name='ssh' accept" --permanent echo "Allowed SSH access for IPv6: $ip" fi fi done # Apply new Management Port 10000 IPv4 rules echo "Applying new Management Port 10000 IPv4 allow rules..." for ip in $MGMT_IPV4; do if [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then if ! echo "$EXISTING_RULES" | grep -q "$ip"; then firewall-cmd --zone="$FIREWALL_ZONE" --add-rich-rule="rule family='ipv4' source address='$ip' port port='10000' protocol='tcp' accept" --permanent echo "Allowed Management Port 10000 access for IPv4: $ip" fi fi done # Apply new Management Port 10000 IPv6 rules echo "Applying new Management Port 10000 IPv6 allow rules..." for ip in $MGMT_IPV6; do if [[ "$ip" =~ ^[a-fA-F0-9:]+$ ]]; then if ! echo "$EXISTING_RULES" | grep -q "$ip"; then firewall-cmd --zone="$FIREWALL_ZONE" --add-rich-rule="rule family='ipv6' source address='$ip' port port='10000' protocol='tcp' accept" --permanent echo "Allowed Management Port 10000 access for IPv6: $ip" fi fi done # Handle global Web Access for HTTP (Port 80) if [ "$ENABLE_HTTP" = "1" ]; then echo "Enabling global HTTP access (Port 80)..." firewall-cmd --zone="$FIREWALL_ZONE" --add-port=80/tcp --permanent else echo "Disabling global HTTP access (Port 80)..." firewall-cmd --zone="$FIREWALL_ZONE" --remove-port=80/tcp --permanent fi # Handle global Web Access for HTTPS (Port 443) if [ "$ENABLE_HTTPS" = "1" ]; then echo "Enabling global HTTPS access (Port 443)..." firewall-cmd --zone="$FIREWALL_ZONE" --add-port=443/tcp --permanent else echo "Disabling global HTTPS access (Port 443)..." firewall-cmd --zone="$FIREWALL_ZONE" --remove-port=443/tcp --permanent fi # Reload firewalld to apply all changes echo "Reloading firewalld..." firewall-cmd --reload echo "Firewall rules for SSH, Management Port 10000, and Web Access updated successfully." exit 0 EOF # Set the script as executable sudo chmod +x "${WAF42_MGMT_SCRIPTS}/reload_firewall.sh" echo "🔹 Updating SELinux Policy for HTTPD, Firewalld, and DBus (v1.6)..." cat <<'EOF' > /tmp/httpd_sudo.te module httpd_sudo 1.6; require { type httpd_t; type firewalld_t; type firewalld_exec_t; type firewalld_unit_file_t; type systemd_logind_t; class service { start stop status reload }; class dbus { send_msg }; class capability { sys_resource fowner }; class file { read execute setattr }; class process { transition sigchld }; class dir { write add_name create setattr }; } # Allow PHP-FPM (httpd_t) to manage its own resources allow httpd_t self:capability { sys_resource fowner }; allow httpd_t self:process { transition sigchld }; allow httpd_t self:file { read execute setattr }; allow httpd_t self:dir { write add_name create setattr }; # Allow httpd_t to control Firewalld services allow httpd_t firewalld_t:service { start stop status reload }; # Allow httpd_t to execute firewall-cmd allow httpd_t firewalld_exec_t:file { read execute }; # Allow httpd_t to query and start Firewalld unit files allow httpd_t firewalld_unit_file_t:service { status start }; # Allow httpd_t to send DBus messages to both Firewalld and systemd_logind allow httpd_t firewalld_t:dbus { send_msg }; allow httpd_t systemd_logind_t:dbus { send_msg }; EOF # Remove any previously loaded module semodule -r httpd_sudo 2>/dev/null # Compile and install the new SELinux policy module checkmodule -M -m -o /tmp/httpd_sudo.mod /tmp/httpd_sudo.te semodule_package -o /tmp/httpd_sudo.pp -m /tmp/httpd_sudo.mod semodule -i /tmp/httpd_sudo.pp # Restore SELinux context on Firewalld's systemd unit file restorecon -Rv /usr/lib/systemd/system/firewalld.service # (Keep your additional settings as needed) semanage fcontext -a -t httpd_sys_rw_content_t "/etc/maxmind(/.*)?" restorecon -Rv /etc/maxmind/ setsebool -P httpd_execmem 1 setsebool -P httpd_can_network_connect 1 setsebool -P httpd_can_network_connect_db 1 setsebool -P httpd_can_sendmail 1 rm -f /tmp/httpd_sudo.te /tmp/httpd_sudo.mod /tmp/httpd_sudo.pp echo "✅ SELinux Policy Updated! (v1.6) HTTPD now has DBus send_msg rights to both Firewalld and systemd_logind." # Download and build nginx mkdir -p "${SRC_PATH}" curl -L "${NGINX_STABLE_VERSION}" -o "${SRC_PATH}/${NGINX_TAR_GZ}" tar -xzf "${SRC_PATH}/${NGINX_TAR_GZ}" -C "${SRC_PATH}" cd "${NGINX_PATH}" ./configure \ --prefix="${WAF42_MGMT_PATH}" \ --sbin-path="${WAF42_MGMT_PATH}/sbin/nginx" \ --conf-path="${WAF42_MGMT_PATH}/conf/nginx.conf" \ --pid-path="${WAF42_MGMT_PATH}/run/nginx.pid" \ --with-http_ssl_module make make install # Generate DH parameter and self-signed cert openssl dhparam -out "${WAF42_SSL_PATH}/dhparam.pem" 2048 openssl req -x509 -newkey rsa:2048 -keyout "${WAF42_SSL_PATH}/waf42.key" \ -out "${WAF42_SSL_PATH}/waf42.crt" -days 3650 -nodes \ -subj "/C=VG/ST=Galactic Sector ZZ9 Plural Z Alpha/L=Milliways/O=Pan-Galactic Gargle Blasters/OU=Department of Improbability/CN=waf42.mgmt" ln -sf "${WAF42_SSL_PATH}/waf42.crt" "${WAF42_SSL_PATH}/ssl.crt" ln -sf "${WAF42_SSL_PATH}/waf42.key" "${WAF42_SSL_PATH}/ssl.key" chmod 600 "${WAF42_SSL_PATH}/waf42.key" "${WAF42_SSL_PATH}/waf42.crt" "${WAF42_SSL_PATH}/dhparam.pem" chown -R waf42mgmt:waf42mgmt "${WAF42_SSL_PATH}" # Configure PHP-FPM with isolated pool mv /etc/php-fpm.d/www.conf /etc/php-fpm.d/www.conf.bak cat << EOF > "${PHP_FPM_CONF}" [waf42mgmt] user = waf42mgmt group = waf42mgmt listen = ${PHP_FPM_SOCK} listen.owner = waf42mgmt listen.group = waf42mgmt listen.mode = 0660 pm = dynamic pm.max_children = 5 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 php_admin_value[error_log] = ${WAF42_MGMT_PATH}/logs/php-fpm_error.log php_admin_flag[log_errors] = on EOF # Install and configure PAM extension pecl install pam || echo "PAM extension already installed" echo "extension=pam.so" > /etc/php.d/50-pam.ini cat << EOF > /etc/pam.d/php auth required pam_unix.so account required pam_unix.so EOF # Start PHP-FPM systemctl enable php-fpm systemctl restart php-fpm chown waf42mgmt:waf42mgmt "${PHP_FPM_SOCK}" chmod 660 "${PHP_FPM_SOCK}" # Configure nginx cat << EOF > "${NGINX_CONF}" events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; access_log ${WAF42_MGMT_PATH}/logs/access.log; error_log ${WAF42_MGMT_PATH}/logs/error.log warn; sendfile on; server { listen ${WAF42_MGMT_PORT} ssl; server_name waf42.mgmt; root ${WAF42_MGMT_PATH}/htdocs; index index.php index.html; ssl_certificate ${WAF42_SSL_PATH}/ssl.crt; ssl_certificate_key ${WAF42_SSL_PATH}/ssl.key; ssl_dhparam ${WAF42_SSL_PATH}/dhparam.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; location / { try_files \$uri \$uri/ /index.php?\$args; } location ~ \.php$ { fastcgi_pass unix:${PHP_FPM_SOCK}; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name; include fastcgi_params; } } } EOF # Create systemd service for nginx cat << EOF > /etc/systemd/system/waf42mgmt.service [Unit] Description=Custom Nginx Server for WAF42 Management After=network.target php-fpm.service [Service] Type=simple ExecStart=${WAF42_MGMT_PATH}/sbin/nginx -c ${WAF42_MGMT_PATH}/conf/nginx.conf -g "daemon off;" ExecReload=${WAF42_MGMT_PATH}/sbin/nginx -s reload ExecStop=${WAF42_MGMT_PATH}/sbin/nginx -s quit PrivateTmp=true User=waf42mgmt Group=waf42mgmt Restart=on-failure [Install] WantedBy=multi-user.target EOF # Start nginx service systemctl daemon-reload systemctl enable waf42mgmt systemctl restart waf42mgmt # Ensure directories are writable chown -R waf42mgmt:waf42mgmt "${WAF42_MGMT_PATH}/logs" "${WAF42_MGMT_PATH}/run" chmod 750 "${WAF42_MGMT_PATH}/logs" "${WAF42_MGMT_PATH}/run" # Install index.php with PAM auth cat << 'EOF' > "${WAF42_MGMT_PATH}/htdocs/index.php"