-
-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathfortress_improved.sh
More file actions
2681 lines (2268 loc) · 92.9 KB
/
fortress_improved.sh
File metadata and controls
2681 lines (2268 loc) · 92.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
# FORTRESS.SH - Linux Security Hardening Script
# Version: 5.1 - Critical Fixes & Enhanced Compatibility
# Author: captainzero93
# GitHub: https://github.com/captainzero93/security_harden_linux
# Optimized for Ubuntu 24.04+, Debian 13+
# Last Updated: 2026-01-31
#
# MAJOR CHANGES IN v5.1:
# - FIXED: Docker networking broken by IP forwarding disable (Issue #10)
# - FIXED: Browser launch failures from /dev/shm noexec (Issue #8)
# - FIXED: Custom configuration file not implemented (Issue #11)
# - ADDED: Docker detection and conditional IP forwarding
# - ADDED: Full configuration file support with fortress.conf
# - ADDED: Pre-flight application compatibility checking
# - IMPROVED: Desktop vs server mode intelligence
set -euo pipefail
#=============================================================================
# GLOBAL CONFIGURATION
#=============================================================================
readonly VERSION="5.1"
readonly SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly LOG_FILE="/var/log/fortress_hardening.log"
readonly REPORT_FILE="/root/fortress_report_$(date +%Y%m%d_%H%M%S).html"
readonly BACKUP_DIR="/root/fortress_backups_$(date +%Y%m%d_%H%M%S)"
CONFIG_FILE="${SCRIPT_DIR}/fortress.conf"
CONFIG_FILE_OVERRIDE=""
readonly TEMP_DIR=$(mktemp -d -t fortress.XXXXXXXXXX)
# Runtime configuration
VERBOSE=false
DRY_RUN=false
INTERACTIVE=true
EXPLAIN_MODE=false
ENABLE_MODULES=""
DISABLE_MODULES=""
SECURITY_LEVEL="moderate"
IS_DESKTOP=false
CURRENT_MODULE=""
# NEW in v5.1: Compatibility flags
DOCKER_DETECTED=false
ALLOW_DOCKER_FORWARDING=false
ALLOW_BROWSER_SHAREDMEM=false
FORCE_DESKTOP_MODE=false
FORCE_SERVER_MODE=false
GENERATE_CONFIG=false
# NEW in v5.1: Application detection
declare -a DETECTED_BROWSERS=()
declare -a DETECTED_CONTAINERS=()
declare -a DETECTED_VMS=()
# Tracking
declare -a EXECUTED_MODULES=()
declare -a FAILED_MODULES=()
declare -A MODULE_EXPLANATIONS=()
# Color codes
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly CYAN='\033[0;36m'
readonly MAGENTA='\033[0;35m'
readonly NC='\033[0m'
#=============================================================================
# SECURITY MODULES WITH DETAILED EXPLANATIONS
#=============================================================================
declare -A SECURITY_MODULES=(
["system_update"]="Update system packages"
["audit"]="Configure auditd logging"
["secure_shared_memory"]="Secure shared memory"
["ssh_hardening"]="Harden SSH configuration"
["automatic_updates"]="Enable automatic security updates"
["firewall"]="Configure UFW firewall"
["sysctl"]="Configure kernel parameters"
["password_policy"]="Set strong password policies"
["ntp"]="Configure time synchronization"
["apparmor"]="Setup AppArmor profiles"
["boot_security"]="Verify secure boot configuration"
["root_access"]="Disable direct root login"
["packages"]="Remove unnecessary packages"
["usb_protection"]="Configure USB device policies"
["filesystems"]="Disable unused filesystems"
["package_verification"]="Setup package integrity checking"
["fail2ban"]="Setup Fail2Ban (optional)"
)
# Module dependencies
declare -A MODULE_DEPS=(
["system_update"]=""
["audit"]="system_update"
["secure_shared_memory"]=""
["ssh_hardening"]="system_update"
["automatic_updates"]="system_update"
["firewall"]=""
["sysctl"]=""
["password_policy"]=""
["ntp"]="system_update"
["apparmor"]="system_update"
["boot_security"]=""
["root_access"]=""
["packages"]=""
["usb_protection"]=""
["filesystems"]=""
["package_verification"]="system_update"
["fail2ban"]="system_update firewall ssh_hardening"
)
#=============================================================================
# UTILITY FUNCTIONS
#=============================================================================
trap cleanup EXIT
cleanup() {
if [[ -d "${TEMP_DIR}" ]]; then
rm -rf "${TEMP_DIR}"
fi
}
log() {
local level="$1"
shift
local message="$*"
local timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
local log_entry="${timestamp} [${level}] [${CURRENT_MODULE:-SYSTEM}]: ${message}"
echo "${log_entry}" | sudo tee -a "${LOG_FILE}" >/dev/null 2>&1 || true
case "${level}" in
ERROR)
echo -e "${RED}[ERROR]${NC} ${message}" >&2
;;
WARNING|WARN)
echo -e "${YELLOW}[WARNING]${NC} ${message}"
;;
SUCCESS)
echo -e "${GREEN}[✓]${NC} ${message}"
;;
INFO)
$VERBOSE && echo -e "${BLUE}[INFO]${NC} ${message}" || true
;;
EXPLAIN)
echo -e "${CYAN}[WHY]${NC} ${message}"
;;
*)
echo "${message}"
;;
esac
}
explain() {
if [[ "${EXPLAIN_MODE}" == "true" ]]; then
echo ""
echo -e "${CYAN}╔════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ EXPLANATION: ${1}${NC}"
echo -e "${CYAN}╠════════════════════════════════════════════════════════════════╣${NC}"
shift
for line in "$@"; do
echo -e "${CYAN}║${NC} ${line}"
done
echo -e "${CYAN}╚════════════════════════════════════════════════════════════════╝${NC}"
echo ""
if [[ "${INTERACTIVE}" == "true" ]]; then
read -p "Press Enter to continue..." -r
fi
fi
}
check_permissions() {
if [[ "${EUID}" -ne 0 ]]; then
echo -e "${RED}This script must be run with sudo privileges.${NC}"
echo "Please run: sudo $0"
exit 1
fi
}
detect_desktop() {
# NEW in v5.1: Check for forced modes
if [[ "${FORCE_DESKTOP_MODE}" == "true" ]]; then
IS_DESKTOP=true
log INFO "Desktop mode FORCED via configuration"
return 0
fi
if [[ "${FORCE_SERVER_MODE}" == "true" ]]; then
IS_DESKTOP=false
log INFO "Server mode FORCED via configuration"
return 0
fi
# Continue with existing desktop detection logic
if [[ -n "${XDG_CURRENT_DESKTOP:-}" ]] || [[ -n "${DESKTOP_SESSION:-}" ]] || \
systemctl is-active --quiet display-manager 2>/dev/null; then
IS_DESKTOP=true
log INFO "Desktop environment detected: ${XDG_CURRENT_DESKTOP:-Unknown}"
else
IS_DESKTOP=false
log INFO "Server environment detected (no desktop)"
fi
}
backup_file() {
local file="$1"
if [[ "${DRY_RUN}" == "true" ]]; then
log INFO "[DRY RUN] Would backup: ${file}"
return 0
fi
if [[ -f "${file}" ]]; then
local backup_path="${BACKUP_DIR}${file}"
sudo mkdir -p "$(dirname "${backup_path}")"
sudo cp -a "${file}" "${backup_path}"
log INFO "Backed up: ${file} → ${backup_path}"
fi
}
execute_command() {
local description="$1"
shift
local command="$*"
if [[ "${DRY_RUN}" == "true" ]]; then
log INFO "[DRY RUN] ${description}: ${command}"
return 0
fi
log INFO "${description}"
eval "${command}"
}
wait_for_apt() {
local max_wait=300
local waited=0
while sudo fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 || \
sudo fuser /var/lib/apt/lists/lock >/dev/null 2>&1 || \
sudo fuser /var/cache/apt/archives/lock >/dev/null 2>&1; do
if [[ ${waited} -eq 0 ]]; then
log WARNING "Waiting for other package managers to finish..."
fi
sleep 2
waited=$((waited + 2))
if [[ ${waited} -ge ${max_wait} ]]; then
log ERROR "Timed out waiting for package manager"
return 1
fi
done
return 0
}
#=============================================================================
# NEW in v5.1: CONFIGURATION FILE SUPPORT
#=============================================================================
load_config() {
# Use override path if provided via -c/--config
if [[ -n "${CONFIG_FILE_OVERRIDE}" ]]; then
CONFIG_FILE="${CONFIG_FILE_OVERRIDE}"
fi
# Load configuration from fortress.conf if it exists
if [[ -f "${CONFIG_FILE}" ]]; then
log INFO "Loading configuration from ${CONFIG_FILE}"
# Save CLI-provided values so they take priority over config
local cli_verbose="${VERBOSE}"
local cli_dry_run="${DRY_RUN}"
local cli_interactive="${INTERACTIVE}"
local cli_explain="${EXPLAIN_MODE}"
local cli_security_level="${SECURITY_LEVEL}"
local cli_enable="${ENABLE_MODULES}"
local cli_disable="${DISABLE_MODULES}"
local cli_force_desktop="${FORCE_DESKTOP_MODE}"
local cli_force_server="${FORCE_SERVER_MODE}"
local cli_docker="${ALLOW_DOCKER_FORWARDING}"
local cli_browser="${ALLOW_BROWSER_SHAREDMEM}"
# Source the config file
# shellcheck source=/dev/null
source "${CONFIG_FILE}"
# Validate configuration
if [[ -n "${SECURITY_LEVEL:-}" ]]; then
case "${SECURITY_LEVEL}" in
low|moderate|high|paranoid) ;;
*)
log ERROR "Invalid SECURITY_LEVEL in config: ${SECURITY_LEVEL}"
log ERROR "Must be: low, moderate, high, or paranoid"
exit 1
;;
esac
fi
# CLI arguments override config file values
# Only apply config value if CLI value is still at its default
[[ "${cli_verbose}" == "true" ]] && VERBOSE=true
[[ "${cli_dry_run}" == "true" ]] && DRY_RUN=true
[[ "${cli_interactive}" == "false" ]] && INTERACTIVE=false
[[ "${cli_explain}" == "true" ]] && EXPLAIN_MODE=true
[[ "${cli_security_level}" != "moderate" ]] && SECURITY_LEVEL="${cli_security_level}"
[[ -n "${cli_enable}" ]] && ENABLE_MODULES="${cli_enable}"
[[ -n "${cli_disable}" ]] && DISABLE_MODULES="${cli_disable}"
[[ "${cli_force_desktop}" == "true" ]] && FORCE_DESKTOP_MODE=true
[[ "${cli_force_server}" == "true" ]] && FORCE_SERVER_MODE=true
[[ "${cli_docker}" == "true" ]] && ALLOW_DOCKER_FORWARDING=true
[[ "${cli_browser}" == "true" ]] && ALLOW_BROWSER_SHAREDMEM=true
log SUCCESS "Configuration loaded successfully"
else
log INFO "No configuration file found at ${CONFIG_FILE}, using defaults"
fi
}
generate_config_template() {
# Generate a template fortress.conf file
local output_file="${1:-${SCRIPT_DIR}/fortress.conf}"
cat > "${output_file}" << 'CONFIGEOF'
# FORTRESS.SH Configuration File v5.1
# =======================================================
# CLI arguments override these settings
# Documentation: https://github.com/captainzero93/security_harden_linux
# ===========================
# BASIC SETTINGS
# ===========================
# Security Level: low, moderate, high, paranoid
# - low: Minimal hardening, maximum compatibility
# - moderate: Balanced security and usability (recommended)
# - high: Maximum security, may break some applications
# - paranoid: Extreme security, requires careful configuration
SECURITY_LEVEL="moderate"
# Interactive prompts (set false for automation/scripts)
INTERACTIVE=true
# Verbose output (detailed logging)
VERBOSE=false
# Dry run mode (show what would be done without applying)
DRY_RUN=false
# Explain mode (show educational context for each action)
EXPLAIN_MODE=false
# ===========================
# COMPATIBILITY SETTINGS
# ===========================
# Docker Compatibility
# If true, enables IP forwarding required for Docker container networking
# Trade-off: Slightly reduced security, Docker works properly
ALLOW_DOCKER_FORWARDING=true
# Browser Shared Memory Compatibility
# If true, skips noexec on /dev/shm (required for Firefox/Chrome JIT)
# Trade-off: Attackers could execute code from /dev/shm, browsers work
ALLOW_BROWSER_SHAREDMEM=true
# Force Desktop Mode
# If true, applies desktop-friendly settings even if no DE detected
FORCE_DESKTOP_MODE=false
# Force Server Mode
# If true, applies strict server settings even if desktop detected
FORCE_SERVER_MODE=false
# ===========================
# MODULE CONTROL
# ===========================
# Enable only specific modules (comma-separated, leave empty for all)
# Example: ENABLE_MODULES="system_update,ssh_hardening,firewall,sysctl"
ENABLE_MODULES=""
# Disable specific modules (comma-separated)
# Example: DISABLE_MODULES="fail2ban,usb_protection,apparmor"
DISABLE_MODULES=""
# ===========================
# SSH HARDENING
# ===========================
# SSH Port (default 22, change for security through obscurity)
SSH_PORT=22
# Allowed SSH users (comma-separated, leave empty to allow all)
# Example: SSH_ALLOWED_USERS="admin,deploy,backup"
SSH_ALLOWED_USERS=""
# Maximum authentication attempts before disconnect
SSH_MAX_AUTH_TRIES=3
# ===========================
# FIREWALL SETTINGS
# ===========================
# UFW Default incoming policy: deny, reject, or allow
UFW_DEFAULT_INCOMING="deny"
# UFW Default outgoing policy: deny, reject, or allow
UFW_DEFAULT_OUTGOING="allow"
# Allow specific ports (comma-separated)
# Example: FIREWALL_ALLOW_PORTS="80,443,8080"
FIREWALL_ALLOW_PORTS=""
# ===========================
# PASSWORD POLICY
# ===========================
# Minimum password length
PASSWORD_MIN_LENGTH=12
# Password complexity requirements
PASSWORD_REQUIRE_UPPERCASE=true
PASSWORD_REQUIRE_LOWERCASE=true
PASSWORD_REQUIRE_DIGITS=true
PASSWORD_REQUIRE_SPECIAL=true
# Password history (prevent reuse of last N passwords)
PASSWORD_HISTORY=5
# ===========================
# AUDIT LOGGING
# ===========================
# Enable audit logging
ENABLE_AUDIT_LOGGING=true
# Audit log size (MB)
AUDIT_MAX_LOG_FILE=50
# Number of audit log files to keep
AUDIT_NUM_LOGS=5
# ===========================
# AUTOMATIC UPDATES
# ===========================
# Enable automatic security updates
ENABLE_AUTO_UPDATES=true
# Auto-reboot after updates (if required)
AUTO_REBOOT_IF_REQUIRED=false
# Auto-reboot time (24-hour format, e.g., "03:00")
AUTO_REBOOT_TIME="03:00"
# ===========================
# FAIL2BAN (OPTIONAL)
# ===========================
# Enable fail2ban (recommended only for SSH-accessible servers)
ENABLE_FAIL2BAN=false
# Ban time (seconds)
FAIL2BAN_BANTIME=3600
# Find time window (seconds)
FAIL2BAN_FINDTIME=600
# Max retry attempts
FAIL2BAN_MAXRETRY=5
# ===========================
# USB PROTECTION
# ===========================
# Enable USB device restrictions
ENABLE_USB_PROTECTION=false
# Allowed USB device IDs (comma-separated, format: vendor:product)
# Example: USB_ALLOWED_DEVICES="046d:c52b,1d6b:0002"
USB_ALLOWED_DEVICES=""
# ===========================
# ADVANCED SETTINGS
# ===========================
# Sysctl custom parameters (one per line in format: key=value)
# Example:
# SYSCTL_CUSTOM="
# net.ipv4.tcp_keepalive_time=600
# net.core.netdev_max_backlog=5000
# "
SYSCTL_CUSTOM=""
# AppArmor custom profiles directory
APPARMOR_CUSTOM_PROFILES="/etc/apparmor.d/local"
# Backup directory (defaults to /root/fortress_backups_TIMESTAMP)
# BACKUP_DIR="/custom/backup/location"
# Log file location
# LOG_FILE="/var/log/fortress_hardening.log"
CONFIGEOF
chmod 600 "${output_file}"
log SUCCESS "Configuration template generated: ${output_file}"
log INFO "Edit this file and run fortress_improved.sh to apply your settings"
echo ""
echo -e "${GREEN}Configuration template created:${NC} ${output_file}"
echo ""
echo "Next steps:"
echo "1. Edit fortress.conf with your preferred settings"
echo "2. Run: sudo ./fortress_improved.sh"
echo ""
}
#=============================================================================
# NEW in v5.1: APPLICATION DETECTION
#=============================================================================
detect_docker() {
# Detect if Docker is installed and running
log INFO "Checking for Docker installation..."
if command -v docker &>/dev/null; then
DOCKER_DETECTED=true
DETECTED_CONTAINERS+=("docker")
log INFO "Docker detected: $(docker --version 2>/dev/null || echo 'version unknown')"
# Check if Docker daemon is running
if systemctl is-active --quiet docker 2>/dev/null || pgrep dockerd >/dev/null; then
log INFO "Docker daemon is running"
# Check for running containers
local container_count=$(docker ps -q 2>/dev/null | wc -l)
if [[ $container_count -gt 0 ]]; then
log INFO "Found $container_count running Docker container(s)"
fi
else
log WARN "Docker is installed but not running"
fi
else
log INFO "Docker not detected"
fi
# Check for Podman
if command -v podman &>/dev/null; then
DETECTED_CONTAINERS+=("podman")
log INFO "Podman detected: $(podman --version 2>/dev/null || echo 'version unknown')"
fi
# Check for LXC/LXD
if command -v lxc &>/dev/null || command -v lxd &>/dev/null; then
DETECTED_CONTAINERS+=("lxc")
log INFO "LXC/LXD detected"
fi
}
detect_browsers() {
# Detect installed web browsers
log INFO "Checking for installed web browsers..."
local browsers=(
"firefox:Firefox"
"firefox-esr:Firefox ESR"
"chromium:Chromium"
"chromium-browser:Chromium"
"google-chrome:Google Chrome"
"brave-browser:Brave"
"microsoft-edge:Edge"
"vivaldi:Vivaldi"
"opera:Opera"
)
for browser_pair in "${browsers[@]}"; do
local cmd="${browser_pair%%:*}"
local name="${browser_pair##*:}"
if command -v "$cmd" &>/dev/null; then
DETECTED_BROWSERS+=("$name")
log INFO "Browser detected: $name"
fi
done
if [[ ${#DETECTED_BROWSERS[@]} -eq 0 ]]; then
log INFO "No browsers detected"
else
log INFO "Total browsers detected: ${#DETECTED_BROWSERS[@]}"
fi
}
detect_virtualization() {
# Detect virtualization platforms
log INFO "Checking for virtualization platforms..."
if command -v VBoxManage &>/dev/null; then
DETECTED_VMS+=("VirtualBox")
log INFO "VirtualBox detected"
fi
if command -v qemu-system-x86_64 &>/dev/null; then
DETECTED_VMS+=("QEMU")
log INFO "QEMU detected"
fi
if command -v vmware &>/dev/null || command -v vmrun &>/dev/null; then
DETECTED_VMS+=("VMware")
log INFO "VMware detected"
fi
if [[ ${#DETECTED_VMS[@]} -eq 0 ]]; then
log INFO "No virtualization platforms detected"
fi
}
detect_critical_applications() {
# Run all application detection functions
log INFO "Performing pre-flight application detection..."
echo ""
detect_docker
detect_browsers
detect_virtualization
echo ""
log INFO "Pre-flight detection complete"
}
prompt_docker_compatibility() {
# Prompt user about Docker IP forwarding
if [[ "${DOCKER_DETECTED}" == "true" ]] && [[ "${INTERACTIVE}" == "true" ]]; then
echo ""
echo -e "${YELLOW}════════════════════════════════════════════════════════${NC}"
echo -e "${YELLOW}⚠️ DOCKER DETECTED${NC}"
echo -e "${YELLOW}════════════════════════════════════════════════════════${NC}"
echo ""
echo "Docker requires IP forwarding to be ENABLED to route container traffic."
echo "However, IP forwarding increases attack surface by allowing packet routing."
echo ""
echo "Your options:"
echo " ${GREEN}Y${NC} = Enable IP forwarding (Docker works, slight security reduction)"
echo " ${RED}N${NC} = Disable IP forwarding (Maximum security, Docker networking broken)"
echo ""
read -p "Allow IP forwarding for Docker? [Y/n]: " -r
if [[ $REPLY =~ ^[Nn]$ ]]; then
ALLOW_DOCKER_FORWARDING=false
log WARN "User chose to disable IP forwarding - Docker networking will be broken"
echo ""
echo -e "${RED}WARNING: Docker container networking will NOT work.${NC}"
echo "You can re-enable later by manually editing /etc/sysctl.d/99-fortress.conf"
else
ALLOW_DOCKER_FORWARDING=true
log INFO "User enabled IP forwarding for Docker compatibility"
fi
echo ""
elif [[ "${DOCKER_DETECTED}" == "true" ]] && [[ "${INTERACTIVE}" == "false" ]]; then
# Non-interactive mode with Docker detected
if [[ "${ALLOW_DOCKER_FORWARDING}" == "true" ]]; then
log INFO "Non-interactive mode: IP forwarding enabled for Docker (from config)"
else
log WARN "Non-interactive mode: IP forwarding disabled - Docker may not work"
fi
fi
}
prompt_browser_compatibility() {
# Prompt user about /dev/shm noexec
if [[ "${IS_DESKTOP}" == "true" ]] && [[ ${#DETECTED_BROWSERS[@]} -gt 0 ]] && [[ "${INTERACTIVE}" == "true" ]]; then
echo ""
echo -e "${YELLOW}════════════════════════════════════════════════════════${NC}"
echo -e "${YELLOW}⚠️ BROWSERS DETECTED${NC}"
echo -e "${YELLOW}════════════════════════════════════════════════════════${NC}"
echo ""
echo "Detected browsers: ${DETECTED_BROWSERS[*]}"
echo ""
echo "Applying 'noexec' to /dev/shm prevents executing code from shared memory."
echo "This BREAKS modern browsers (Firefox, Chrome) that use JIT compilation."
echo ""
echo "Your options:"
echo " ${GREEN}N${NC} = Skip noexec (Browsers work, slightly reduced security)"
echo " ${RED}Y${NC} = Apply noexec (Maximum security, browsers will NOT launch)"
echo ""
read -p "Apply noexec to /dev/shm? (breaks browsers) [y/N]: " -r
if [[ $REPLY =~ ^[Yy]$ ]]; then
ALLOW_BROWSER_SHAREDMEM=false
log WARN "User chose to apply noexec - browsers will be broken"
echo ""
echo -e "${RED}WARNING: Browsers will NOT work.${NC}"
echo "You can fix later with: sudo ./fix_library_permissions.sh"
else
ALLOW_BROWSER_SHAREDMEM=true
log INFO "User skipped noexec for browser compatibility"
fi
echo ""
elif [[ "${IS_DESKTOP}" == "false" ]]; then
# Server mode - always apply noexec
ALLOW_BROWSER_SHAREDMEM=false
log INFO "Server mode: Applying noexec to /dev/shm (no browsers detected)"
fi
}
#=============================================================================
# HELP AND INFORMATION
#=============================================================================
display_help() {
cat << 'EOF'
╔══════════════════════════════════════════════════════════════════════════╗
║ FORTRESS.SH - Security Hardening ║
║ Version 5.1 ║
╚══════════════════════════════════════════════════════════════════════════╝
USAGE:
sudo ./fortress_improved.sh [OPTIONS]
OPTIONS:
-h, --help Show this help message
-v, --verbose Enable verbose output
-n, --non-interactive Run without user prompts
-d, --dry-run Show what would be done (no changes)
-e, --enable MODULES Enable specific modules (comma-separated)
-x, --disable MODULES Disable specific modules (comma-separated)
-l, --level LEVEL Security level (low|moderate|high|paranoid)
--explain Educational mode - explains WHY for each action
--list-modules List all available modules
--version Show version information
Compatibility Options:
--allow-docker Enable IP forwarding for Docker
--no-docker-compat Disable IP forwarding (breaks Docker)
--allow-browser-shm Skip noexec on /dev/shm (browsers work)
--no-browser-compat Apply noexec to /dev/shm (breaks browsers)
--force-desktop Force desktop-mode settings
--force-server Force server-mode settings
Configuration:
-c, --config FILE Use custom configuration file
--generate-config Create fortress.conf template
EXAMPLES:
# Run with explanations (recommended for learning)
sudo ./fortress_improved.sh --explain
# Dry run to see what would be done
sudo ./fortress_improved.sh --dry-run --verbose
# Enable only specific modules
sudo ./fortress_improved.sh -e system_update,ssh_hardening,firewall
# High security level, non-interactive
sudo ./fortress_improved.sh -l high -n
# Generate configuration file template
sudo ./fortress_improved.sh --generate-config
# Run with Docker compatibility
sudo ./fortress_improved.sh --allow-docker
# Use custom configuration file
sudo ./fortress_improved.sh -c /path/to/fortress.conf
SECURITY LEVELS:
low - Minimal hardening, preserves compatibility
moderate - Balanced security and usability (default)
high - Strong security, may affect some services
paranoid - Maximum security, requires careful configuration
MODULES:
Use --list-modules to see all available security modules
EDUCATIONAL MODE (--explain):
Explains the security rationale behind each action:
• What threats each configuration addresses
• Why certain defaults are insecure
• Trade-offs between security and usability
• Common misconceptions and security theater
IMPORTANT NOTES:
• Always run with --dry-run first to understand changes
• Use --explain mode to learn about each security measure
• SSH hardening requires key-based auth setup FIRST
• fail2ban is optional - understand when it's actually useful
• This script cannot protect against kernel-level rootkits
• Secure Boot verification is recommended but not automated
NEW IN v5.1:
• Docker detection with conditional IP forwarding
• Browser compatibility mode (skip /dev/shm noexec)
• Full configuration file support (fortress.conf)
• Pre-flight application detection
• Health verification script (verify_fortress.sh)
GITHUB: https://github.com/captainzero93/security_harden_linux
AUTHOR: captainzero93
EOF
}
list_modules() {
echo ""
echo "Available Security Modules:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
local sorted_modules=($(echo "${!SECURITY_MODULES[@]}" | tr ' ' '\n' | sort))
for module in "${sorted_modules[@]}"; do
local deps="${MODULE_DEPS[${module}]:-none}"
[[ -z "${deps}" ]] && deps="none"
printf " %-25s %s\n" "${module}" "${SECURITY_MODULES[${module}]}"
printf " %-25s Dependencies: %s\n\n" "" "${deps}"
done
exit 0
}
#=============================================================================
# MODULE IMPLEMENTATIONS
#=============================================================================
module_system_update() {
CURRENT_MODULE="system_update"
explain "System Updates" \
"Keeping your system updated is the SINGLE most important security measure." \
"" \
"Why: Most exploits target known vulnerabilities that have patches available." \
"Security updates fix these vulnerabilities before attackers can exploit them." \
"" \
"Updates address:" \
" • Kernel vulnerabilities (privilege escalation)" \
" • Library vulnerabilities (remote code execution)" \
" • Application bugs (various attack vectors)" \
"" \
"Trade-offs: Updates can occasionally break compatibility, but the security" \
"benefit far outweighs this risk. Test in non-production first."
log INFO "Updating package repositories..."
wait_for_apt
execute_command "Refreshing package lists" \
"sudo apt-get update"
if [[ "${SECURITY_LEVEL}" == "paranoid" ]]; then
execute_command "Upgrading all packages (full upgrade)" \
"sudo DEBIAN_FRONTEND=noninteractive apt-get full-upgrade -y"
else
execute_command "Upgrading packages (safe upgrade)" \
"sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y"
fi
log SUCCESS "System packages updated"
return 0
}
module_ssh_hardening() {
CURRENT_MODULE="ssh_hardening"
explain "SSH Hardening" \
"SSH is often the primary remote access method and a common attack target." \
"" \
"What we configure:" \
" • Disable password authentication (KEY-BASED AUTH ONLY)" \
" • Disable root login (use sudo instead)" \
" • Use strong ciphers and key exchange algorithms" \
" • Change default port (optional, reduces noise)" \
"" \
"Why password auth is disabled:" \
" • Even 'strong' passwords are vulnerable to brute force" \
" • SSH keys are cryptographically much stronger" \
" • No password means no password attacks - simple" \
"" \
"CRITICAL: You MUST have SSH key authentication working BEFORE" \
"running this module, or you will be locked out!" \
"" \
"Why fail2ban is NOT included here:" \
" • With password auth disabled, brute force is impossible" \
" • fail2ban only blocks IPs, which doesn't stop key-based attacks" \
" • Your logs will fill with failed attempts, but they're harmless" \
" • If log noise bothers you, run SSH on non-standard port" \
"" \
"Common misconception: fail2ban is NOT needed with proper SSH hardening."
local ssh_config="/etc/ssh/sshd_config"
# Check if SSH is even installed/needed
if ! command -v sshd &>/dev/null; then
if [[ "${IS_DESKTOP}" == "true" ]]; then
log INFO "SSH server not installed (normal for desktop)"
log INFO "Desktop systems typically don't need SSH server running"
if [[ "${INTERACTIVE}" == "true" ]]; then
read -p "Do you want to install and configure SSH? (y/N): " -r install_ssh
[[ ! "${install_ssh}" =~ ^[Yy]$ ]] && return 0
execute_command "Installing OpenSSH server" \
"sudo apt-get install -y openssh-server"
else
return 0
fi
fi
fi
# Safety check for existing key-based auth
if [[ "${INTERACTIVE}" == "true" ]] && [[ "${DRY_RUN}" == "false" ]]; then
echo ""
echo -e "${YELLOW}⚠️ IMPORTANT SSH SAFETY CHECK${NC}"
echo ""
echo "This module will disable password authentication."
echo "You MUST have SSH key-based authentication working."
echo ""
echo "Can you currently SSH into this system using a key? (not a password)"
read -p "Type 'yes' to confirm you have working key-based SSH: " -r confirm
if [[ "${confirm}" != "yes" ]]; then
log WARNING "SSH hardening skipped - setup key-based auth first"
echo ""
echo "To setup SSH key authentication:"
echo " 1. On your client: ssh-keygen -t ed25519"
echo " 2. Copy key to server: ssh-copy-id user@server"
echo " 3. Test: ssh -i ~/.ssh/id_ed25519 user@server"
echo " 4. Only then run this hardening module"
return 0
fi
fi
backup_file "${ssh_config}"
# Get SSH port from config or use default
local ssh_port="${SSH_PORT:-22}"
local ssh_max_auth="${SSH_MAX_AUTH_TRIES:-3}"
# Configure SSH hardening
if [[ "${DRY_RUN}" == "false" ]]; then
sudo tee "${ssh_config}" > /dev/null << EOF
# FORTRESS.SH SSH Configuration v5.1
# Strong security, key-based authentication only
# Port configuration
Port ${ssh_port}
# Authentication
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no
KbdInteractiveAuthentication no
UsePAM yes
# Strong cryptography
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256
# Connection settings
ClientAliveInterval 300
ClientAliveCountMax 2
LoginGraceTime 30
MaxAuthTries ${ssh_max_auth}
MaxSessions 10
# Logging
SyslogFacility AUTH
LogLevel VERBOSE
# Other security settings
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
PermitTunnel no
PrintMotd no
PrintLastLog yes
TCPKeepAlive yes
Compression delayed
EOF
# Add allowed users if configured
if [[ -n "${SSH_ALLOWED_USERS:-}" ]]; then
echo "AllowUsers ${SSH_ALLOWED_USERS//,/ }" | sudo tee -a "${ssh_config}" >/dev/null
log INFO "SSH restricted to users: ${SSH_ALLOWED_USERS}"
fi
execute_command "Restarting SSH service" \
"sudo systemctl restart sshd 2>/dev/null || sudo systemctl restart ssh"