Skip to content

compliance-checker.sh

Script d'audit de conformité basé sur les benchmarks CIS. Vérifie automatiquement les configurations de sécurité d'un serveur Linux.

Cas d'Usage

  • Audit de conformité CIS Benchmark
  • Hardening verification
  • Pre-deployment security check
  • Rapport pour audits externes

Prérequis

  • Bash 4.0+
  • Accès root (pour certains checks)
  • Linux (RHEL/CentOS/Rocky, Debian/Ubuntu)

Script

#!/bin/bash
#===============================================================================
# compliance-checker.sh - Audit de conformité CIS Benchmark
#
# Usage: ./compliance-checker.sh [OPTIONS]
#   -p, --profile PROFILE   Profil (server|workstation) [défaut: server]
#   -l, --level LEVEL       Niveau CIS (1|2) [défaut: 1]
#   -c, --category CAT      Catégorie spécifique
#   -o, --output FILE       Fichier de sortie
#   -f, --format FORMAT     Format (text|json|html) [défaut: text]
#   -q, --quiet             Mode silencieux
#===============================================================================

set -uo pipefail

# === CONFIGURATION ===
PROFILE="server"
LEVEL=1
CATEGORY=""
OUTPUT=""
FORMAT="text"
QUIET=false

# Compteurs
PASSED=0
FAILED=0
SKIPPED=0
MANUAL=0

# Couleurs
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

# Résultats
declare -a RESULTS=()

# === FONCTIONS UTILITAIRES ===

log() { [[ "$QUIET" == "true" ]] || echo -e "$1"; }
log_pass() { log "${GREEN}[PASS]${NC} $1"; ((PASSED++)); RESULTS+=("PASS|$1"); }
log_fail() { log "${RED}[FAIL]${NC} $1"; ((FAILED++)); RESULTS+=("FAIL|$1"); }
log_skip() { log "${YELLOW}[SKIP]${NC} $1"; ((SKIPPED++)); RESULTS+=("SKIP|$1"); }
log_manual() { log "${BLUE}[MANUAL]${NC} $1"; ((MANUAL++)); RESULTS+=("MANUAL|$1"); }
log_info() { log "${BLUE}[INFO]${NC} $1"; }

# Vérifier si un service est actif
is_service_active() {
    systemctl is-active "$1" &>/dev/null
}

# Vérifier si un service est enabled
is_service_enabled() {
    systemctl is-enabled "$1" &>/dev/null 2>&1
}

# Vérifier si un package est installé
is_package_installed() {
    if command -v rpm &>/dev/null; then
        rpm -q "$1" &>/dev/null
    elif command -v dpkg &>/dev/null; then
        dpkg -l "$1" 2>/dev/null | grep -q "^ii"
    fi
}

# Vérifier une ligne dans un fichier
check_file_content() {
    local file=$1
    local pattern=$2
    [[ -f "$file" ]] && grep -qE "$pattern" "$file"
}

# Vérifier les permissions d'un fichier
check_file_perms() {
    local file=$1
    local expected=$2
    [[ -f "$file" ]] && [[ "$(stat -c %a "$file")" == "$expected" ]]
}

# === CHECKS CIS ===

# 1.1 - Filesystem Configuration
check_filesystem() {
    log_info "=== 1.1 Filesystem Configuration ==="

    # 1.1.1.1 - cramfs disabled
    if ! lsmod | grep -q cramfs && ! modprobe -n -v cramfs 2>&1 | grep -q "insmod"; then
        log_pass "1.1.1.1 cramfs est désactivé"
    else
        log_fail "1.1.1.1 cramfs n'est pas désactivé"
    fi

    # 1.1.2 - /tmp séparé
    if findmnt -n /tmp &>/dev/null; then
        log_pass "1.1.2 /tmp est une partition séparée"
    else
        log_fail "1.1.2 /tmp n'est pas une partition séparée"
    fi

    # 1.1.3-5 - Options /tmp
    if findmnt -n /tmp | grep -q nodev; then
        log_pass "1.1.3 nodev sur /tmp"
    else
        log_fail "1.1.3 nodev manquant sur /tmp"
    fi

    # 1.1.8 - /var/tmp séparé
    if findmnt -n /var/tmp &>/dev/null || findmnt -n /var | grep -q "/var"; then
        log_pass "1.1.8 /var/tmp configuré"
    else
        log_skip "1.1.8 /var/tmp non vérifié"
    fi
}

# 1.3 - Filesystem Integrity
check_integrity() {
    log_info "=== 1.3 Filesystem Integrity ==="

    # AIDE installed
    if is_package_installed aide || is_package_installed aide-common; then
        log_pass "1.3.1 AIDE est installé"
    else
        log_fail "1.3.1 AIDE n'est pas installé"
    fi

    # AIDE cron
    if [[ -f /etc/cron.daily/aide ]] || crontab -l 2>/dev/null | grep -q aide; then
        log_pass "1.3.2 AIDE est planifié"
    else
        log_fail "1.3.2 AIDE n'est pas planifié"
    fi
}

# 1.4 - Secure Boot Settings
check_bootloader() {
    log_info "=== 1.4 Secure Boot Settings ==="

    # GRUB permissions
    local grub_cfg=""
    [[ -f /boot/grub2/grub.cfg ]] && grub_cfg="/boot/grub2/grub.cfg"
    [[ -f /boot/grub/grub.cfg ]] && grub_cfg="/boot/grub/grub.cfg"

    if [[ -n "$grub_cfg" ]]; then
        local perms=$(stat -c %a "$grub_cfg")
        if [[ "$perms" == "600" || "$perms" == "400" ]]; then
            log_pass "1.4.1 Permissions GRUB correctes ($perms)"
        else
            log_fail "1.4.1 Permissions GRUB incorrectes ($perms, attendu: 600)"
        fi
    else
        log_skip "1.4.1 Fichier GRUB non trouvé"
    fi

    # Boot password
    if grep -q "^set superusers" /boot/grub2/grub.cfg 2>/dev/null || \
       grep -q "^password" /boot/grub2/user.cfg 2>/dev/null; then
        log_pass "1.4.2 Mot de passe boot configuré"
    else
        log_fail "1.4.2 Mot de passe boot non configuré"
    fi
}

# 2.1 - Services spéciaux
check_services() {
    log_info "=== 2.1 Special Purpose Services ==="

    local unwanted_services=(
        "avahi-daemon"
        "cups"
        "dhcpd"
        "slapd"
        "nfs"
        "rpcbind"
        "named"
        "vsftpd"
        "httpd"
        "dovecot"
        "smb"
        "squid"
        "snmpd"
        "ypserv"
        "telnet.socket"
    )

    for svc in "${unwanted_services[@]}"; do
        if is_service_enabled "$svc"; then
            log_fail "2.1.x Service $svc est activé"
        else
            log_pass "2.1.x Service $svc est désactivé"
        fi
    done
}

# 3.1-3.3 - Network Configuration
check_network() {
    log_info "=== 3.x Network Configuration ==="

    # IP forwarding disabled
    local ip_forward=$(sysctl -n net.ipv4.ip_forward 2>/dev/null)
    if [[ "$ip_forward" == "0" ]]; then
        log_pass "3.1.1 IP forwarding désactivé"
    else
        log_fail "3.1.1 IP forwarding activé ($ip_forward)"
    fi

    # ICMP redirects
    local icmp_redirect=$(sysctl -n net.ipv4.conf.all.accept_redirects 2>/dev/null)
    if [[ "$icmp_redirect" == "0" ]]; then
        log_pass "3.2.2 ICMP redirects désactivés"
    else
        log_fail "3.2.2 ICMP redirects activés"
    fi

    # TCP SYN Cookies
    local syn_cookies=$(sysctl -n net.ipv4.tcp_syncookies 2>/dev/null)
    if [[ "$syn_cookies" == "1" ]]; then
        log_pass "3.2.8 TCP SYN cookies activés"
    else
        log_fail "3.2.8 TCP SYN cookies désactivés"
    fi
}

# 4.1 - Firewall
check_firewall() {
    log_info "=== 4.x Firewall Configuration ==="

    # Firewall installé et actif
    if is_service_active firewalld; then
        log_pass "4.1.1 firewalld est actif"
    elif is_service_active ufw; then
        log_pass "4.1.1 ufw est actif"
    elif iptables -L &>/dev/null && [[ $(iptables -L -n | wc -l) -gt 8 ]]; then
        log_pass "4.1.1 iptables configuré"
    else
        log_fail "4.1.1 Aucun firewall actif"
    fi
}

# 5.1 - SSH Configuration
check_ssh() {
    log_info "=== 5.2 SSH Server Configuration ==="

    local sshd_config="/etc/ssh/sshd_config"

    # Permissions sshd_config
    if check_file_perms "$sshd_config" "600"; then
        log_pass "5.2.1 Permissions sshd_config (600)"
    else
        log_fail "5.2.1 Permissions sshd_config incorrectes"
    fi

    # PermitRootLogin
    if grep -qE "^PermitRootLogin\s+(no|prohibit-password)" "$sshd_config" 2>/dev/null; then
        log_pass "5.2.10 PermitRootLogin désactivé"
    else
        log_fail "5.2.10 PermitRootLogin non restreint"
    fi

    # PermitEmptyPasswords
    if grep -qE "^PermitEmptyPasswords\s+no" "$sshd_config" 2>/dev/null || \
       ! grep -qE "^PermitEmptyPasswords\s+yes" "$sshd_config" 2>/dev/null; then
        log_pass "5.2.11 PermitEmptyPasswords désactivé"
    else
        log_fail "5.2.11 PermitEmptyPasswords activé"
    fi

    # Protocol 2
    if ! grep -qE "^Protocol\s+1" "$sshd_config" 2>/dev/null; then
        log_pass "5.2.4 SSH Protocol 2"
    else
        log_fail "5.2.4 SSH Protocol 1 activé"
    fi

    # MaxAuthTries
    local max_auth=$(grep -E "^MaxAuthTries" "$sshd_config" 2>/dev/null | awk '{print $2}')
    if [[ -n "$max_auth" && "$max_auth" -le 4 ]]; then
        log_pass "5.2.7 MaxAuthTries <= 4 ($max_auth)"
    else
        log_fail "5.2.7 MaxAuthTries trop élevé ou non défini"
    fi
}

# 5.3 - PAM Configuration
check_pam() {
    log_info "=== 5.3 PAM Configuration ==="

    # Password quality
    if is_package_installed libpam-pwquality || is_package_installed pam_pwquality; then
        log_pass "5.3.1 pam_pwquality installé"
    else
        log_fail "5.3.1 pam_pwquality non installé"
    fi

    # Faillock/pam_tally
    if grep -rq "pam_faillock\|pam_tally" /etc/pam.d/ 2>/dev/null; then
        log_pass "5.3.2 Verrouillage de compte configuré"
    else
        log_fail "5.3.2 Verrouillage de compte non configuré"
    fi
}

# 5.4 - User Accounts
check_accounts() {
    log_info "=== 5.4 User Accounts ==="

    # Password expiration
    local pass_max=$(grep "^PASS_MAX_DAYS" /etc/login.defs 2>/dev/null | awk '{print $2}')
    if [[ -n "$pass_max" && "$pass_max" -le 365 ]]; then
        log_pass "5.4.1.1 PASS_MAX_DAYS <= 365 ($pass_max)"
    else
        log_fail "5.4.1.1 PASS_MAX_DAYS trop élevé"
    fi

    # Password minimum days
    local pass_min=$(grep "^PASS_MIN_DAYS" /etc/login.defs 2>/dev/null | awk '{print $2}')
    if [[ -n "$pass_min" && "$pass_min" -ge 1 ]]; then
        log_pass "5.4.1.2 PASS_MIN_DAYS >= 1 ($pass_min)"
    else
        log_fail "5.4.1.2 PASS_MIN_DAYS trop bas"
    fi

    # Root is only UID 0
    local uid0_count=$(awk -F: '($3 == 0) {print}' /etc/passwd | wc -l)
    if [[ "$uid0_count" -eq 1 ]]; then
        log_pass "5.4.3 Seul root a UID 0"
    else
        log_fail "5.4.3 $uid0_count utilisateurs avec UID 0"
    fi
}

# 6.1 - File Permissions
check_permissions() {
    log_info "=== 6.1 System File Permissions ==="

    # /etc/passwd
    if check_file_perms "/etc/passwd" "644"; then
        log_pass "6.1.2 /etc/passwd permissions (644)"
    else
        log_fail "6.1.2 /etc/passwd permissions incorrectes"
    fi

    # /etc/shadow
    local shadow_perms=$(stat -c %a /etc/shadow 2>/dev/null)
    if [[ "$shadow_perms" == "000" || "$shadow_perms" == "640" || "$shadow_perms" == "600" ]]; then
        log_pass "6.1.3 /etc/shadow permissions ($shadow_perms)"
    else
        log_fail "6.1.3 /etc/shadow permissions incorrectes ($shadow_perms)"
    fi

    # /etc/group
    if check_file_perms "/etc/group" "644"; then
        log_pass "6.1.4 /etc/group permissions (644)"
    else
        log_fail "6.1.4 /etc/group permissions incorrectes"
    fi

    # World-writable files
    local ww_count=$(find / -xdev -type f -perm -0002 2>/dev/null | wc -l)
    if [[ "$ww_count" -eq 0 ]]; then
        log_pass "6.1.10 Pas de fichiers world-writable"
    else
        log_fail "6.1.10 $ww_count fichiers world-writable trouvés"
    fi
}

# 6.2 - User and Group Settings
check_users() {
    log_info "=== 6.2 User and Group Settings ==="

    # Empty passwords
    local empty_pass=$(awk -F: '($2 == "") {print $1}' /etc/shadow 2>/dev/null)
    if [[ -z "$empty_pass" ]]; then
        log_pass "6.2.1 Pas de mots de passe vides"
    else
        log_fail "6.2.1 Utilisateurs sans mot de passe: $empty_pass"
    fi

    # Duplicate UIDs
    local dup_uid=$(cut -d: -f3 /etc/passwd | sort | uniq -d)
    if [[ -z "$dup_uid" ]]; then
        log_pass "6.2.15 Pas d'UIDs dupliqués"
    else
        log_fail "6.2.15 UIDs dupliqués: $dup_uid"
    fi
}

# === RAPPORT ===

generate_report() {
    local total=$((PASSED + FAILED + SKIPPED + MANUAL))
    local score=0
    [[ $total -gt 0 ]] && score=$((PASSED * 100 / (PASSED + FAILED)))

    case "$FORMAT" in
        json)
            echo "{"
            echo "  \"date\": \"$(date -Iseconds)\","
            echo "  \"hostname\": \"$(hostname)\","
            echo "  \"profile\": \"$PROFILE\","
            echo "  \"level\": $LEVEL,"
            echo "  \"summary\": {"
            echo "    \"passed\": $PASSED,"
            echo "    \"failed\": $FAILED,"
            echo "    \"skipped\": $SKIPPED,"
            echo "    \"manual\": $MANUAL,"
            echo "    \"score\": $score"
            echo "  },"
            echo "  \"results\": ["
            local first=true
            for r in "${RESULTS[@]}"; do
                IFS='|' read -r status desc <<< "$r"
                $first || echo ","
                echo -n "    {\"status\": \"$status\", \"description\": \"$desc\"}"
                first=false
            done
            echo ""
            echo "  ]"
            echo "}"
            ;;
        *)
            echo ""
            echo "=============================================="
            echo " RAPPORT DE CONFORMITÉ CIS"
            echo "=============================================="
            echo " Date     : $(date '+%Y-%m-%d %H:%M:%S')"
            echo " Host     : $(hostname)"
            echo " Profile  : $PROFILE"
            echo " Level    : $LEVEL"
            echo "=============================================="
            echo " PASSED   : $PASSED"
            echo " FAILED   : $FAILED"
            echo " SKIPPED  : $SKIPPED"
            echo " MANUAL   : $MANUAL"
            echo "----------------------------------------------"
            echo " SCORE    : $score%"
            echo "=============================================="
            ;;
    esac
}

# === MAIN ===

while [[ $# -gt 0 ]]; do
    case $1 in
        -p|--profile) PROFILE="$2"; shift 2 ;;
        -l|--level) LEVEL="$2"; shift 2 ;;
        -c|--category) CATEGORY="$2"; shift 2 ;;
        -o|--output) OUTPUT="$2"; shift 2 ;;
        -f|--format) FORMAT="$2"; shift 2 ;;
        -q|--quiet) QUIET=true; shift ;;
        -h|--help) grep '^#' "$0" | grep -v '#!/' | sed 's/^# //' | head -15; exit 0 ;;
        *) shift ;;
    esac
done

log "=============================================="
log " Audit de Conformité CIS - ShellBook"
log " Profil: $PROFILE | Niveau: $LEVEL"
log "==============================================\n"

# Exécuter les checks
check_filesystem
check_integrity
check_bootloader
check_services
check_network
check_firewall
check_ssh
check_pam
check_accounts
check_permissions
check_users

# Générer le rapport
report=$(generate_report)

if [[ -n "$OUTPUT" ]]; then
    echo "$report" > "$OUTPUT"
    log "\nRapport sauvegardé dans $OUTPUT"
else
    echo "$report"
fi

# Exit code basé sur les échecs
[[ $FAILED -eq 0 ]] && exit 0 || exit 1

Exemples d'Utilisation

# Audit de base
sudo ./compliance-checker.sh

# Audit niveau 2
sudo ./compliance-checker.sh -l 2

# Export JSON
sudo ./compliance-checker.sh -f json -o /var/log/compliance.json

# Mode silencieux (pour cron)
sudo ./compliance-checker.sh -q -o /var/log/cis-audit.txt

Voir Aussi