-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathharden.sh
More file actions
216 lines (192 loc) · 8.32 KB
/
Copy pathharden.sh
File metadata and controls
216 lines (192 loc) · 8.32 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
#!/usr/bin/env bash
# harden.sh — Ubuntu server hardening script
# Usage: sudo bash harden.sh [--admin-user USERNAME] [--ssh-port PORT]
# Tested on: Ubuntu 22.04 / 24.04
set -euo pipefail
# ── defaults ──────────────────────────────────────────────────────────────────
ADMIN_USER="${ADMIN_USER:-jf_admin}"
SSH_PORT="${SSH_PORT:-22}"
SKIP_UPDATES="${SKIP_UPDATES:-0}"
# ── arg parsing ───────────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
--admin-user) ADMIN_USER="$2"; shift 2 ;;
--ssh-port) SSH_PORT="$2"; shift 2 ;;
--skip-updates) SKIP_UPDATES=1; shift ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
# ── helpers ───────────────────────────────────────────────────────────────────
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
info() { echo -e "${GREEN}[✓]${NC} $*"; }
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
section() { echo -e "\n${YELLOW}══ $* ══${NC}"; }
require_root() { [[ $EUID -eq 0 ]] || { echo -e "${RED}Run as root.${NC}"; exit 1; }; }
require_root
# ── 1. OS updates ─────────────────────────────────────────────────────────────
section "OS Updates"
if [[ "$SKIP_UPDATES" -eq 0 ]]; then
apt-get update -qq
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y
apt-get autoremove -y
info "OS packages updated"
else
warn "Skipping OS updates (--skip-updates)"
fi
# ── 2. Fail2ban ───────────────────────────────────────────────────────────────
section "Fail2ban"
apt-get install -y fail2ban > /dev/null
cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
banaction = ufw
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = systemd
maxretry = 3
bantime = 24h
[nginx-http-auth]
enabled = true
[nginx-limit-req]
enabled = true
EOF
systemctl enable fail2ban
systemctl restart fail2ban
info "fail2ban installed and active (sshd jail: 3 attempts → 24h ban)"
# ── 3. Non-root sudo user ─────────────────────────────────────────────────────
section "Admin user: $ADMIN_USER"
if id "$ADMIN_USER" &>/dev/null; then
warn "User $ADMIN_USER already exists — skipping creation"
else
useradd -m -s /bin/bash -G sudo "$ADMIN_USER"
info "User $ADMIN_USER created with sudo access"
fi
# Copy root SSH keys if the user has none
ADMIN_HOME="/home/$ADMIN_USER"
if [[ -f /root/.ssh/authorized_keys ]] && [[ ! -f "$ADMIN_HOME/.ssh/authorized_keys" ]]; then
mkdir -p "$ADMIN_HOME/.ssh"
cp /root/.ssh/authorized_keys "$ADMIN_HOME/.ssh/"
chown -R "$ADMIN_USER:$ADMIN_USER" "$ADMIN_HOME/.ssh"
chmod 700 "$ADMIN_HOME/.ssh"
chmod 600 "$ADMIN_HOME/.ssh/authorized_keys"
info "Copied root SSH keys to $ADMIN_USER"
fi
# ── 4. SSH hardening ──────────────────────────────────────────────────────────
section "SSH Hardening"
# Warn and prompt before disabling root login
if [[ -f "$ADMIN_HOME/.ssh/authorized_keys" ]]; then
cat > /etc/ssh/sshd_config.d/99-hardening.conf << EOF
# Generated by harden.sh
Port $SSH_PORT
PermitRootLogin no
PasswordAuthentication no
PermitEmptyPasswords no
PubkeyAuthentication yes
X11Forwarding no
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
AllowAgentForwarding no
AllowTcpForwarding no
EOF
sshd -t
systemctl reload ssh 2>/dev/null || systemctl reload sshd 2>/dev/null
info "SSH hardened (password auth off, root login off, port $SSH_PORT)"
warn "Root SSH login is DISABLED. Use: ssh $ADMIN_USER@<server>"
else
warn "No SSH key found for $ADMIN_USER — skipping root login disable to avoid lockout"
cat > /etc/ssh/sshd_config.d/99-hardening.conf << EOF
# Generated by harden.sh
Port $SSH_PORT
PermitRootLogin prohibit-password
PasswordAuthentication no
PermitEmptyPasswords no
PubkeyAuthentication yes
X11Forwarding no
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
AllowAgentForwarding no
AllowTcpForwarding no
EOF
sshd -t
systemctl reload ssh 2>/dev/null || systemctl reload sshd 2>/dev/null
warn "Add your SSH key to /home/$ADMIN_USER/.ssh/authorized_keys, then re-run to fully disable root login"
fi
# ── 5. Nginx hardening ────────────────────────────────────────────────────────
section "Nginx Hardening"
if ! command -v nginx &>/dev/null; then
warn "Nginx not installed — skipping"
else
# Fix TLS protocols in nginx.conf
NGINX_CONF="/etc/nginx/nginx.conf"
cp "$NGINX_CONF" "${NGINX_CONF}.bak"
sed -i 's/ssl_protocols TLSv1 TLSv1\.1 TLSv1\.2 TLSv1\.3/ssl_protocols TLSv1.2 TLSv1.3/' "$NGINX_CONF"
sed -i 's/# server_tokens off;/server_tokens off;/' "$NGINX_CONF"
# Security headers snippet
mkdir -p /etc/nginx/snippets
cat > /etc/nginx/snippets/security-headers.conf << 'EOF'
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
EOF
# Inject snippet into all HTTPS server blocks that don't have it yet
COUNT=0
for f in /etc/nginx/sites-enabled/*.conf; do
if grep -q 'listen 443' "$f" && ! grep -q 'security-headers' "$f"; then
sed -i '/listen 443/a\\ include snippets/security-headers.conf;' "$f"
COUNT=$((COUNT + 1))
fi
done
nginx -t
systemctl reload nginx
info "Nginx: TLS 1.0/1.1 removed, server_tokens off, security headers added to $COUNT site(s)"
fi
# ── 6. Docker port audit ──────────────────────────────────────────────────────
section "Docker Port Audit"
if ! command -v docker &>/dev/null; then
warn "Docker not installed — skipping"
else
EXPOSED=$(docker ps --format '{{.Names}}\t{{.Ports}}' | grep -v '127.0.0.1' | grep '0.0.0.0\|::')
if [[ -n "$EXPOSED" ]]; then
warn "The following containers have ports exposed on 0.0.0.0 (bypasses UFW):"
echo "$EXPOSED"
warn "Fix: change port bindings to '127.0.0.1:<host>:<container>' in docker-compose files"
else
info "All Docker ports are bound to 127.0.0.1 or internal only"
fi
fi
# ── 7. UFW baseline ───────────────────────────────────────────────────────────
section "UFW Firewall"
if ! command -v ufw &>/dev/null; then
apt-get install -y ufw > /dev/null
fi
ufw --force reset > /dev/null
ufw default deny incoming
ufw default allow outgoing
ufw allow "$SSH_PORT/tcp" comment 'SSH'
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'
echo "y" | ufw enable > /dev/null
info "UFW enabled: deny-all in, allow SSH/HTTP/HTTPS out"
# ── summary ───────────────────────────────────────────────────────────────────
section "Done"
echo ""
echo -e " Admin user : ${GREEN}$ADMIN_USER${NC}"
echo -e " SSH port : ${GREEN}$SSH_PORT${NC}"
echo -e " Root login : ${GREEN}disabled${NC}"
echo -e " Passwords : ${GREEN}disabled (key-only)${NC}"
echo -e " fail2ban : ${GREEN}active${NC}"
echo -e " UFW : ${GREEN}active${NC}"
echo ""
warn "If SSH port changed, update your client config and open the new port in your cloud firewall."