Explorar el Código

feat(Firewall): Added the ability to order firewall rules, changing their precedence within the iptables (#5080)

* Added the ability to order firewall rules, changing their precedence within the iptables

* Implemented @divinity76's suggestion

* Prettier fixes

* Made shellcheck happy

---------

Co-authored-by: Christoph Schläpfer <c.schlaepfer@datact.ch>
Christoph Schlaepfer hace 7 meses
padre
commit
7b3a58e36c

+ 138 - 0
bin/v-move-firewall-rule

@@ -0,0 +1,138 @@
+#!/bin/bash
+# info: change firewall rule
+# options: RULE DIRECTION
+#
+# example: v-move-firewall-rule 4 up
+#
+# This function is used for moving an existing firewall rule.
+# Direction can be either "up" or "down".
+#
+# If the direction is "up", the rule will be moved to a lower
+# number (higher priority).
+#
+# If the direction is "down", the rule will be moved to a
+# higher number (lower priority).
+
+#----------------------------------------------------------#
+#                Variables & Functions                     #
+#----------------------------------------------------------#
+
+# Argument definition
+source_rule=$1
+direction=$(echo $2 | tr '[:lower:]' '[:upper:]')
+
+# Includes
+# shellcheck source=/etc/hestiacp/hestia.conf
+source /etc/hestiacp/hestia.conf
+# shellcheck source=/usr/local/hestia/func/main.sh
+source $HESTIA/func/main.sh
+# load config file
+source_conf "$HESTIA/conf/hestia.conf"
+
+get_rule_id() {
+	local old_rule_no=$1
+	local direction=$2
+	local new_rule_no
+	if [[ "$direction" == "UP" ]]; then
+		new_rule_no=$((old_rule_no - 1))
+	else
+		new_rule_no=$((old_rule_no + 1))
+	fi
+	echo "$new_rule_no"
+}
+
+# Sort function
+sort_fw_rules() {
+	cat $HESTIA/data/firewall/rules.conf \
+		| sort -n -k 2 -t \' > $HESTIA/data/firewall/rules.conf.tmp
+	mv -f $HESTIA/data/firewall/rules.conf.tmp \
+		$HESTIA/data/firewall/rules.conf
+}
+#----------------------------------------------------------#
+#                    Verifications                         #
+#----------------------------------------------------------#
+
+check_args '2' "$#" 'RULE DIRECTION'
+is_format_valid 'rule' "$source_rule"
+if [ ! -z "$comment" ]; then
+	is_format_valid 'comment'
+fi
+is_system_enabled "$FIREWALL_SYSTEM" 'FIREWALL_SYSTEM'
+is_object_valid '../../../data/firewall/rules' 'RULE' "$source_rule"
+
+# Check if the direction is valid
+if [[ "$direction" != "UP" && "$direction" != "DOWN" ]]; then
+	echo "Invalid direction. Use 'up' or 'down'."
+	exit 1
+fi
+
+# Check if the rule can be moved up (if it's not the first rule)
+if [[ "$direction" == "UP" && "$source_rule" -eq 1 ]]; then
+	echo "Cannot move rule up. It's already the first rule."
+	exit 1
+fi
+# Check if the rule can be moved down (if it's not the last rule)
+last_rule=$(tail -n 1 $HESTIA/data/firewall/rules.conf | cut -d "'" -f 2)
+if [[ "$direction" == "DOWN" && "$source_rule" -eq "$last_rule" ]]; then
+	echo "Cannot move rule down. It's already the last rule."
+	exit 1
+fi
+
+# Perform verification if read-only mode is enabled
+check_hestia_demo_mode
+
+#----------------------------------------------------------#
+#                       Action                             #
+#----------------------------------------------------------#
+
+target_rule=$(get_rule_id "$source_rule" "$direction")
+
+# Get the rule that will be moved
+parse_object_kv_list $(grep "RULE='$source_rule'" $HESTIA/data/firewall/rules.conf)
+
+# Generating timestamp
+time_n_date=$(date +'%T %F')
+time=$(echo "$time_n_date" | cut -f 1 -d \ )
+date=$(echo "$time_n_date" | cut -f 2 -d \ )
+
+# Concatenating firewall rule
+source_str="RULE='$target_rule' ACTION='$ACTION' PROTOCOL='$PROTOCOL' PORT='$PORT'"
+source_str="$source_str IP='$IP' COMMENT='$COMMENT' SUSPENDED='$SUSPENDED'"
+source_str="$source_str TIME='$time' DATE='$date'"
+
+# Deleting old source rule
+sed -i "/RULE='$source_rule' /d" $HESTIA/data/firewall/rules.conf
+
+parse_object_kv_list $(grep "RULE='$target_rule'" $HESTIA/data/firewall/rules.conf)
+
+# Generating timestamp
+time_n_date=$(date +'%T %F')
+time=$(echo "$time_n_date" | cut -f 1 -d \ )
+date=$(echo "$time_n_date" | cut -f 2 -d \ )
+
+# Concatenating firewall rule
+target_str="RULE='$source_rule' ACTION='$ACTION' PROTOCOL='$PROTOCOL' PORT='$PORT'"
+target_str="$target_str IP='$IP' COMMENT='$COMMENT' SUSPENDED='$SUSPENDED'"
+target_str="$target_str TIME='$time' DATE='$date'"
+
+# Deleting old target rule
+sed -i "/RULE='$target_rule' /d" $HESTIA/data/firewall/rules.conf
+
+# Adding new source and target rules
+echo "$source_str" >> $HESTIA/data/firewall/rules.conf
+echo "$target_str" >> $HESTIA/data/firewall/rules.conf
+
+# Sorting firewall rules by id number
+sort_fw_rules
+
+# Updating system firewall
+$BIN/v-update-firewall
+
+#----------------------------------------------------------#
+#                       Hestia                             #
+#----------------------------------------------------------#
+
+# Logging
+$BIN/v-log-action "system" "Info" "Firewall" "Firewall rule $source_rule moved $direction (Source rule: $source_rule, Target rule: $target_rule)."
+log_event "$OK" "$ARGUMENTS"
+exit

+ 3 - 2
bin/v-update-firewall

@@ -110,7 +110,9 @@ for line in $(sort -r -n -k 2 -t \' $rules); do
 
 		if [[ "$IP" =~ ^ipset: ]]; then
 			ipset_name="${IP#ipset:}"
-			$("$BIN/v-list-firewall-ipset" plain | grep "^$ipset_name\s" > /dev/null) || log_event $E_NOTEXIST "IPset IP list ($ipset_name) not found"
+			if ! "$BIN/v-list-firewall-ipset" plain | grep "^$ipset_name\s" > /dev/null; then
+				log_event $E_NOTEXIST "IPset IP list ($ipset_name) not found"
+			fi
 			ip="-m set --match-set '${ipset_name}' src"
 		else
 			ip="-s $IP"
@@ -133,7 +135,6 @@ for line in $(sort -r -n -k 2 -t \' $rules); do
 			else
 				port="-m multiport --dports 20,21,12000:12100"
 			fi
-			ftp="yes"
 		fi
 
 		# Adding firewall rule

+ 8 - 1
func/main.sh

@@ -1050,6 +1050,13 @@ is_ip_status_format_valid() {
 	fi
 }
 
+# Comment validator
+is_comment_format_valid() {
+	if ! [[ "$1" =~ ^[[:alnum:]][[:alnum:][:space:]._-]{0,64}[[:alnum:]]$ ]]; then
+		check_result "$E_INVALID" "invalid $2 format :: $1"
+	fi
+}
+
 # Cron validator
 is_cron_format_valid() {
 	limit=59
@@ -1203,7 +1210,7 @@ is_format_valid() {
 				charset) is_object_format_valid "$arg" "$arg_name" ;;
 				charsets) is_common_format_valid "$arg" 'charsets' ;;
 				chain) is_object_format_valid "$arg" 'chain' ;;
-				comment) is_object_format_valid "$arg" 'comment' ;;
+				comment) is_comment_format_valid "$arg" 'comment' ;;
 				cron_command) is_cron_command_valid_format "$arg" ;;
 				database) is_database_format_valid "$arg" 'database' ;;
 				day) is_cron_format_valid "$arg" $arg_name ;;

+ 33 - 0
web/move/firewall/index.php

@@ -0,0 +1,33 @@
+<?php
+use function Hestiacp\quoteshellarg\quoteshellarg;
+
+ob_start();
+
+session_start();
+include $_SERVER["DOCUMENT_ROOT"] . "/inc/main.php";
+
+// Check token
+verify_csrf($_GET);
+
+// Check user
+if ($_SESSION["userContext"] != "admin") {
+	header("Location: /list/user");
+	exit();
+}
+
+if (!empty($_GET["rule"])) {
+	$v_rule = quoteshellarg($_GET["rule"]);
+	$v_direction = quoteshellarg($_GET["direction"]);
+	exec(HESTIA_CMD . "v-move-firewall-rule " . $v_rule . " " . $v_direction, $output, $return_var);
+}
+check_return_code($return_var, $output);
+unset($output);
+
+$back = getenv("HTTP_REFERER");
+if (!empty($back)) {
+	header("Location: " . $back);
+	exit();
+}
+
+header("Location: /list/firewall/");
+exit();

+ 48 - 0
web/templates/pages/list_firewall.php

@@ -67,6 +67,8 @@
 			<div class="units-table-cell">
 				<input type="checkbox" class="js-toggle-all-checkbox" title="<?= _("Select all") ?>">
 			</div>
+			<div class="units-table-cell"><?= _("Pos") ?></div>
+			<div class="units-table-cell"></div>
 			<div class="units-table-cell"><?= _("Action") ?></div>
 			<div class="units-table-cell"></div>
 			<div class="units-table-cell"><?= _("Comment") ?></div>
@@ -79,6 +81,15 @@
 		<?php
 			foreach ($data as $key => $value) {
 				++$i;
+				if ($i === 1) {
+					$move_up_enabled = false;
+				} elseif ($i == count($data)) {
+					$move_up_enabled = true;
+					$move_down_enabled = false;
+				} else {
+					$move_up_enabled = true;
+					$move_down_enabled = true;
+				}
 				if ($data[$key]['SUSPENDED'] == 'yes') {
 					$status = 'suspended';
 					$spnd_action = 'unsuspend';
@@ -107,6 +118,43 @@
 						<label for="check<?= $i ?>" class="u-hide-desktop"><?= _("Select") ?></label>
 					</div>
 				</div>
+				<div class="units-table-cell units-table-heading-cell u-text-bold">
+					<span class="u-hide-desktop"><?= _("Position") ?>:</span>
+					<a href="/edit/firewall/?rule=<?= $key ?>&token=<?= $_SESSION["token"] ?>" title="<?= _("Edit Firewall Rule") ?>">
+						<?php
+							$rule = $key;
+						?>
+						<?= $rule ?>
+					</a>
+				</div>
+				<div class="units-table-cell" style="padding-left: 0;padding-right: 0;">
+					<ul class="units-table-row-actions">
+						<li class="units-table-row-action shortcut-up" data-key-action="js">
+							<a
+								class="units-table-row-action-link data-controls js-confirm-action"
+								style="<?= $move_up_enabled ? "display:block!important" : "display:none!important" ?>"
+								href="/move/firewall/?rule=<?= $key ?>&direction=up&token=<?= $_SESSION["token"] ?>"
+								title="<?= _("Move Firewall Rule Up") ?>"
+								data-confirm-title="<?= _("Move Up") ?>"
+								data-confirm-message="<?= sprintf(_("Are you sure you want to move rule #%s up?"), $key) ?>">
+								<i class="fas fa-arrow-up icon-blue"></i>
+								<span class="u-hide-desktop"><?= _("Move Firewall Rule Up") ?></span>
+							</a>
+						</li>
+						<li class="units-table-row-action shortcut-down" data-key-action="js">
+							<a
+								class="units-table-row-action-link data-controls js-confirm-action"
+								style="<?= $move_down_enabled ? "" : "display:block!important" ?>"
+								href="/move/firewall/?rule=<?= $key ?>&direction=down&token=<?= $_SESSION["token"] ?>"
+								title="<?= _("Move Firewall Rule Down") ?>"
+								data-confirm-title="<?= _("Move Down") ?>"
+								data-confirm-message="<?= sprintf(_("Are you sure you want to move rule #%s down?"), $key) ?>">
+								<i class="fas fa-arrow-down icon-blue"></i>
+								<span class="u-hide-desktop"><?= _("Move Firewall Rule Down") ?></span>
+							</a>
+						</li>
+					</ul>
+				</div>
 				<div class="units-table-cell units-table-heading-cell u-text-bold">
 					<span class="u-hide-desktop"><?= _("Action") ?>:</span>
 					<a href="/edit/firewall/?rule=<?= $key ?>&token=<?= $_SESSION["token"] ?>" title="<?= _("Edit Firewall Rule") ?>">