Рейтинг:1

Отформатируйте журналы SFTP, чтобы иметь имя пользователя в каждой записи

флаг cn

Я использую производственный сервер (Debian 10, стандартный пакет OpenSSH), на котором работает Pure-FTPD для устаревших соединений и SFTP для всех наших текущих соединений. SFTP-сервер настроен с chroot-тюрьмой, которая регистрируется через связанное устройство в пользовательской chroot-тюрьме. Это принимается rsyslog и отправляется в /var/log/sftp.log, после чего я использую logstash для разбора этого файла и пересылки всего на сервер визуализации для наших суперпользователей. Суперпользователи входят в визуализацию, чтобы просматривать все журналы SFTP и FTP/FTPS в одном месте.

Журналы pure-ftpd отформатированы так, как нравится нашим суперпользователям:

pure-ftpd: (testuser@hostname) [ВНИМАНИЕ] /home/ftpusers/testuser//outbound/testfile.pdf загружен (1765060 байт, 5989,55 КБ/сек)

Это здорово, потому что в одной строке отображается конкретный пользователь и точный файл, который он загрузил или скачал. Однако для SFTP ситуация не так хороша:

internal-sftp[8848]: сеанс открыт для локального пользователя testuser с [{ip_address}]
внутренний-sftp [8848]: opendir "/ входящий"
внутренний-sftp[8848]: реальный путь "/входящий/."
internal-sftp[8848]: открыть флаги "/inbound/testfile.pdf" WRITE,CREATE,TRUNCATE режим 0666
internal-sftp[8848]: закрыть "/inbound/testfile.pdf" байт прочитано 0 записано 1734445

В этом случае за логами легко следить. тестовый пользователь входит в систему, записывает файл, готово. НО у нас есть много пользователей, которые входят в систему одновременно, и журналы из нескольких экземпляров внутреннего sftp могут происходить одновременно. Если это произойдет, единственный способ отследить активность пользователя — это найти имя пользователя. тестовый пользователь, найдите идентификатор процесса, который регистрируется (8848 в приведенном выше примере), затем найдите все сообщения с этим идентификатором процесса. Многие пользователи входят в систему через cronjob, так что это происходит каждые 2 минуты или около того... когда у нас есть 300 пользователей, которые входят в систему через регулярные промежутки времени, вы можете себе представить, что поиск по такому количеству идентификаторов процессов может быть мучительным.

Мой вопрос

Есть ли способ предварять каждое сообщение журнала от sftp-internal именем пользователя, создающего журнал? Это должно работать в chroot-тюрьме. Я не могу найти ничего о том, как изменить сообщение, которое генерирует rsyslog, чтобы включить имя пользователя.

Я хотел бы увидеть что-то подобное из моих журналов SFTP:

internal-sftp[8848]: (testuser) открыть "/inbound/testfile.pdf" флаги WRITE,CREATE,TRUNCATE режим 0666
internal-sftp[8848]: (testuser) закрыть "/inbound/testfile.pdf" байт прочитано 0 записано 1734445

Текущее состояние конфигурации

Моя цепочка процессов идет:

ssh -> sftp-internal -> rsyslog (на local3.*) -> файл /var/log/sftp.log -> logstash -> экспорт на сервер визуализации

Выдержка из моей группы chroot в /etc/ssh/sshd_config

Match Group sftpusers 
        ChrootDirectory %h
        AuthorizedKeysFile %h/.ssh/authorized_keys
        ForceCommand внутренний-sftp -f local3 -l INFO
        # ForceCommand internal-sftp -l VERBOSE
        AllowTcpForwarding нет
        X11Номер переадресации

и мой /etc/rsyslog.d/sftp.conf

local3.* -/var/log/sftp.log

Похожие вопросы:

Этот вопрос речь идет о ведении журнала SFTP для отдельных файлов, но упоминается это waybackmachine для старой статьи, которая включала красивое форматирование записей журнала SFTP, чтобы они выглядели как стандартные xferlogs.В статье упоминается сценарий Perl (Святой Грааль), который отформатирует его для вас, но, увы, ссылка мертва. Я мог бы написать сценарий Python или Perl, который находит конкретное сообщение для передачи, получает идентификатор процесса и выполняет обратный поиск, чтобы найти пользователя, а затем печатает переформатированное сообщение xfer с именем пользователя в файл. Но наверняка кто-то решал эту проблему раньше и имеет лучшее решение.

Спасибо за любую помощь.

Рейтинг:0
флаг cn

Мне удалось создать решение с помощью Python и systemd. Это очень быстро и грязно, но работает для моих целей. Я беру внутренний файл журнала sftp и выгружаю его в переформатированный. я не изменять оригинал на случай, если этот форматировщик сделает какие-либо ошибки.

Скрипт Python

Это выходит из rsyslog для мониторинга и отвечает на SEGINT из systemd. Да, это должно использовать что-то лучше, чем список, но в python нет встроенных кольцевых буферов или формальной системы очередей (отправьте мне комментарий, если я что-то упустил). В любом случае... это не Си!

#!/usr/bin/python3

журнал импорта
импортировать повторно
импорт системы
время импорта


класс SFTPLogFormatter:

    def __init__(self, infile: str, outfile: str):
        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(регистрация.DEBUG)
        stdout_handler = ведение журнала.StreamHandler()
        stdout_handler.setLevel(регистрация.DEBUG)
        stdout_handler.setFormatter(logging.Formatter('%(имя уровня)8s | %(сообщение)s'))
        self.logger.addHandler (stdout_handler)

        self.infile = открыть (infile, 'r')

        # добавляем в файл и сохраняем только 1 строку в буфере записи (запись почти
        # немедленно)
        self.outfile = открыть (outfile, 'a', 1)

    деф старт(сам):
        пытаться:
            self.logger.info('запуск форматтера')
            самозапуск()
        кроме KeyboardInterrupt:
            self.logger.warning('SIGINT получен, успешно завершается')
            самостоятельная остановка ()

    @статический метод
    def tail_file (file_obj):
        пока верно:
            строка = file_obj.readline()
            # спать, если файл не был обновлен
            если не строка:
                время сна(1)
                Продолжить

            линия доходности

    деф запустить (самостоятельно):
        self.infile.seek(0, 2) # переход в конец файла для поведения типа `tail -f`
        строки_читать = []
        для строки в self.tail_file(self.infile): # завершите файл, например `tail -f`
            lines_read.insert(0, line) # обрабатывать список как стек
            lines_read = lines_read[:2000] # обрезаем стек, так как в python нет кольцевых буферов

            изменитьline_match = re.match(r'(.*)\[(\d+)\]: (открыть|закрыть|удалить имя) (.*)', строка) 
            если не изменитьline_match:
                self.logger.info(строка)
                self.outfile.write(строка)
                Продолжить

            mod_line_procid = mod_line_match.group(2)

            self.logger.debug(f'поиск оператора открытия сеанса для open|close file match string: \"{modifyline_match.group(0)}\"')
            open_session_regex = rf'.*\[{modify_line_procid}\]: сеанс открыт для локального пользователя (.*) из.*'
            open_session_match = Нет
            для предыдущей строки в lines_read[1:]:
                open_session_match = re.match(open_session_regex, предыдущая строка)
                если open_session_match:
                    self.logger.debug (f'найдена строка открытия сеанса: \"{open_session_match.group(0)}\"')
                    сломать
            еще:
                # мы ничего не нашли
                self.logger.debug('не удалось найти строку открытого сеанса для: \"{modifyline_match.group(0)}\"')
                Продолжить

            mod_line_start = mod_line_match.group(1)
            оператор_модификации_линии = оператор_модификации_линии.группа(3)
            edit_line_details = modifyline_match.group(4)

            имя пользователя = open_session_match.group(1)

            log_str = f'{modify_line_start}[{modify_line_procid}]: (user={username}) {modify_line_operator} {modify_line_details}\n'
            self.logger.info(log_str)
            self.outfile.write(log_str)

    деф стоп(сам):
        self.logger.info('очистка')
        пытаться:
            self.infile.close ()
        кроме Исключения как e:
            self.logger.error(f'сбой при закрытии файла: {e}')

        пытаться:
            self.outfile.close()
        кроме Исключения как e:
            self.logger.error(f'сбой при закрытии внешнего файла: {e}')

        self.logger.info('выход')
        системный выход (0)


если __name__ == '__main__':
    входящий файл = sys.argv[1]
    выходной файл = sys.argv[2]
    служба = SFTPLogFormatter (входящий файл, выходной файл)
    сервис.старт()

Сервисный файл

Следующий служебный файл был создан и включен в systemd.

[Ед. изм]
Description=Отформатируйте сообщения журнала из sftp, чтобы имя пользователя в любом файле читалось, записывалось и удалялось, что значительно облегчало чтение многопользовательских журналов.
После=network.target

[Оказание услуг]
Пользователь=корень
Тип=простой
ExecStart=/usr/bin/python3 /home/admin/services/format_sftp_logs_with_username.py /var/log/sftp.log /var/log/sftp_with_usernames.log
KillSignal=SIGINT

[Установить]
WantedBy=многопользовательская.цель

Результаты

Это приводит к следующим сообщениям журнала. Обратите внимание на дополнения (user=XYZ).

11 февраля, 21:22:01 ip-10-20-0-96 internal-sftp[18241]: сеанс открыт для локального пользователя testuser из [127.0.0.1]
11 февраля 21:22:02 ip-10-20-0-96 внутренний-sftp[18241]: opendir "/"
11 февраля 21:22:02 ip-10-20-0-96 внутренний-sftp[18241]: закрытый "/"
11 февраля 21:22:05 ip-10-20-0-96 внутренний-sftp[18241]: opendir "/ входящий"
11 февраля 21:22:05 ip-10-20-0-96 внутренний-sftp[18241]: Closedir "/ входящий"
11 февраля 21:22:10 ip-10-20-0-96 внутренний-sftp[18241]: opendir "/inbound/"
11 февраля 21:22:10 ip-10-20-0-96 внутренний-sftp[18241]: Closedir "/входящий/"
11 февраля, 21:22:12 ip-10-20-0-96 internal-sftp[18241]: (user=testuser) open «/inbound/mailhog-deployment.yaml» флаги режима READ 0666
11 февраля, 21:22:12 ip-10-20-0-96 internal-sftp[18241]: (user=testuser) close "/inbound/mailhog-deployment.yaml" байт прочитано 815 записано 0
11 февраля 21:22:13 ip-10-20-0-96 внутренний-sftp[18241]: opendir "/inbound/"
11 февраля, 21:22:13 ip-10-20-0-96 внутренний-sftp[18241]: Closedir "/inbound/"
11 февраля 21:22:14 ip-10-20-0-96 внутренний-sftp[18241]: opendir "/inbound/"
11 февраля, 21:22:14 ip-10-20-0-96 внутренний-sftp[18241]: Closedir "/inbound/"
11 февраля, 21:22:14 ip-10-20-0-96 internal-sftp[18241]: (user=testuser) удалить имя "/inbound/mailhog-deployment.yaml"
11 февраля 21:22:18 ip-10-20-0-96 internal-sftp [18241]: (пользователь = testuser) открыть флаги «/inbound/mailhog-deployment.yaml» WRITE, CREATE, TRUNCATE режим 0644
11 февраля, 21:22:18 ip-10-20-0-96 internal-sftp[18241]: (user=testuser) close "/inbound/mailhog-deployment.yaml" байт прочитано 0 записано 815
11 февраля, 21:22:19 ip-10-20-0-96 internal-sftp[18241]: сеанс закрыт для локального пользователя testuser из [127.0.0.1]

Ограничения

Буфер содержит 2000 строк для поиска идентификатора процесса. Увеличьте это значение, если у вас есть десятки или сотни пользователей, которые входят в систему в данный момент. В противном случае это должно покрыть потребности большинства серверов.

Ответить или комментировать

Большинство людей не понимают, что склонность к познанию нового открывает путь к обучению и улучшает межличностные связи. В исследованиях Элисон, например, хотя люди могли точно вспомнить, сколько вопросов было задано в их разговорах, они не чувствовали интуитивно связи между вопросами и симпатиями. В четырех исследованиях, в которых участники сами участвовали в разговорах или читали стенограммы чужих разговоров, люди, как правило, не осознавали, что задаваемый вопрос повлияет — или повлиял — на уровень дружбы между собеседниками.