setup-first-survey.sh
Skript pre kapitolu Prvý kontakt — audit a housekeeping
Veľkosť: 19172 B
Upravené: 2026-05-15 22:32 UTC
SHA256: 06c11ca8295e…
1#!/usr/bin/env bash
2# =============================================================================
3# VCS Akadémia — Level 1 / Chapter 1: First Survey & System Update
4# -----------------------------------------------------------------------------
5# Web: https://vcs-akademia.net/kurz/level-1/system-update
6# GitHub: github.com/virtucybersecurity/vcs-akademia
7#
8# Usage (run on the VPS, as root or via sudo):
9# curl -O https://vcs-akademia.net/script/level-1/system-update/setup-first-survey.sh
10# bash setup-first-survey.sh
11#
12# =============================================================================
13# PREČO TENTO SKRIPT EXISTUJE
14# =============================================================================
15#
16# Nový server od poskytovateľa NIKDY nie je v takom stave, v akom myslíš.
17# Tri konkrétne bezpečnostné problémy:
18#
19# 1. Staré balíky so známymi zraniteľnosťami (CVE).
20# Botnety skenujú servery a vedia presne, ktoré verzie balíkov sú
21# napadnuteľné. Server po týždňoch bez updateov je doslova červený
22# koberec.
23#
24# 2. Predinštalované služby, ktoré počúvajú na verejných portoch.
25# Niektorí poskytovatelia majú v default image webhosting panely,
26# monitoring agentov alebo databázy s defaultnými heslami. Ak
27# nevidíš, čo počúva, nemôžeš to chrániť.
28#
29# 3. Žiadny referenčný bod pre budúce zmeny.
30# O mesiac sa niečo pokazí. Bez baseline dokumentu nevieš, čo bolo
31# normálne a čo je nové (= podozrivé).
32#
33# Tento skript rieši všetky tri:
34# - Aplikuje bezpečnostné updaty
35# - Audituje, čo počúva (a varuje, ak vidí veci na verejnej IP)
36# - Vytvorí baseline.txt ako referenciu na disk
37#
38# =============================================================================
39# AKO TO TENTO SKRIPT ROBÍ
40# =============================================================================
41#
42# Skript beží PRIAMO NA VPS (nie lokálne). Predpokladá root alebo sudo.
43#
44# V 5 krokoch:
45# 1. Audit "pred" — zachytí súčasný stav servera (OS, kernel, služby,
46# porty, disk, RAM) do dočasného súboru.
47# 2. Apt update + upgrade — aplikuje všetky dostupné updaty.
48# 3. Housekeeping — pýta sa na hostname a timezone, nastavuje ich.
49# 4. Audit "po" — zachytí nový stav po aktualizácii.
50# 5. Vytvorí /root/server-baseline.txt s celým auditom.
51#
52# Skript NEINŠTALUJE žiadne aplikácie ani služby. Len upgraduje existujúce
53# balíky a zbiera informácie.
54#
55# Bezpečnostné varovanie počas auditu:
56# Ak skript zistí, že na 0.0.0.0 počúva niečo iné než SSH (port 22),
57# upozorní ťa. To je riziko, ktoré treba riešiť — buď zastavením služby,
58# alebo zmenou na 127.0.0.1, alebo firewallom (CH 5).
59#
60# =============================================================================
61
62set -u
63set -o pipefail
64
65# -----------------------------------------------------------------------------
66# Colors (ANSI escape codes)
67# -----------------------------------------------------------------------------
68RED='\033[0;31m'
69GREEN='\033[0;32m'
70YELLOW='\033[1;33m'
71BLUE='\033[0;34m'
72NC='\033[0m'
73
74# -----------------------------------------------------------------------------
75# Output helpers
76# -----------------------------------------------------------------------------
77print_info() { printf "${BLUE}[INFO]${NC} %s\n" "$*"; }
78print_success() { printf "${GREEN}[OK]${NC} %s\n" "$*"; }
79print_warning() { printf "${YELLOW}[VAROVANIE]${NC} %s\n" "$*" >&2; }
80print_error() { printf "${RED}[CHYBA]${NC} %s\n" "$*" >&2; }
81print_fatal() { print_error "$*"; cleanup_on_exit; exit 1; }
82
83# -----------------------------------------------------------------------------
84# Cleanup — remove the script file when we're done (success or failure).
85# -----------------------------------------------------------------------------
86SCRIPT_SOURCE="${BASH_SOURCE[0]:-$0}"
87
88cleanup_on_exit() {
89 [ -f "$SCRIPT_SOURCE" ] || return 0
90
91 local script_dir script_name script_path
92 script_dir="$(cd "$(dirname "$SCRIPT_SOURCE")" 2>/dev/null && pwd)" || return 0
93 script_name="$(basename "$SCRIPT_SOURCE")"
94 script_path="${script_dir}/${script_name}"
95
96 case "$script_name" in
97 setup-first-survey.sh) ;;
98 *) return 0 ;;
99 esac
100
101 case "$script_path" in
102 /|/bin/*|/sbin/*|/usr/*|/etc/*|/var/*|/lib/*|/lib64/*|/boot/*)
103 return 0 ;;
104 esac
105
106 [ -f "$script_path" ] && rm -f "$script_path" 2>/dev/null && \
107 print_info "Skript bol odstránený: $script_path"
108 return 0
109}
110
111trap cleanup_on_exit EXIT
112
113# -----------------------------------------------------------------------------
114# Pre-flight checks
115# -----------------------------------------------------------------------------
116preflight() {
117 # Must be Linux (this script runs ON the server, not locally)
118 if [ "$(uname -s)" != "Linux" ]; then
119 print_fatal "Tento skript sa spúšťa NA serveri (Linux), nie lokálne."
120 fi
121
122 # Must be root or have sudo
123 if [ "$(id -u)" -ne 0 ]; then
124 if ! command -v sudo >/dev/null 2>&1; then
125 print_fatal "Skript potrebuje root prístup. Spusti ako root alebo nainštaluj sudo."
126 fi
127 print_warning "Skript nie je spustený ako root."
128 print_info "Mnoho operácií bude vyžadovať sudo heslo."
129 fi
130
131 # Must be Debian/Ubuntu based (uses apt)
132 if ! command -v apt >/dev/null 2>&1; then
133 print_fatal "Tento skript podporuje len Debian/Ubuntu (apt). Pre iné distribúcie použi ručný postup z kapitoly."
134 fi
135}
136
137# -----------------------------------------------------------------------------
138# Confirmation prompt
139# -----------------------------------------------------------------------------
140confirm() {
141 local prompt="${1:-Pokračovať?}"
142 local reply
143 printf "${YELLOW}%s [y/N]:${NC} " "$prompt"
144 read -r reply </dev/tty
145 case "$reply" in
146 y|Y|yes|YES|ano|ANO) return 0 ;;
147 *) printf "${BLUE}[INFO]${NC} Zrušené.\n"; exit 0 ;;
148 esac
149}
150
151# -----------------------------------------------------------------------------
152# Ask for user input with optional default
153# -----------------------------------------------------------------------------
154ask() {
155 local prompt="$1"
156 local default="${2:-}"
157 local reply
158 if [ -n "$default" ]; then
159 printf "%s [%s]: " "$prompt" "$default" >&2
160 else
161 printf "%s: " "$prompt" >&2
162 fi
163 read -r reply </dev/tty
164 if [ -z "$reply" ]; then
165 echo "$default"
166 else
167 echo "$reply"
168 fi
169}
170
171# -----------------------------------------------------------------------------
172# Run command with sudo if not root
173# -----------------------------------------------------------------------------
174run_priv() {
175 if [ "$(id -u)" -eq 0 ]; then
176 "$@"
177 else
178 sudo "$@"
179 fi
180}
181
182# -----------------------------------------------------------------------------
183# Audit: collect server state into a file
184# -----------------------------------------------------------------------------
185audit_to_file() {
186 local out_file="$1"
187 local title="$2"
188
189 {
190 echo "================================================"
191 echo "$title"
192 echo "Dátum: $(date +'%Y-%m-%d %H:%M:%S %Z')"
193 echo "================================================"
194 echo
195
196 echo "=== Operačný systém ==="
197 if command -v lsb_release >/dev/null 2>&1; then
198 lsb_release -a 2>/dev/null
199 else
200 cat /etc/os-release 2>/dev/null
201 fi
202 echo
203
204 echo "=== Kernel a architektúra ==="
205 uname -a
206 echo
207
208 echo "=== Hostname ==="
209 hostnamectl 2>/dev/null || hostname
210 echo
211
212 echo "=== Aktívne služby (systemd) ==="
213 if command -v systemctl >/dev/null 2>&1; then
214 systemctl list-units --type=service --state=running --no-pager 2>/dev/null
215 else
216 echo "systemctl nie je dostupný"
217 fi
218 echo
219
220 echo "=== Otvorené TCP porty ==="
221 if command -v ss >/dev/null 2>&1; then
222 ss -tlnp 2>/dev/null
223 elif command -v netstat >/dev/null 2>&1; then
224 netstat -tlnp 2>/dev/null
225 else
226 echo "ss ani netstat nie sú dostupné"
227 fi
228 echo
229
230 echo "=== Disk ==="
231 df -h
232 echo
233
234 echo "=== RAM a Swap ==="
235 free -h
236 echo
237
238 echo "=== CPU ==="
239 if command -v lscpu >/dev/null 2>&1; then
240 lscpu | head -10
241 else
242 cat /proc/cpuinfo | head -20
243 fi
244 echo
245 } > "$out_file"
246}
247
248# -----------------------------------------------------------------------------
249# Security check: warn about services listening on 0.0.0.0 (other than SSH)
250# -----------------------------------------------------------------------------
251check_exposed_services() {
252 if ! command -v ss >/dev/null 2>&1; then
253 print_warning "ss nie je dostupné, preskakujem kontrolu vystavených služieb."
254 return 0
255 fi
256
257 # Get services listening on 0.0.0.0 (excluding SSH on 22)
258 local exposed
259 exposed=$(ss -tlnp 2>/dev/null | awk '
260 NR > 1 && $4 ~ /^0\.0\.0\.0:/ {
261 split($4, a, ":")
262 port = a[length(a)]
263 if (port != "22") {
264 print $4 " " $NF
265 }
266 }
267 ')
268
269 if [ -z "$exposed" ]; then
270 print_success "Žiadne neočakávané služby na verejnej IP (0.0.0.0)."
271 return 0
272 fi
273
274 echo
275 print_warning "VAROVANIE — Tieto služby počúvajú na verejnej IP (0.0.0.0):"
276 echo
277 echo "$exposed" | while IFS= read -r line; do
278 printf " ${RED}%s${NC}\n" "$line"
279 done
280 echo
281 print_warning "Toto je BEZPEČNOSTNÉ RIZIKO:"
282 print_warning " - Botnety vidia tieto porty na celom internete."
283 print_warning " - Skenujú ich, hľadajú default heslá a známe zraniteľnosti."
284 print_warning ""
285 print_warning "Možnosti riešenia:"
286 print_warning " a) Ak službu nepotrebuješ — zastav ju a vypni boot:"
287 print_warning " systemctl stop <nazov>"
288 print_warning " systemctl disable <nazov>"
289 print_warning " b) Ak ju potrebuješ ale len lokálne — zmeň ju na 127.0.0.1"
290 print_warning " (úprava jej konfigurácie)"
291 print_warning " c) Vytvor firewall pravidlo (preberáme v CH 5)"
292 echo
293 print_info "Tento skript NIČ NEVYPÍNA. Rozhodnutie je na tebe."
294 echo
295}
296
297# -----------------------------------------------------------------------------
298# Apply system updates
299# -----------------------------------------------------------------------------
300apply_updates() {
301 print_info "Aktualizujem databázu balíkov (apt update)..."
302 if ! run_priv apt update; then
303 print_fatal "apt update zlyhal. Skontroluj sieť alebo apt repozitáre."
304 fi
305
306 # Count upgradable packages
307 local upgradable
308 upgradable=$(apt list --upgradable 2>/dev/null | grep -c upgradable)
309 upgradable=$((upgradable > 0 ? upgradable - 1 : 0)) # subtract header line
310
311 if [ "$upgradable" -eq 0 ]; then
312 print_success "Všetky balíky sú aktuálne — žiadne updaty."
313 return 0
314 fi
315
316 print_info "Dostupných $upgradable updateov."
317 echo
318 confirm "Spustiť apt upgrade?"
319
320 print_info "Inštalujem updaty (môže trvať pár minút)..."
321 if ! run_priv apt upgrade -y; then
322 print_warning "apt upgrade skončil s chybou. Skontroluj výstup vyššie."
323 return 1
324 fi
325
326 print_success "Updaty nainštalované."
327
328 # Check if reboot is required
329 if [ -f /var/run/reboot-required ]; then
330 echo
331 print_warning "Niektoré updaty (kernel, libc) vyžadujú REŠTART servera."
332 print_warning "Bez reštartu bežia staré verzie napriek tomu, že apt ich má za nainštalované."
333 print_info "Reštart neskôr cez: reboot"
334 fi
335}
336
337# -----------------------------------------------------------------------------
338# Set hostname interactively
339# -----------------------------------------------------------------------------
340set_hostname_interactive() {
341 local current_hostname
342 current_hostname=$(hostname)
343
344 print_info "Aktuálny hostname: $current_hostname"
345 print_info "Hostname je meno servera, ktoré uvidíš v termináli ('user@nazov')."
346 print_info "Daj mu zmysluplné meno (napr. 'moj-web', 'db-prod', 'lab-test')."
347 echo
348
349 local new_hostname
350 new_hostname=$(ask "Nový hostname (Enter pre ponechanie)" "$current_hostname")
351
352 if [ "$new_hostname" = "$current_hostname" ]; then
353 print_info "Hostname zostáva: $current_hostname"
354 return 0
355 fi
356
357 # Validate: only alphanumeric, hyphens, no spaces
358 if ! echo "$new_hostname" | grep -qE '^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$'; then
359 print_warning "Neplatný hostname (povolené: písmená, čísla, pomlčky)."
360 print_info "Hostname nezmenený."
361 return 0
362 fi
363
364 if run_priv hostnamectl set-hostname "$new_hostname"; then
365 print_success "Hostname zmenený na: $new_hostname"
366 print_info "Pre prejavenie v aktuálnej session: exec bash (alebo nové prihlásenie)"
367 else
368 print_warning "Nepodarilo sa zmeniť hostname."
369 fi
370}
371
372# -----------------------------------------------------------------------------
373# Set timezone interactively
374# -----------------------------------------------------------------------------
375set_timezone_interactive() {
376 local current_tz
377 current_tz=$(timedatectl show -p Timezone --value 2>/dev/null || echo "unknown")
378
379 print_info "Aktuálny timezone: $current_tz"
380 print_info "Možnosti:"
381 print_info " - UTC (odporúčané pre produkčné servery)"
382 print_info " - Europe/Bratislava (slovenský čas)"
383 print_info " - Europe/Prague (český čas)"
384 print_info " - Iný (zadaj manuálne)"
385 echo
386
387 local new_tz
388 new_tz=$(ask "Nový timezone (Enter pre ponechanie)" "$current_tz")
389
390 if [ "$new_tz" = "$current_tz" ]; then
391 print_info "Timezone zostáva: $current_tz"
392 elif run_priv timedatectl set-timezone "$new_tz" 2>/dev/null; then
393 print_success "Timezone zmenený na: $new_tz"
394 else
395 print_warning "Neplatný timezone alebo chyba. Timezone nezmenený."
396 print_info "Zoznam timezone: timedatectl list-timezones"
397 fi
398
399 # Check NTP sync
400 local ntp_active
401 ntp_active=$(timedatectl show -p NTP --value 2>/dev/null || echo "unknown")
402 local sync_status
403 sync_status=$(timedatectl show -p NTPSynchronized --value 2>/dev/null || echo "unknown")
404
405 if [ "$sync_status" = "yes" ]; then
406 print_success "NTP synchronizácia funguje."
407 else
408 print_warning "NTP synchronizácia nie je aktívna."
409 print_info "Zapínam ju..."
410 if run_priv timedatectl set-ntp true 2>/dev/null; then
411 print_success "NTP zapnuté. Synchronizácia môže trvať minútu."
412 else
413 print_warning "Nepodarilo sa zapnúť NTP automaticky."
414 fi
415 fi
416}
417
418# -----------------------------------------------------------------------------
419# Final summary
420# -----------------------------------------------------------------------------
421print_summary() {
422 local baseline_path="$1"
423
424 echo
425 printf "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
426 printf "${GREEN} Audit a housekeeping: HOTOVO${NC}\n"
427 printf "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
428 echo
429 printf " Baseline dokument: ${BLUE}%s${NC}\n" "$baseline_path"
430 printf " Pozri si ho: ${BLUE}cat %s${NC}\n" "$baseline_path"
431 echo
432 printf "${YELLOW} Tento súbor zachováva stav servera v jednom konkrétnom${NC}\n"
433 printf "${YELLOW} momente. V budúcnosti, keď sa niečo pokazí alebo objavíš${NC}\n"
434 printf "${YELLOW} podozrivú vec, porovnaj aktuálny stav s týmto baseline.${NC}\n"
435 echo
436 printf "${YELLOW} Ďalej:${NC} CH 2 — Sudo user (non-root)\n"
437 printf " Vytvoríme používateľa s sudo právami, aby si sa odpútal\n"
438 printf " od priameho prihlasovania ako root.\n"
439 printf " https://vcs-akademia.net/kurz/level-1/sudo-user\n"
440 echo
441
442 # Reboot reminder
443 if [ -f /var/run/reboot-required ]; then
444 print_warning "Server vyžaduje REŠTART pred ďalšou kapitolou (kernel/libc update)."
445 print_info "Spusti: reboot"
446 echo
447 fi
448}
449
450# -----------------------------------------------------------------------------
451# MAIN
452# -----------------------------------------------------------------------------
453main() {
454 printf "\n${BLUE}=== VCS Akadémia — CH 1: Prvý kontakt ===${NC}\n\n"
455
456 cat <<EOF
457Tento skript urobí 5 krokov priamo na serveri:
458 1. Audit "pred" — zachytí súčasný stav servera.
459 2. Apt update + upgrade — bezpečnostné a feature updaty.
460 3. Housekeeping — nastaví hostname a timezone (interaktívne).
461 4. Audit "po" — zachytí nový stav po aktualizácii.
462 5. Vytvorí /root/server-baseline.txt ako referenciu.
463
464BEZPEČNOSTNÁ POZNÁMKA:
465 Počas auditu skript skontroluje, či na verejnej IP (0.0.0.0)
466 počúva niečo iné než SSH. Ak áno, upozorní ťa — toto je
467 potenciálne riziko, ktoré treba riešiť.
468
469EOF
470
471 confirm "Chceš pokračovať?"
472
473 printf "\n${BLUE}-- Pre-flight kontrola --${NC}\n"
474 preflight
475 print_success "Server je pripravený."
476
477 # Determine baseline path (root vs sudo)
478 local baseline_path="/root/server-baseline.txt"
479 if [ "$(id -u)" -ne 0 ]; then
480 baseline_path="$HOME/server-baseline.txt"
481 print_info "Beží ako non-root, baseline uložím do: $baseline_path"
482 fi
483
484 # Temp file for pre-update audit
485 local audit_before
486 audit_before=$(mktemp /tmp/audit-before-XXXXXX.txt)
487
488 printf "\n${BLUE}-- Krok 1/5 — Audit pred zmenami --${NC}\n"
489 audit_to_file "$audit_before" "AUDIT PRED APLIKÁCIOU UPDATEOV"
490 print_success "Pred-audit zachytený."
491
492 # Check for exposed services BEFORE updates (might change after)
493 check_exposed_services
494
495 printf "\n${BLUE}-- Krok 2/5 — System update --${NC}\n"
496 apply_updates
497
498 printf "\n${BLUE}-- Krok 3/5 — Hostname --${NC}\n"
499 set_hostname_interactive
500
501 printf "\n${BLUE}-- Krok 4/5 — Timezone a NTP --${NC}\n"
502 set_timezone_interactive
503
504 printf "\n${BLUE}-- Krok 5/5 — Vytváram baseline --${NC}\n"
505 audit_to_file "$baseline_path" "SERVER BASELINE (po prvom audite a update)"
506
507 # Append the "before" audit as appendix for reference
508 {
509 echo
510 echo "================================================"
511 echo "APPENDIX: Stav PRED aplikovaním updateov"
512 echo "(pre porovnanie)"
513 echo "================================================"
514 echo
515 cat "$audit_before"
516 } >> "$baseline_path"
517
518 rm -f "$audit_before"
519 print_success "Baseline vytvorený: $baseline_path"
520
521 print_summary "$baseline_path"
522}
523
524main "$@"