MAATRIX / Блог / Кто и когда последний раз правил конфиги: восстанавливаем историю изменений

Кто и когда последний раз правил конфиги: восстанавливаем историю изменений

MAATRIX

Сервис ведёт себя странно, и первый вопрос — что и когда в конфиге поменяли. Но git на /etc никто не заводил, тикетов нет, а спросить некого: админ уволился, подрядчик пропал, или сервер просто «всегда так работал». Хорошая новость — даже без единой строчки документации на сервере остаются следы: временные метки файлов, журналы пакетного менеджера, забытые .bak-копии. По отдельности каждый источник даёт неполную и не всегда достоверную картину, но вместе они складываются в рабочую хронологию. Разберём, как её собрать.

Карта источников: что вообще можно узнать без документации

Прежде чем копать, стоит понимать иерархию доверия к источникам — некоторые дают точную дату и автора, другие только намёк, что «здесь что-то трогали».

Сильные источники (обычно есть дата, иногда — кто и что именно):

  • журнал пакетного менеджера (apt/dpkg или yum/dnf) — фиксирует момент установки или обновления пакета, который принёс конфиг-файл;
  • auditd, если он был настроен заранее — единственный источник, который реально пишет «кто именно открыл файл на запись»;
  • git-репозиторий на /etc, если его кто-то когда-то завёл (проверьте — иногда заводят и забывают);
  • журнал systemd (journalctl) — не про сами файлы, но про перезапуски служб, которые часто следуют сразу за правкой.

Слабые источники (дают только дату, без автора и без гарантии, что это ручная правка, а не деплой):

  • время модификации файла (mtime) — переписывается при любом действии, включая tar -x, rsync, восстановление из бэкапа;
  • .bak/.orig/.dpkg-old-файлы — говорят, что правку когда-то сделали осознанно и даже подстраховались, но не когда именно и не кто;
  • shell-история пользователей — легко подчищается, легко фальсифицируется, и в общем root-шелле часто общая для всех, кто заходил.

Если конфиги — лишь часть более крупной задачи (вам вообще достался чужой сервер целиком, без документации), сначала стоит провести полную инвентаризацию — методология описана в статье с чего начинать разбор незнакомой машины. Здесь же сузим фокус до одной конкретной задачи: датировать и, по возможности, атрибутировать правки в конфигах.

Даты модификации файлов: stat, ls -la, find -newer

Самый быстрый способ — посмотреть метаданные файловой системы. У каждого файла в Linux их три:

  • mtime — когда менялось содержимое файла;
  • ctime — когда менялись метаданные инода (права, владелец, или содержимое — ctime всегда обновляется вместе с mtime, но ещё и отдельно от него);
  • atime — когда файл последний раз читали (на большинстве серверов отключено флагом noatime в fstab ради производительности диска, так что не удивляйтесь, если он совпадает с mtime или вообще не обновляется).

Смотрим детально на конкретный файл:

stat /etc/nginx/nginx.conf

В выводе три штампа времени сразу — Modify, Change, Access. Если ctime новее mtime — скорее всего, кто-то менял права или владельца уже после последней правки содержимого (например, конфиг «поправили», а потом chown вернул на место после восстановления из архива).

Массовый просмотр — искать всё, что менялось за последние N дней:

find /etc -type f -mtime -30 -printf '%TY-%Tm-%Td %TH:%TM  %p\n' | sort

-mtime -30 — файлы, изменённые за последние 30 суток. Для точки отсчёта «изменено позже конкретного файла» удобнее -newer:

# всё, что изменилось в /etc после установки системы
find /etc -type f -newer /etc/hostname -printf '%TY-%Tm-%Td %TH:%TM  %p\n' | sort

/etc/hostname тут — просто файл-реперная точка, который обычно не трогают после первичной настройки; можно взять любой файл с известной датой.

Отсортировать вообще всё содержимое каталога по времени правки, от старого к новому:

ls -la --time-style=full-iso /etc/nginx/conf.d/ | sort -k6,7

Главная ловушка. mtime — это метаданные файла, а не метаданные правки. Он обнуляется при восстановлении из бэкапа, при tar -x без сохранения атрибутов, при переустановке пакета (конфиг получает время установки, даже если содержимое побайтово то же). Исключение — rsync -a: флаг -a включает -t и честно сохраняет исходный mtime, в отличие от scp без -p.

Поэтому если mtime на пачке файлов в /etc совпадает до секунды — это почти наверняка след массовой операции: миграции, восстановления бэкапа или переустановки образа, а не совпадение «все правили в одно время». Отдельная ручная правка обычно выделяется на этом фоне — у неё mtime не совпадает с соседями.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Логи пакетного менеджера: apt/dpkg и yum/dnf history

Если конфиг пришёл с пакетом (а не написан руками с нуля), у пакетного менеджера есть история установок и обновлений — и она переживает многие другие следы.

Debian/Ubuntu. Основной журнал команд:

cat /var/log/apt/history.log
# более старые записи — в ротированных архивах
zless /var/log/apt/history.log.1.gz

Каждая запись содержит Start-Date, Commandline (что именно вводили — apt install, apt upgrade и так далее) и список пакетов с версиями. Это не покажет правку конфига вручную, но точно покажет, когда обновлялся пакет, который мог откатить или перезаписать конфиг новым дефолтным вариантом.

Более детальный журнал — dpkg.log, где видно каждое действие с файлами пакета:

grep -i "configuration file" /var/log/dpkg.log*

Здесь всплывают строки о конфликтах конфигурационных файлов при обновлении — характерный признак, что apt заметил локальную правку и спросил (или тихо оставил) .dpkg-old/.dpkg-dist.

Проверить, отличается ли текущий конфиг от того, что положил пакет:

debsums -c
# только для пакета, которому принадлежит конкретный файл
dpkg -S /etc/nginx/nginx.conf
debsums -c nginx

debsums -c без аргументов выведет все файлы во всех установленных пакетах, чья контрольная сумма разошлась с оригиналом — то есть список всех ручных правок конфигов на сервере разом. Штука должна быть установлена отдельно (apt install debsums), но её стоит поставить даже просто ради этой команды.

RHEL/CentOS/AlmaLinux/RockyLinux. Аналог — yum history или dnf history:

dnf history list
dnf history info 42      # детали конкретной транзакции по её номеру

Транзакция покажет, какие пакеты и когда были установлены/обновлены/удалены — с точной датой и временем. Для проверки, что конкретный конфиг-файл менялся после установки пакета:

rpm -V nginx

Вывод rpm -V расшифровывается по колонкам; ключевая — 5 (изменилась контрольная сумма содержимого) в связке с c (пометка «это конфигурационный файл»). Строка вида S.5....T. c /etc/nginx/nginx.conf означает: файл конфигурационный, содержимое изменилось, время не совпадает с временем в пакете — то есть кто-то правил его руками после установки.

RPM при конфликте обновления сам сохраняет старую версию в .rpmsave или новую версию пакета — в .rpmnew, оставляя изменённый файл действующим. Оба — прямая подсказка о ручном вмешательстве:

find /etc -name '*.rpmsave' -o -name '*.rpmnew'

.bak, .orig и другие бэкапы как отпечатки правок

Администраторы, которые всё же подстраховываются перед правкой, обычно делают быструю копию рядом — cp nginx.conf nginx.conf.bak. Это не система версионирования, но зато почти всегда честный маркер: раз есть копия «до», значит, кто-то сознательно готовился менять файл именно тогда.

Поиск по всему дереву конфигов:

find /etc -type f \( -name '*.bak' -o -name '*.orig' -o -name '*.old' \
  -o -name '*~' -o -name '*.save' -o -name '*.dist' \) -printf '%TY-%Tm-%Td  %p\n' | sort

Дальше — самое полезное действие, которое часто пропускают: сравнить копию с текущим файлом, а не просто зафиксировать факт её существования.

diff -u /etc/nginx/nginx.conf.bak /etc/nginx/nginx.conf

Разница в diff — это фактически коммит-сообщение, которого не было. Она показывает не просто «что-то поменяли», а конкретно что: изменили таймаут, добавили location, закомментировали директиву. Дата самого .bak-файла (stat nginx.conf.bak) при этом даёт нижнюю границу времени правки — копию сняли непосредственно перед изменением или очень близко к нему.

Отдельно стоит поискать своп-файлы Vim (.имя.swp) — если такой жив рядом с конфигом, значит сессия редактирования оборвалась некорректно (обрыв SSH, kill -9) и, возможно, содержит несохранённую правку. Открывается тем же vim -r.

Отсутствие .bak-файлов ничего не доказывает — значит лишь, что конкретно этот администратор не подстраховывался. Но полное отсутствие любых следов ручного вмешательства на сервере, где явно что-то настроено нестандартно, — уже сигнал: либо правки шли не через shell (панель управления, Ansible), либо следы замели целенаправленно. Без прямых доказательств эти два случая не различить.

auditd и journalctl: ловим изменения, которые ещё не произошли

Всё, что описано выше — реконструкция прошлого по косвенным уликам. Она никогда не даст железной уверенности «в 14:32 пользователь ivan через vim записал в файл именно эту строку». Единственный источник, который отвечает на вопрос «кто» с такой точностью — auditd, но он должен быть настроен заранее, до интересующего события.

Если auditd ещё не установлен — самое время это исправить, чтобы следующий раз не пришлось гадать:

apt install auditd audispd-plugins   # Debian/Ubuntu
dnf install audit                     # RHEL-семейство

# следить за записью и сменой атрибутов в каталоге конфигов
auditctl -w /etc/nginx -p wa -k nginx_config_changes
auditctl -w /etc -p wa -k etc_config_changes

Флаг -p wa — отслеживать запись и изменение атрибутов, -k — метка для поиска. Чтобы правило пережило перезагрузку, продублируйте его в /etc/audit/rules.d/audit.rules.

Дальше запросы вида «кто трогал nginx» становятся точными:

ausearch -k nginx_config_changes -ts today
ausearch -k nginx_config_changes -ui 1000   # только конкретный UID

ausearch покажет PID, UID, исполняемый файл (vim, sed, ansible) и точное время события — это уже настоящий журнал, а не догадка.

Даже без auditd полезно проверить journalctl — не по самим файлам, а по сервисам, которые их читают. Момент перезапуска службы сразу после правки конфига почти всегда рядом по времени с самой правкой:

journalctl -u nginx --since "30 days ago" | grep -i "reload\|restart"

Сопоставив список перезапусков с датами mtime файлов из первого раздела, часто удаётся сузить окно правки до конкретного часа, даже если сама запись о ней не сохранилась. Похожая логика работает и для входов на сервер — если нужно понять, кто вообще заходил в интересующий период, это отдельная методология: разбор истории входов через last, lastb и auth.log хорошо дополняет картину «кто мог быть автором правки».

Заводим git-историю задним числом: etckeeper и его границы

Восстановить прошлое, которого не фиксировали, до конца невозможно. Но можно за 10 минут закрыть вопрос на будущее — поставить etckeeper, который автоматически коммитит /etc в git при каждом изменении через пакетный менеджер и по расписанию.

apt install etckeeper    # или dnf install etckeeper
cd /etc
etckeeper init
etckeeper commit "Начальный снимок конфигурации"

Это сразу даёт первый коммит — снимок текущего состояния всех конфигов, с которого дальше можно вести нормальную git-историю: git log -- nginx/nginx.conf, git diff HEAD~5, git blame на конкретную строку. etckeeper также коммитит до и после каждой транзакции apt/dnf, так что обновление пакетов больше не будет молча стирать локальные правки без следа.

Важное честное ограничение: этот коммит — не восстановленная история, а просто дата, с которой она начинает существовать. Всё, что было в конфигах до etckeeper init, остаётся неизвестным навсегда — методы из предыдущих разделов дают приближение, а не факт. Если на сервере в принципе нет git-процесса для инфраструктуры, стоит посмотреть на полноценный GitOps-подход, где конфиги живут в репозитории и применяются пайплайном, а не правятся руками по SSH — подробный разбор на примере VPN-сервера есть в статье про GitOps для конфигов.

Собираем находки в единую хронологию

По отдельности каждый источник — обрывок. Ценность появляется, когда их сводят в одну таблицу по датам и смотрят на совпадения.

Файлmtimeapt/dpkg history.bak найденКомментарий
/etc/nginx/nginx.conf2026-06-14обновления nginx не было с 2026-03да, .bak от 2026-06-14ручная правка, diff показывает добавленный location
/etc/ssh/sshd_config2026-08-02нетmtime совпадает с обновлением пакета openssh-server — вероятно, не ручная правка
/etc/fail2ban/jail.local2026-07-20пакет не обновлялсянетправка без следа копии — стоит спросить у команды напрямую, если есть кого

Строка вроде второй в таблице — типичный ложный след: дата совпадает с обновлением пакета, значит файл скорее переустановился, а не был правлен руками. Не включать такие файлы в «список подозрительных» так же важно, как найти реально изменённые, — иначе хронология зашумляется.

Итоговую таблицу стоит сохранить не в блокноте, а сразу первым файлом в репозитории etckeeper — так следующий человек, которому достанется этот сервер, не начнёт расследование с нуля.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли доверять mtime, если сервер недавно мигрировали rsync'ом на новый VPS?

Частично. rsync -a сохраняет исходный mtime, так что даты переживут миграцию. А вот scp без -p или копирование через tar без сохранения атрибутов обнуляют время на момент копирования — тогда раздел про mtime для этих файлов бесполезен.

Стоит ли доверять bash_history как источнику «кто именно правил конфиг»?

Как единственному источнику — нет. История легко чистится (history -c), часто общая для всех, кто заходил под одним sudo-пользователем, и не привязана к файлу — только к введённым командам. Используйте её для сужения временного окна, а не как доказательство авторства.

Стоит ли ставить etckeeper на боевой сервер прямо сейчас?

Да, риск минимален — это git-репозиторий и несколько хуков на пакетный менеджер, он не меняет работу сервисов. Единственное — проверьте .gitignore перед первым коммитом, чтобы туда не попали секреты и приватные ключи из /etc.

Что делать, если apt-логи уже успели ротироваться и удалиться?

По умолчанию logrotate держит dpkg.log несколько ротаций, но старое может быть потеряно безвозвратно. Тогда остаются mtime, .bak-копии и rpm -V/debsums — сравнение с эталоном пакета, не зависящее от логов.

Как отличить ручную правку от изменения, сделанного Ansible или Salt?

Косвенно — по характеру диффа: конфиг-менеджмент обычно правит несколько похожих серверов одновременно и оставляет предсказуемый стиль (одинаковые отступы, комментарии вроде # managed by ansible). Ручная правка чаще точечная. Если конфиг-менеджмент используется — надёжнее сразу проверить его собственные логи на управляющем хосте.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →