← Назад в блог

Программирование и скриптинг для кибербезопасности: часть 3 — автоматизация превентивного обслуживания

Опубликовано
6 мин чтения
--- просмотров

Введение

Когда лабораторное окружение готово, пора автоматизировать реальные процедуры обслуживания. Часть 3 серии проведёт тебя через проектирование bash-скрипта, который бэкапит конфигурационные данные, очищает устаревшие артефакты и записывает каждое действие в лог с меткой времени. К концу этого урока ты поймёшь, как условия, циклы и аргументы работают вместе, чтобы поддерживать здоровье Linux-серверов.

Цели обучения

  • Собирать bash-скрипт, который проверяет предварительные условия, создаёт сжатые бэкапы и корректно обрабатывает ошибки.
  • Использовать переиспользуемые хелперы логирования, чтобы каждый шаг выдавал структурированные сообщения для журналов аудита.
  • Перебирать коллекции с помощью циклов, чтобы находить и удалять устаревшие файлы, не затрагивая свежие данные.
  • Параметризовать скрипт позиционными аргументами, чтобы адаптировать его под разные окружения.
  • Осмыслять стратегии работы с путями, портируемость и меры безопасности, используемые в автоматизации обслуживания.

Сценарий: автоматизация превентивного обслуживания в Acme

Security-команда в Acme недавно запустила программу превентивного мониторинга. Вместо ручного запуска разовых команд они хотят надёжный скрипт, который operations-команда сможет планировать через cron. Скрипт должен:

  1. Подготовить или переиспользовать директорию для бэкапов.
  2. Архивировать /etc (или другую цель) с именем файла, содержащим метку времени.
  3. Удалять файлы .bak старше 30 дней.
  4. Логировать прогресс и ошибки для последующего просмотра.

Тебе поручено подготовить первую версию, проверить её поведение и задокументировать, почему каждое проектное решение поддерживает безопасность и портируемость.

Разбор скрипта — mantenimiento.sh

Реализация ниже следует требованиям и добавляет защитные механизмы, которые мы изучили в предыдущих частях серии.

#!/usr/bin/env bash
# Automate maintenance/auditing workflow with validations and logging.
# Args (optional):
#   1) SOURCE_DIR  - directory to back up (default: /etc)
#   2) DEST_FOLDER - destination folder name (default: backups)

# ===== Defaults & Args =====
SOURCE_DIR="${1:-/etc}"
DEST_FOLDER="${2:-backups}"

# Validate source directory before doing anything else
if [[ ! -d "$SOURCE_DIR" ]]; then
  printf 'Error: source directory does not exist: %s\n' "$SOURCE_DIR" >&2
  exit 1
fi

# Resolve an absolute path to avoid surprises with relative input
if ! SOURCE_ABS=$(cd "$SOURCE_DIR" 2>/dev/null && pwd -P); then
  printf 'Error: unable to access source directory: %s\n' "$SOURCE_DIR" >&2
  exit 1
fi

# Date stamps
STAMP="$(date '+%Y%m%d_%H%M%S')"

# Paths
SCRIPT_DIR="$(pwd)"
DEST_PATH="${SCRIPT_DIR}/${DEST_FOLDER}"

if ! mkdir -p "$DEST_PATH"; then
  printf 'Error: failed to create destination folder: %s\n' "$DEST_PATH" >&2
  exit 1
fi

LOG_FILE="${DEST_PATH}/mantenimiento_${STAMP}.log"
if ! touch "$LOG_FILE" 2>/dev/null; then
  printf 'Error: cannot write log file: %s\n' "$LOG_FILE" >&2
  exit 1
fi

timestamp() { date '+%Y-%m-%d %H:%M:%S'; }

log_line() {
  local level="$1"
  shift
  local target="$1"
  shift
  local message="$*"
  local formatted
  formatted=$(printf '[%s] %s | %s\n' "$level" "$(timestamp)" "$message")
  if [[ "$target" == "stderr" ]]; then
    printf '%s' "$formatted" | tee -a "$LOG_FILE" >&2
  else
    printf '%s' "$formatted" | tee -a "$LOG_FILE"
  fi
}

log_info()  { log_line "INFO" "stdout" "$@"; }
log_warn()  { log_line "WARN" "stderr" "$@"; }
log_error() { log_line "ERROR" "stderr" "$@"; }

log_info "=== Maintenance run started ==="
log_info "Source: ${SOURCE_ABS}"
log_info "Destination: ${DEST_PATH}"

# ===== A) Ensure backup folder exists =====
log_info "Ensuring backup folder exists: ${DEST_PATH}"
if [[ -d "$DEST_PATH" ]]; then
  log_info "Backup folder ready."
else
  log_error "Backup folder is not available after creation attempt."
  exit 1
fi

# ===== B) Compressed backup of SOURCE_DIR with date in name =====
SOURCE_LABEL="$(basename "$SOURCE_ABS")"
if [[ -z "$SOURCE_LABEL" || "$SOURCE_LABEL" == "/" ]]; then
  SOURCE_LABEL="root"
fi
BACKUP_FILE="${DEST_PATH}/${SOURCE_LABEL}_backup_${STAMP}.tar.gz"

log_info "Creating compressed backup: ${BACKUP_FILE}"

if [[ "$SOURCE_ABS" == "/" ]]; then
  TAR_BASE="/"
  TAR_TARGET="."
else
  TAR_BASE="$(dirname "$SOURCE_ABS")"
  TAR_TARGET="$(basename "$SOURCE_ABS")"
fi

if tar -czf "$BACKUP_FILE" -C "$TAR_BASE" "$TAR_TARGET" >>"$LOG_FILE" 2>&1; then
  if [[ -s "$BACKUP_FILE" ]]; then
    log_info "Backup created successfully: ${BACKUP_FILE}"
  else
    log_error "Backup file is empty. Aborting."
    exit 1
  fi
else
  log_error "Backup failed. Aborting."
  exit 1
fi

# ===== C) Delete .bak files older than 30 days inside SOURCE_DIR =====
log_info "Searching .bak files older than 30 days in: ${SOURCE_ABS}"

CANDIDATES_RAW=$(find "$SOURCE_ABS" -type f -name '*.bak' -mtime +30 -print 2>>"$LOG_FILE" || true)

if [[ -n "$CANDIDATES_RAW" ]]; then
  CANDIDATE_COUNT=$(printf '%s\n' "$CANDIDATES_RAW" | wc -l | tr -d ' ')
  log_info "${CANDIDATE_COUNT} .bak files scheduled for deletion."
  printf '%s\n' "$CANDIDATES_RAW" | while IFS= read -r file; do
    [[ -z "$file" ]] && continue
    log_info "Deleting: ${file}"
  done
  if find "$SOURCE_ABS" -type f -name '*.bak' -mtime +30 -delete >>"$LOG_FILE" 2>&1; then
    log_info ".bak cleanup completed."
  else
    log_warn "Cleanup encountered issues. Review log: ${LOG_FILE}"
  fi
else
  log_info "No .bak files older than 30 days found."
fi

# ===== D) Log bookkeeping =====
log_info "All steps completed."
log_info "Log file: ${LOG_FILE}"
log_info "Backup file: ${BACKUP_FILE}"
log_info "=== Maintenance run finished ==="

Ключевые концепции

Валидации и ранние выходы

  • Скрипт сразу проверяет, существует ли SOURCE_DIR. Раннее завершение предотвращает каскад вводящих в заблуждение ошибок.
  • Мы избегаем cd "$SOURCE_DIR"; вместо этого получаем абсолютный путь (SOURCE_ABS) через pwd -P, чтобы безопасно обрабатывать символические ссылки.
  • Каждая файловая операция (mkdir, touch, tar) проверяет свой результат и прерывается при ошибке, сохраняя достоверность логов.

Стратегия логирования

  • timestamp() предоставляет метки времени в формате ISO, благодаря чему логи можно сортировать в таблицах или SIEM-инструментах.
  • log_info, log_warn и log_error централизуют форматирование сообщений, уменьшая дублирование и проясняя уровни серьёзности.
  • Сообщения проходят через tee, поэтому оператор видит прогресс на экране, пока лог-файл фиксирует то же самое содержимое.

Перебор целей для очистки

  • Команда find собирает файлы-кандидаты .bak старше 30 дней. Скрипт подсчитывает их и логирует каждое удаление для прослеживаемости.
  • Поскольку удаления потенциально деструктивны, скрипт выводит имена директорий перед вызовом find … -delete и сохраняет stdout/stderr для последующего анализа.

Параметризация и портируемость

  • Аргументы по умолчанию (/etc, backups) поддерживают использование «из коробки», а опциональные параметры позволяют другим командам переиспользовать скрипт под свои задачи.
  • Выходные пути формируются от текущей рабочей директории (SCRIPT_DIR), что полезно, когда operations монтируют скрипт в контейнеризированные задачи.
  • Скрипт использует POSIX-совместимые конструкции (с минимумом расширений Bash), чтобы оставаться портируемым между распространёнными дистрибутивами Linux.

Запуск скрипта и сбор доказательств

После сохранения файла как mantenimiento.sh запусти:

chmod +x mantenimiento.sh
./mantenimiento.sh

Чтобы передать аргументы (например, бэкапить /var/www в nightly_backups):

./mantenimiento.sh /var/www nightly_backups

Проверь архив бэкапа и лог, выведя список целевой директории и просмотрев заголовок лога:

ls -lh nightly_backups
head -n 10 nightly_backups/mantenimiento_*.log

Скриншоты этих команд служат артефактами успешного выполнения.

Размышление и теоретическое обоснование

  • Условия контролируют каждое критичное действие, гарантируя, что последующие шаги выполняются только при успешном выполнении предпосылок. Это предотвращает частично выполненное состояние и сохраняет точность логов.
  • Циклы управляют динамическими наборами файлов (результатами find), заменяя ручное удаление повторяемой логикой.
  • Аргументы делают скрипт переиспользуемым на серверах с разной структурой директорий, способствуя единообразию внутри operations-команды.
  • Абсолютные и относительные пути. Скрипт полагается на абсолютные пути для исходных директорий и мест хранения логов, чтобы устранить неоднозначность. Внутри директории бэкапов относительные ссылки (например, ./mantenimiento_${STAMP}.log) можно использовать безопасно.
  • Портируемость и меры безопасности. Использование set -euo pipefail (которое можно добавить при запуске на Bash 4+) и проверка существования директорий защищают от ошибок конфигурации. Логирование в отдельный файл поддерживает аудит, а описанные ранее опциональные стратегии ротации или хеширования усиливают обнаружение подмены.

Следующие шаги

Чтобы продолжить харденинг воркфлоу обслуживания, рассмотри:

  • Добавление email- или webhook-уведомлений при сбое бэкапа.
  • Внедрение генерации и проверки контрольных сумм для архивированных файлов.
  • Расширение скрипта флагами командной строки (с помощью getopts) для подробного режима или dry run.
  • Планирование скрипта через cron с безопасными для окружения обёртками (/usr/bin/env bash).

Эти итерации опираются на навыки, представленные здесь, готовя тебя к управлению более крупными проектами автоматизации в безопасных operations-окружениях.

Открыт для работы по контракту

Я доступен для работы по контракту. Если у вас есть интересная идея проекта — запишитесь на звонок через Calendly.

Записаться на 30-минутный звонок