Skip to content

Commit bffb501

Browse files
Merge pull request #34 from StrangeRanger/dev
Enhance UFW error handling and simplify documentation
2 parents 5bb5f9a + acf9dea commit bffb501

2 files changed

Lines changed: 87 additions & 43 deletions

File tree

hardening/UFW Cloudflare/CHANGELOG.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,39 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## v1.0.3 - 2026-05-21
8+
9+
### Added
10+
11+
- Add a diagnostic `ERR` trap that reports the failing command and line number before exiting.
12+
13+
### Changed
14+
15+
- Enable strict Bash error handling with `set -euo pipefail`.
16+
- Move the UFW active-status check after the user confirmation prompt.
17+
- Create the UFW backup archive as a gzip-compressed tar archive.
18+
- Reuse the Cloudflare UFW comment constant when reading existing Cloudflare-marked rules.
19+
20+
### Fixed
21+
22+
- Replace the `yes | ufw delete` pipeline with a single `y` response to avoid `SIGPIPE` failures under `pipefail`.
23+
- Use the `-z` flag when restoring archived backup files to handle gzip-compressed tar archives correctly.
24+
25+
## v1.0.2 - 2026-05-20
26+
27+
### Changed
28+
29+
- Track active rule changes with boolean.
30+
31+
### Fixed
32+
33+
- Fix duplicate "Waiting X second for changes to take effect" when no existing Cloudflare rules are present.
34+
735
## v1.0.1 - 2025-08-10
836

937
### Fixed
1038

11-
- Remove temp files on exit to avoid leftovers
39+
- Remove temp files on exit to avoid leftovers.
1240

1341
## v1.0.0 - 2025-08-09
1442

hardening/UFW Cloudflare/ufw-cloudflare.bash

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
#
33
# Set up UFW to only allow HTTP and HTTPS traffic from Cloudflare's IP ranges.
44
#
5-
# Version: v1.0.1
5+
# Version: v1.0.3
66
# License: MIT License
77
# Copyright (c) 2024-2026 Hunter T. (StrangeRanger)
88
#
99
############################################################################################
10+
set -euo pipefail
1011
####[ Global Variables ]####################################################################
1112

1213

@@ -27,29 +28,29 @@ readonly C_RED=$'\033[1;31m'
2728
readonly C_NC=$'\033[0m'
2829

2930
readonly C_ERROR="${C_RED}ERROR:${C_NC} "
30-
readonly C_SUCC="${C_GREEN}==>${C_NC} "
3131
readonly C_WARN="${C_YELLOW}==>${C_NC} "
32+
readonly C_SUCC="${C_GREEN}==>${C_NC} "
3233
readonly C_INFO="${C_BLUE}==>${C_NC} "
3334
readonly C_NOTE="${C_CYAN}==>${C_NC} "
3435

3536
current_cloudflare_rule_numbers=()
3637
current_cloudflare_ip_ranges=()
3738
new_cloudflare_ip_ranges=()
38-
stage=0
39+
modifications_in_progress=false
3940

4041

4142
####[ Function ]############################################################################
4243

4344

4445
####
45-
# Cleanly exit the script by removing temporary files, restoring backups if needed, and
46-
# displaying a message based on the exit code.
47-
#
48-
# PARAMETERS:
49-
# - $1: exit_code (Required)
46+
# Remove temporary files, restore backups if needed, and display a message based on the exit
47+
# code.
5048
clean_exit() {
5149
local exit_code="$1"
5250

51+
trap - ERR
52+
set +e
53+
5354
case "$exit_code" in
5455
0|1) echo "" ;;
5556
129) echo -e "\n\n${C_WARN}Hangup signal detected (SIGHUP)" ;;
@@ -58,21 +59,22 @@ clean_exit() {
5859
*) echo -e "\n\n${C_WARN}Exiting with code: $exit_code" ;;
5960
esac
6061

61-
case $stage in
62-
2|3|4)
63-
echo "${C_WARN}Interrupt occurred during stage '$stage'; incomplete changes"
64-
echo "${C_INFO}Temporarily disabling UFW..."
65-
ufw disable
66-
echo "${C_INFO}Restoring previous UFW rules..."
67-
sudo tar -C /etc -xf "$C_UFW_BACKUP_ARCHIVE"
68-
echo "${C_INFO}Re-enabling UFW..."
69-
ufw enable
70-
echo "${C_INFO}Displaying current UFW status..."
71-
echo "---"
72-
ufw status verbose
73-
echo "---"
74-
;;
75-
esac
62+
# Check if we need to restore the original configurations.
63+
if [[ $modifications_in_progress == true ]]; then
64+
echo "${C_INFO}Temporarily disabling UFW..."
65+
ufw disable
66+
67+
echo "${C_INFO}Restoring previous UFW rules..."
68+
tar -C /etc -xzf "$C_UFW_BACKUP_ARCHIVE"
69+
70+
echo "${C_INFO}Re-enabling UFW..."
71+
ufw enable
72+
73+
echo "${C_INFO}Displaying current UFW status..."
74+
echo "---"
75+
ufw status verbose
76+
echo "---"
77+
fi
7678

7779
if [[ -d "$C_TMP_DIR" ]]; then
7880
echo "${C_INFO}Cleaning up..."
@@ -83,13 +85,24 @@ clean_exit() {
8385
exit "$exit_code"
8486
}
8587

88+
# shellcheck disable=SC2329,SC2317
89+
# These appear to be false positives. The function is intended to be used in the 'ERR'
90+
# trap handler, and the exit code is passed implicitly via the special variable '$?'.
91+
on_err() {
92+
local exit_code=$?
93+
94+
echo "${C_ERROR}Command failed at line ${BASH_LINENO[0]}: ${BASH_COMMAND}" >&2
95+
clean_exit "$exit_code"
96+
}
97+
8698

8799
####[ Trapping Logic ]######################################################################
88100

89101

90102
trap 'clean_exit 129' SIGHUP
91103
trap 'clean_exit 130' SIGINT
92104
trap 'clean_exit 143' SIGTERM
105+
trap 'on_err' ERR
93106

94107

95108
####[ Prepping ]############################################################################
@@ -106,17 +119,21 @@ fi
106119

107120
read -rp "${C_NOTE}We will now configure Cloudflare UFW rules. Press [Enter] to continue."
108121

122+
echo "${C_INFO}Checking UFW status..."
123+
if ! ufw status | grep -q '^Status: active$'; then
124+
echo "${C_ERROR}UFW is not active"
125+
clean_exit 1
126+
fi
127+
109128
###
110129
### [ Initial Setup ]
111130
###
112131

113-
stage=1
114-
115132
echo "${C_INFO}Retrieving current Cloudflare IP rules from UFW..."
116133
while IFS= read -r line; do
117134
read -ra fields <<< "$line"
118135
current_cloudflare_ip_ranges+=("${fields[2]}")
119-
done < <(sudo ufw status | grep "Cloudflare IP")
136+
done < <(ufw status | grep "$C_CLOUDFLARE_UFW_COMMENT")
120137
unset fields
121138

122139
echo "${C_INFO}Retrieving new Cloudflare IP ranges..."
@@ -127,28 +144,27 @@ mapfile -t new_cloudflare_ip_ranges < <(
127144
)
128145

129146
echo "${C_INFO}Creating UFW backup archive at: $C_UFW_BACKUP_ARCHIVE"
130-
tar -C /etc -cf "$C_UFW_BACKUP_ARCHIVE" ufw
147+
tar -C /etc -czf "$C_UFW_BACKUP_ARCHIVE" ufw
131148

132149
###
133150
### Add temporary rule to prevent traffic disruption.
134151
###
135152

136-
stage=2
153+
modifications_in_progress=true
137154

138155
echo "${C_INFO}Temporarily opening ports 80 and 443 from any IP address..."
139156
if ! ufw allow from any to any port 80,443 proto tcp comment "Temporary rule"; then
140157
echo "${C_ERROR}Failed to add temporary rule" >&2
141158
clean_exit 1
142159
fi
143160

161+
echo "${C_NOTE}Waiting '$C_SLEEP_TIME' second for changes to take effect..."
162+
sleep "$C_SLEEP_TIME"
163+
144164
###
145165
### Remove the existing Cloudflare IP ranges to allow new ones.
146166
###
147167

148-
stage=3
149-
echo "${C_NOTE}Waiting '$C_SLEEP_TIME' second for changes to take effect..."
150-
sleep "$C_SLEEP_TIME"
151-
152168
if (( ${#current_cloudflare_ip_ranges[@]} != 0 )); then
153169
echo "${C_INFO}Removing the existing Cloudflare IP ranges..."
154170

@@ -168,8 +184,11 @@ if (( ${#current_cloudflare_ip_ranges[@]} != 0 )); then
168184
)
169185

170186
for rule_num in "${current_cloudflare_rule_numbers[@]}"; do
171-
yes | ufw delete "$rule_num"
187+
echo "y" | ufw delete "$rule_num"
172188
done
189+
190+
echo "${C_NOTE}Waiting '$C_SLEEP_TIME' second for changes to take effect..."
191+
sleep "$C_SLEEP_TIME"
173192
fi
174193

175194
unset current_cloudflare_rule_numbers
@@ -178,10 +197,6 @@ unset current_cloudflare_rule_numbers
178197
### Add the new Cloudflare IP ranges.
179198
###
180199

181-
stage=4
182-
echo "${C_NOTE}Waiting '$C_SLEEP_TIME' second for changes to take effect..."
183-
sleep "$C_SLEEP_TIME"
184-
185200
echo "${C_INFO}Adding the new Cloudflare IPv4 and IPv6 ranges..."
186201
for ip in "${new_cloudflare_ip_ranges[@]}"; do
187202
echo "${C_INFO} Adding rule for '$ip'..."
@@ -192,21 +207,22 @@ for ip in "${new_cloudflare_ip_ranges[@]}"; do
192207
fi
193208
done
194209

210+
echo "${C_NOTE}Waiting '$C_SLEEP_TIME' second for changes to take effect..."
211+
sleep "$C_SLEEP_TIME"
212+
195213
###
196214
### Perform the last modifications to UFW.
197215
###
198216

199-
stage=5
200-
echo "${C_NOTE}Waiting '$C_SLEEP_TIME' second for changes to take effect..."
201-
sleep "$C_SLEEP_TIME"
202-
203217
echo "${C_INFO}Removing temporary rules..."
204218
if ! ufw delete allow from any to any port 80,443 proto tcp comment "Temporary rule"; then
205219
echo "${C_ERROR}Failed to remove temporary rule" >&2
206220
echo "${C_NOTE}Please check your UFW configuration and remove it manually"
207221
fi
208222

209-
sleep "$C_SLEEP_TIME"
223+
modifications_in_progress=false
224+
210225
echo "${C_NOTE}Waiting '$C_SLEEP_TIME' second for changes to take effect..."
226+
sleep "$C_SLEEP_TIME"
211227
echo "${C_SUCC}Finished setting up UFW with Cloudflare IP ranges"
212228
clean_exit 0

0 commit comments

Comments
 (0)