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 "$@"
⤓ Stiahni raw skript ← Späť na kapitolu