#!/bin/bash # ========================================== # INTERACTIVE FIREWALL CONFIGURATOR FOR SAP B1 # (With Save/Load State) # ========================================== # Configuration File CONFIG_FILE="./firewall_state.conf" # Colors for formatting RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' # No Color # ========================================== # SERVICE DEFINITIONS # ========================================== declare -a SVC_NAMES declare -a SVC_PORTS SVC_NAMES[0]="SAP Web Client" SVC_PORTS[0]="443" SVC_NAMES[1]="SAP HANA Database (System & Company DB)" SVC_PORTS[1]="30013 30015" SVC_NAMES[2]="SAP Business One SLD" SVC_PORTS[2]="40000" SVC_NAMES[3]="SAP Business One Auth" SVC_PORTS[3]="40020" SVC_NAMES[4]="SAP Business One Service Layer, Cockpit" SVC_PORTS[4]="50000 4300" SVC_NAMES[5]="SAP Host Agent" SVC_PORTS[5]="1128 1129" SVC_NAMES[6]="SSH Remote Access" SVC_PORTS[6]="22" SVC_NAMES[7]="SMB / B1_SHR (File Sharing)" SVC_PORTS[7]="139 445" # Arrays to store user decisions declare -a CONFIG_DECISION # "ALL", "IP", "SKIP" declare -a CONFIG_IPS # Stores the IP string if "IP" is chosen DO_FLUSH=false # ========================================== # HELPER FUNCTIONS # ========================================== print_header() { clear echo -e "${CYAN}==========================================${NC}" echo -e "${CYAN} SAP B1 Interactive Firewall Setup ${NC}" echo -e "${CYAN}==========================================${NC}" echo "" } save_config() { echo "# SAP B1 Firewall State - Do not edit manually unless you know what you are doing" > "$CONFIG_FILE" for i in "${!SVC_NAMES[@]}"; do # Use simple variable assignment format echo "SAVED_DECISION[$i]=\"${CONFIG_DECISION[$i]}\"" >> "$CONFIG_FILE" echo "SAVED_IPS[$i]=\"${CONFIG_IPS[$i]}\"" >> "$CONFIG_FILE" done echo "Config state saved to $CONFIG_FILE" } load_config() { if [ -f "$CONFIG_FILE" ]; then echo -e "${GREEN}Found saved configuration file.${NC}" source "$CONFIG_FILE" # Map SAVED variables to current CONFIG variables for i in "${!SVC_NAMES[@]}"; do CONFIG_DECISION[$i]="${SAVED_DECISION[$i]}" CONFIG_IPS[$i]="${SAVED_IPS[$i]}" done return 0 else return 1 fi } # ========================================== # INITIAL SETUP # ========================================== print_header # 1. Flush Question (First) echo -e "${YELLOW}Existing Configuration:${NC}" echo "Do you want to FLUSH (remove) all currently active firewall rules before starting?" echo "This ensures a clean slate. (Only affects Runtime, not Permanent config)" read -p "Flush all current rules? [Y/n]: " flush_choice flush_choice=${flush_choice:-Y} # Default to Yes if [[ "$flush_choice" =~ ^[Yy]$ ]]; then DO_FLUSH=true echo -e "-> ${RED}Will flush all rules (Clean Slate).${NC}" else echo -e "-> Keeping existing rules (Appending new ones)." fi echo "" sleep 1 # 2. Load Configuration (Second) HAS_CONFIG=false if load_config; then HAS_CONFIG=true echo "Previous settings loaded. You can press ENTER to accept defaults during selection." else echo "No previous configuration found. Starting fresh." fi echo "" sleep 1 # ========================================== # CONFIGURATION LOOP # ========================================== print_header echo -e "This script will help you configure access rules for each service." echo -e "${YELLOW}Note: Configuration applies to RUNTIME only.${NC}" echo "" for i in "${!SVC_NAMES[@]}"; do NAME="${SVC_NAMES[$i]}" PORTS="${SVC_PORTS[$i]}" # Get previous setting if available PREV_DECISION="${CONFIG_DECISION[$i]}" PREV_IPS="${CONFIG_IPS[$i]}" # Determine default option number for UI DEFAULT_OPT="" DEFAULT_TXT="" if [[ "$PREV_DECISION" == "ALL" ]]; then DEFAULT_OPT="1"; DEFAULT_TXT="[Default: 1 - Public]"; fi if [[ "$PREV_DECISION" == "IP" ]]; then DEFAULT_OPT="2"; DEFAULT_TXT="[Default: 2 - Restricted]"; fi if [[ "$PREV_DECISION" == "SKIP" ]]; then DEFAULT_OPT="3"; DEFAULT_TXT="[Default: 3 - Skip]"; fi echo -e "--------------------------------------------------" echo -e "Configuring Service: ${GREEN}$NAME${NC}" echo -e "Ports: ${YELLOW}$PORTS${NC}" echo "--------------------------------------------------" echo "1) Allow from ANYWHERE (Public)" echo "2) Restrict to SPECIFIC IPs" echo "3) Skip / Block (Do not open)" while true; do read -p "Select option (1-3) $DEFAULT_TXT: " choice # Handle Enter key (Default) if [[ -z "$choice" && -n "$DEFAULT_OPT" ]]; then choice=$DEFAULT_OPT fi case $choice in 1) CONFIG_DECISION[$i]="ALL" CONFIG_IPS[$i]="" echo -e "-> Selected: ${RED}Public Access${NC}" break ;; 2) CONFIG_DECISION[$i]="IP" echo "" echo -e "Enter IPs or Subnets separated by spaces or commas." # Show previous IPs as default if they exist if [[ -n "$PREV_IPS" ]]; then echo -e "Current Saved IPs: ${CYAN}$PREV_IPS${NC}" read -p "IPs [Press Enter to keep current]: " ip_input if [[ -z "$ip_input" ]]; then ip_input="$PREV_IPS" fi else echo -e "Example: ${CYAN}192.168.1.10, 192.168.1.20${NC}" read -p "IPs: " ip_input fi # Replace commas with spaces to sanitize CONFIG_IPS[$i]="${ip_input//,/ }" echo -e "-> Selected: ${GREEN}Restricted to ${CONFIG_IPS[$i]}${NC}" break ;; 3) CONFIG_DECISION[$i]="SKIP" CONFIG_IPS[$i]="" echo -e "-> Selected: ${YELLOW}Skipping${NC}" break ;; *) echo "Invalid option." ;; esac done echo "" done # ========================================== # SUMMARY & CONFIRMATION # ========================================== print_header echo -e "${YELLOW}SUMMARY OF PENDING CHANGES:${NC}" echo "" printf "%-25s | %-15s | %-30s\n" "Service" "Action" "Details" echo "-------------------------------------------------------------------------------" for i in "${!SVC_NAMES[@]}"; do NAME="${SVC_NAMES[$i]}" ACTION="${CONFIG_DECISION[$i]}" DETAILS="${CONFIG_IPS[$i]}" # Shorten name for table SHORT_NAME=${NAME:0:24} if [ "$ACTION" == "ALL" ]; then printf "%-25s | ${RED}%-15s${NC} | %-30s\n" "$SHORT_NAME" "Open Public" "0.0.0.0/0" elif [ "$ACTION" == "IP" ]; then printf "%-25s | ${GREEN}%-15s${NC} | %-30s\n" "$SHORT_NAME" "Restricted" "$DETAILS" else printf "%-25s | ${YELLOW}%-15s${NC} | %-30s\n" "$SHORT_NAME" "Blocked/Skip" "-" fi done echo "" echo -e "${CYAN}Global Actions:${NC}" if [ "$DO_FLUSH" = true ]; then echo "1. FLUSH ALL current rules (Clean Slate)." else echo "1. Remove specific insecure rules (0-65535) and standard SSH service." fi echo "" read -p "Do you want to SAVE config and APPLY changes now? [Y/n]: " confirm confirm=${confirm:-Y} # Default to Yes if [[ ! "$confirm" =~ ^[Yy]$ ]]; then echo "Aborted." exit 0 fi # ========================================== # EXECUTION # ========================================== echo "" echo "Saving configuration to $CONFIG_FILE..." save_config echo "Applying configurations (RUNTIME ONLY)..." # 1. Flush or Safety Cleanup if [ "$DO_FLUSH" = true ]; then echo "-> Flushing active rules..." # Flush Services for service in $(firewall-cmd --list-services); do firewall-cmd --remove-service="$service" >/dev/null 2>&1 done # Flush Ports for port in $(firewall-cmd --list-ports); do firewall-cmd --remove-port="$port" >/dev/null 2>&1 done # Flush Rich Rules firewall-cmd --list-rich-rules | while read -r rule; do if [ -n "$rule" ]; then firewall-cmd --remove-rich-rule="$rule" >/dev/null 2>&1 fi done else # Only remove specific conflicting rules if not flushing everything echo "-> Cleaning up insecure rules..." firewall-cmd --remove-port=0-65535/tcp >/dev/null 2>&1 firewall-cmd --remove-service=ssh >/dev/null 2>&1 firewall-cmd --remove-port=22/tcp >/dev/null 2>&1 fi # 2. Loop and Apply for i in "${!SVC_NAMES[@]}"; do PORTS="${SVC_PORTS[$i]}" DECISION="${CONFIG_DECISION[$i]}" # Convert space-separated ports to array for inner loop read -ra PORT_ARR <<< "$PORTS" if [ "$DECISION" == "ALL" ]; then # Open ports globally for port in "${PORT_ARR[@]}"; do echo " Opening $port globally..." firewall-cmd --add-port=${port}/tcp >/dev/null done elif [ "$DECISION" == "IP" ]; then # Add rich rules for specific IPs read -ra IP_ARR <<< "${CONFIG_IPS[$i]}" for ip in "${IP_ARR[@]}"; do if [[ -z "$ip" ]]; then continue; fi for port in "${PORT_ARR[@]}"; do echo " Allowing $ip access to port $port..." firewall-cmd --add-rich-rule="rule family='ipv4' source address='$ip' port port='$port' protocol='tcp' accept" >/dev/null done done fi done echo "" echo -e "${YELLOW}==========================================${NC}" echo -e "${YELLOW} TESTING CONNECTIVITY ${NC}" echo -e "${YELLOW}==========================================${NC}" echo "The new rules are active. If you are locked out, DO NOTHING." echo "The firewall will automatically REVERT in 15 seconds." echo "" echo -e "${GREEN}If you can read this and your connection works:${NC}" echo -e "Press ${CYAN}ENTER${NC} now to CONFIRM and KEEP the changes." echo "OR press Ctrl+C to keep changes without saving (script exits)." echo "" if read -t 15 -p "Waiting for confirmation... "; then # User pressed Enter (Connection is good) echo "" echo -e "${GREEN}Connection confirmed!${NC}" echo "" read -p "Do you want to save these rules PERMANENTLY now? [Y/n]: " save_choice save_choice=${save_choice:-Y} # Default to Yes if [[ "$save_choice" =~ ^[Yy]$ ]]; then echo "Saving to permanent configuration..." firewall-cmd --runtime-to-permanent echo -e "${GREEN}Configuration Saved.${NC}" else echo "Rules kept in RUNTIME only. They will be lost on reboot." fi else # Timeout occurred (User likely locked out) echo "" echo -e "${RED}Timeout reached! Reverting changes...${NC}" # Revert Logic echo "1. Reloading permanent configuration..." firewall-cmd --reload >/dev/null 2>&1 echo "2. Ensuring SSH is accessible (Failsafe)..." firewall-cmd --add-service=ssh >/dev/null 2>&1 echo -e "${YELLOW}Firewall reverted to previous state (plus global SSH allow).${NC}" fi exit 0