pg_sec_check#

pg_sec_check

pg_sec_check

pg_sec_check — Инструмент аудита безопасности для PostgreSQL

Введение

pg_sec_check (Проверка безопасности Postgres) — это утилита, предназначенная для проведения аудита безопасности конфигураций баз данных PostgreSQL. Она позволяет автоматизировать процесс проверки различных аспектов безопасности — от настроек сервера до специфических параметров базы данных — предоставляя подробные отчеты о выявленных проблемах и рекомендации по их устранению.

Особенности

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

  • Многомодульная архитектура: поддержка различных типов исполнителей (SQL-запросы, shell-скрипты) и валидаторов (Lua-скрипты).

  • Версионирование проверок: возможность привязки проверок к конкретным версиям PostgreSQL (минимальная и максимальная поддерживаемые версии).

  • Локализация: поддержка многоязычных отчетов и сообщений (в настоящее время русский и английский языки).

  • Различные форматы отчетов: генерация отчетов в форматах HTML и JSON.

  • Параллельное выполнение: возможность использования нескольких потоков для ускорения процесса сканирования.

  • Защита целостности: в релизных сборках программа контролирует целостность своих компонентов с помощью проверки контрольных сумм.

Архитектура

  • Ядро программы: отвечает за парсинг аргументов командной строки, чтение конфигурационного файла, управление потоками выполнения, запуск исполнителей, передачу результатов валидаторам и формирование итогового отчета.

  • Конфигурационный файл (config.json): определяет структуру отчета, секции и конкретные проверки, включая пути к скриптам исполнителей и валидаторов.

  • Модули исполнителей (Executors): Скрипты .sh или .sql, которые непосредственно взаимодействуют с системой или базой данных для сбора необходимой информации.

  • Модули валидаторов (Validators): lua-скрипты, которые анализируют данные, полученные от исполнителей, и формируют заключение о статусе проверки, описание и рекомендации.

  • Механизм локализации: позволяет отображать сообщения валидаторов и элементы отчета на языке, выбранном пользователем.

Программа построена на основе трех основных типов модулей, которые взаимодействуют для выполнения проверок:

  • Исполнители (Executors): отвечают за сбор данных: SQL: для выполнения запросов к базе данных PostgreSQL и sh: для выполнения shell-скриптов на хосте, где запускается утилита, или на хосте с базой данных (в зависимости от логики скрипта и доступных переменных окружения).

  • Валидаторы (Validators): отвечают за анализ данных, полученных от исполнителей. Поддерживается только один тип файлов Lua: Для написания логики валидации.

  • Конфигурация: определяет какие исполнители и валидаторы используются для каждой конкретной проверки, а также метаданные проверки (название, описание, поддерживаемые версии PostgreSQL).

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

Конфигурационный Файл (config.json)

Конфигурация проверок осуществляется через JSON-файл. Пример, simple_config.json:

{
	"header": "Server and Database Information Report",
    "report_name": "Report name",
    "description": "Some description",

    "section" : {

        "system" : {
            "header": "Example bash test",
            "description": "Common info about current host",
            "items": [
                {
                    "name": "hello world check",
    
                    "description" : "Compare hello world results",

                    "executors": [
                        {
                          "script": "tests/common/script/simple_script.sh",
                          "min_ver": "none",
                          "max_ver": "none"
                        }
                    ],
                    "validators": [
                        {
                          "script": "tests/common/lua/simple_bash_validate.lua",
                          "min_ver": "none",
                          "max_ver": "none"
                        }
                    ]
                }
            ]
        },
        "sql" : {
            "header": "Example sql test",
            "description": "Test connection into database",
            "items": [
                {
                    "name" : "select pg_version",
                    
                    "description" : "select pg_version",
                    
                    "executors": [
                        {
                          "script": "tests/common/script/simple_sql.sql",
                          "min_ver": "none",
                          "max_ver": "none"
                        }
                    ],
                    "validators": [
                        {
                          "script": "tests/common/lua/simple_sql_validate.lua",
                          "min_ver": "none",
                          "max_ver": "none"
                        }
                    ]
                }
            ]
        }
    }
}

Глобальные поля

  • header (String): общий заголовок для всего отчета.

  • report_name (String): имя отчета.

  • description (String): общее описание отчета.

  • section (Object): объект, содержащий определения различных секций отчета. Каждая секция представляет собой логическую группу проверок. Ключи этого объекта (например, “system”, “sql”) являются идентификаторами групп, которые можно использовать для выборочного запуска проверок с помощью аргумента командной строки --groups.

Структура секции (section)

  • header (String): заголовок для данной секции в отчете.

  • description (String): описание данной секции.

  • items (Array): массив объектов, каждый из которых определяет отдельную проверку.

Структура элемента проверки (items)

  • name (String): уникальное имя проверки, отображаемое в отчете.

  • description (String): подробное описание сути проверки.

  • executors (Array): массив объектов-исполнителей. Для одной проверки может быть определено несколько исполнителей, однако следует учитывать, как их результаты будут обрабатываться валидатором. Обычно используется один исполнитель на проверку.

  • validators (Array): массив объектов-валидаторов. Аналогично исполнителям, может быть несколько валидаторов. Чаще всего используется один валидатор.

Структура executor

  • script (String): путь к файлу скрипта-исполнителя. Это может быть .sh-файл для shell-скриптов или .sql-файлом для SQL-запросов.

  • min_ver (String): минимальная версия PostgreSQL, для которой применима данная проверка (например, “10.0.0”). Если указано “none”, ограничение по минимальной версии отсутствует. Проверка не будет запускаться для версий PostgreSQL ниже указанной.

  • max_ver (String): максимальная версия PostgreSQL, для которой применима данная проверка. Если указано «none», ограничение по максимальной версии отсутствует. Проверка не будет запускаться для версий PostgreSQL выше указанной.

Структура validator

  • script (String): путь к файлу Lua-скрипта валидатора.

  • min_ver (String) / max_ver (String): Аналогично min_ver для исполнителя, определяет минимальную версию PostgreSQL для запуска валидатора.

Такая детализированная структура конфигурационного файла позволяет точно настраивать каждую проверку, включая ее применимость к определенным версиям PostgreSQL, что особенно важно в средах с разнородными версиями СУБД.

Механизм проверок

Процесс проверки состоит из двух основных этапов: выполнение скрипта-исполнителя и анализ его результата скриптом-валидатором.

Исполнители (Executors)

Исполнители разработаны для сбора данных, необходимых для анализа безопасности.

  • Shell-скрипты (.sh): позволяют выполнять произвольные команды операционной системы. Это полезно для проверки конфигурационных файлов сервера, прав доступа, переменных окружения и других системных параметров.

    При запуске shell-скрипта программа устанавливает следующие переменные окружения, которые могут быть использованы внутри скрипта:

    • PG_HOST="хост базы данных"

    • PG_PORT="порт базы данных"

    • PG_USER:="имя пользователя для подключения к базе данных"

    • PG_DB="имя базы данных"

    • PGDATA="путь к каталогу данных PostgreSQL"

    • PG_VERSION="версия сервера PostgreSQL, к которому выполняется подключение"

    • PG_HBA_FILE="путь к файлу pg_hba.conf"

  • SQL-скрипты (.sql): используются для выполнения запросов к базе данных PostgreSQL с целью получения информации о настройках, объектах базы данных, правах пользователей и т.д.

    В настоящее время для SQL-файлов поддерживается выполнение только одного SELECT-запроса. Результат этого запроса передается валидатору. Это ограничение следует учитывать при разработке SQL-проверок; сложные проверки могут потребовать либо более комплексного SELECT-запроса, либо разделения на несколько отдельных проверок.

Валидаторы (Validators)

Валидаторы анализируют данные, собранные исполнителями, и выносят вердикт о состоянии проверяемого параметра. Вся логика валидации реализуется на языке Lua.

Глобальные данные, доступные в Lua-валидаторах:

  • Для валидаторов, обрабатывающих результаты sh-скриптов:

    result.stdout (String): стандартный вывод (stdout) выполненного shell-скрипта.
    result.stderr (String): стандартный вывод ошибок (stderr) выполненного shell-скрипта.
    result.return_code (Number): код возврата выполненного shell-скрипта.

    Пример Lua-скрипта для валидации shell скрипта simple_bash_validate.lua:

    -- GLOBAL DATA
    
    -- result.stdout the output of stdout from the executed bash script
    -- result.stderr the output of stderr from the executed bash script
    -- result.return_code the return code from the executed bash script
    
    function validate_result()
    	local success = true
    	local status_str = "Success"
    	local short_desc = "The check was successful"
    	local full_desc = "All parameters meet the requirements"
    	local recommend = "No action is required"
    
    	if not result.stdout == "hello world" then
    		status_str = "Warning"
    		short_desc = "Validation error"
    		full_desc = "The X parameter does not match the Y format"
    		recommend = "Fix the X parameter"
    	end
    
    	-- Important! The return value should look exactly like this.
    	-- That is, the structure (table) should not change.
    	-- {
    	--		status: one of the values is (Warning, Info, Error, Success, Failed)
    	--		short_description: String
    	--		full_description: String
    	--		recommendation: String
    	-- }
    	local result_table = {
    			status = status_str,
    			short_description = short_desc,
    			full_description = full_desc,
    			recommendation = recommend
    	}
    	return result_table
    end
    
  • Для валидаторов, обрабатывающих результаты SQL-скриптов:

    В Lua-скрипт передается глобальная таблица sql_results. Эта таблица представляет собой массив, где каждый элемент является таблицей, соответствующей одной строке результата SQL-запроса. Имена полей в этих вложенных таблицах соответствуют именам столбцов в SELECT-запросе.

    Пример Lua-скрипта для валидации SQL-запроса simple_sql_validate.lua:

    -- GLOBAL DATA
    
    -- sql_results a table with the result of SQL execution
    
    function validate_result()
    
        local success = true
        local status_str = "Success"
        local short_desc = "Error: SQL data is not received or incorrect"
        local full_desc = "All parameters meet the requirements"
        local recommend = "No action is required"
    
        -- Checking for the presence of the first line
        if sql_results and sql_results[1] then
            -- Getting the first row (this is a table)
            local first_row = sql_results[1]
    
    		-- an example for a table of the form:
    		-- pg_version
    		------------------------
    		-- pg version data bla bla bla
            if first_row and type(first_row) == 'table' and first_row.pg_version then
    
                short_desc = first_row.pg_version
                status_str = "Success" 
                full_desc = "Version PostgreSQL: " .. short_desc
            else
                 short_desc = "Error: The 'pg_version' field was not found in the SQL result"
                 status_str = "Failed"
            end
        else
            -- sql_results is empty
            status_str = "Failed"
        end
    
    	-- Important! The return value should look exactly like this.
    	-- That is, the structure (table) should not change.
    	-- {
    	--		status: one of the values is (Warning, Info, Error, Success, Failed)
    	--		short_description: String
    	--		full_description: String
    	--		recommendation: String
    	-- }
    
        local result_table = {
                status = status_str,
                short_description = short_desc,
                full_description = full_desc,
                recommendation = recommend
        }
        return result_table
    end
    

Структура возвращаемого значения Lua-валидатора:

Каждый Lua-скрипт валидатора должен определять функцию validate_result(), которая возвращает таблицу со строго определенной структурой:

{
  status: "Warning" | "Info" | "Error" | "Success" | "Failed", -- Must be one of these values
  short_description: "Brief description of the result",        -- String
  full_description: "Detailed explanation of the result",      -- String
  recommendation: "Recommended remediation actions"            -- String
}

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

locale_msg функция

Сценарии проверки Lua могут использовать функцию locale_msg(key) для получения локализованных строк.

См. раздел Локализация для получения более подробной информации.

Локализация

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

Механизм locale_msg

В Lua-скриптах валидаторов для получения переведенных строк используется функция locale_msg, где key – это строковый ключ сообщения. Например, вызов locale_msg("first") вернет строку, соответствующую ключу "first" на языке, указанном при запуске программы через аргумент --locale. По умолчанию используется английский язык, если файлы локализации для выбранного языка не найдены или ключ отсутствует.

Формат файла локализации (.loc)

Файлы локализации представляют собой текстовые файлы с расширением .loc (например, en.loc, ru.loc). Каждая строка в файле соответствует одному сообщению и имеет следующий формат:

id,"message_key"

Примеры:

  • localizations/ru.loc

    # ru.loc
    # --- validate_anon_extension.lua ---
    1,"<p>Проверка предзагрузки расширений анонимизации (<code>anon</code> или <code>pg_anonymize</code>) через параметр <code>session_preload_libraries</code>.</p>"
    2,"<p>Анализ <code>session_preload_libraries</code> для расширений анонимизации не был успешно завершен или расширения не найдены.</p>"
    3,"<ul><li>Проверьте SQL-запрос и конфигурацию PostgreSQL (параметр <code>session_preload_libraries</code>).</li></ul>"
    ...
    1674,"использование SSL"
    
  • localizations/en.loc

    # en.loc
    # --- validate_anon_extension.lua ---
    1,"<p>Checking preload of anonymization extensions (<code>anon</code> or <code>pg_anonymize</code>) via the <code>session_preload_libraries</code> parameter.</p>"
    2,"<p>Analysis of <code>session_preload_libraries</code> for anonymization extensions was not successfully completed or extensions were not found.</p>"
    3,"<ul><li>Check the SQL query and PostgreSQL configuration (parameter <code>session_preload_libraries</code>).</li></ul>"
    ...
    1674,"Ssl usage"
    

Если программа запускается с аргументом --locale ru, вызов locale_msg("first") вернет строку "first" на русском языке. Механизм поиска основан на сопоставлении строкового ключа, переданного в locale_msg, со вторым полем в файле локализации. Назначение числового идентификатора (например, 1) в настоящее время не полностью определено, но строковый ключ считается основным идентификатором.

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

Поддерживаемые языки

  • Английский (en)

  • Русский (ru)

Форматы отчетов

Программа способна генерировать отчеты о результатах проверок в нескольких форматах. Формат вывода указывается с помощью аргумента командной строки --format (или -f).

Доступные форматы:

  • Html: генерирует отчет в виде HTML-страницы. Этот формат удобен для визуального анализа результатов, так как он может содержать интерактивные элементы, стилизацию и легко читается человеком в веб-браузере.

  • Json: генерирует отчет в формате JSON. Этот формат является текстовым, машиночитаемым и широко используется для обмена данными между системами. JSON-отчеты подходят для автоматизированной обработки, интеграции с системами мониторинга, SIEM-системами или для последующего анализа с помощью скриптов и других инструментов.

  • Full: при выборе этого формата (который используется по умолчанию), программа, предположительно, генерирует отчеты во всех доступных форматах одновременно (т.е. и HTML, и JSON). Это позволяет пользователю получить как человекочитаемую версию для непосредственного изучения, так и машиночитаемую версию для автоматизации без необходимости повторного запуска утилиты. Имена выходных файлов будут формироваться на основе значения аргумента --output с добавлением соответствующих расширений (.html и .json).

Выбор формата отчета зависит от целей пользователя: для быстрого обзора и представления результатов руководству подойдет HTML, для интеграции и автоматической обработки — JSON.

Использование

Аргументы командной строки

Программа управляется через аргументы командной строки. Ниже приведена таблица с описанием доступных опций:

Аргумент Тип Значение по умолчанию Описание
--format String Full Формат выходного отчета. Опции: Full, Html, Json
--config String config.json Путь к конфигурационному файлу
--output String output Базовое имя для выходных файлов отчета (output_web/ и output.json)
--port u16 5432 Порт для подключения к базе данных PostgreSQL
--host String /tmp Хост для подключения к базе данных (IP-адрес, имя хоста или путь к Unix-сокету)
--user String dev Имя пользователя для подключения к базе данных
--database String postgres Имя базы данных для подключения
--threads u16 Количество ядер CPU Количество потоков для выполнения задач
--debug bool false Включить режим отладки (более подробный вывод)
--pgclients u16 10 Размер пула соединений PostgreSQL
--locale String ru Код локали для отчетов
--groups Vec<String> None Список групп проверок для запуска (через запятую, например “system,sql”)
--unsecure bool false Запуск без проверки контрольных сумм файлов программы
  • --groups предоставляет возможность запускать только определенные группы проверок, определенные как ключи в объекте section конфигурационного файла. Это удобно для целевого тестирования или поэтапного аудита.

  • --unsecure предназначен для особых случаев использования. В релизной сборке программа по умолчанию проверяет контрольные суммы всех своих компонентов, включая скрипты исполнителей и валидаторов, которые поставляются вместе с ней. Эта мера обеспечивает целостность инструмента и защищает от случайных или намеренных несанкционированных модификаций его частей. Если какой-либо из этих файлов был изменен (например, в процессе разработки или кастомизации стандартной проверки), программа откажется запускаться, сообщив об ошибке несоответствия контрольной суммы. Флаг --unsecure позволяет обойти эту проверку и запустить программу с модифицированными файлами.

Предупреждение: Использование флага --unsecure отключает важный механизм безопасности самой утилиты. Его следует применять с осторожностью. Не рекомендуется использовать флаг --unsecure в производственных или доверенных средах без полного понимания рисков, так как это может позволить выполнение потенциально измененного (возможно, вредоносного) кода скриптов.

Дополнительные аргументы:

  • RUST_LOG: переменная окружения, позволяющая настроить уровень логов.

  • BACKTRACE: переменная окружения, позволяющая увидеть стек вызовов в случае аварийного завершения утилиты.

Разработка и Кастомизация

Гибкость Postgres Security Check обеспечивается возможностью создания пользовательских проверок и добавления новых языков локализации.

Создание собственных проверок

Процесс создания новой проверки включает следующие шаги:

  1. Определить цель проверки.

    Четко сформулировать, какой аспект безопасности или конфигурации необходимо проверить.

  2. Написать скрипт-исполнитель.

    • Для shell-скриптов (.sh): Разработать скрипт, который собирает необходимые данные. Помните о доступных переменных окружения (PG_HOST, PG_PORT, PG_USER, PG_DB, PGDATA, PG_VERSION, PG_HBA_FILE).

    • Для SQL-скриптов (.sql): Написать SELECT-запрос (только один на файл), который извлекает необходимую информацию из базы данных.

  3. Написать Lua-скрипт-валидатор (.lua).

    Разработать Lua-скрипт, который будет анализировать вывод исполнителя (переданный через глобальную таблицу result для sh или sql_results для SQL). Функция validate_result(), в скрипте должна возвращать таблицу со стандартной структурой. Используйте функцию locale_msg("ключ") для всех строк, которые должны быть локализованы, и добавить соответствующие ключи и переводы в файлы *.loc.

  4. Добавить новую проверку в config.json.

    В соответствующем секции (section) добавить новый элемент в массив items. Указать name (имя) и description (описание) проверки. В массивах executors и validators указать пути к созданным скриптам. При необходимости задать min_ver и max_ver для ограничения применимости проверки к определенным версиям PostgreSQL.

Советы по отладке:

  • Тестируйте скрипты-исполнители отдельно, чтобы убедиться, что они корректно собирают данные.

  • Проверяйте логику Lua-валидаторов, подавая им на вход ожидаемые и граничные данные от исполнителей. Пошаговая отладка или вывод промежуточных значений в Lua может помочь.

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

Добавление новых языков локализации

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

Чтобы добавить новую локализацию:

  1. Создайте новый файл .loc. Например, для французского создайте файл с именем fr.loc. Имя файла должно соответствовать коду языка, который будет передан с использованием аргумента --locale.

  2. Скопируйте ключи. Возьмите за основу существующий файл en.locи скопируйте все строки.

  3. Переведите значения. Замените текстовые значения строк на соответствующий перевод на новый язык. Например, если в en.loc есть строка: 10,"connection failed", то в fr.loc она может выглядеть так: 10,"échec de la connexion"

  4. Разместите файл. Убедитесь, что программа сможет найти новый файл локализации. Такие файлы размещаются в специальном каталогe localizations.

Тщательное ведение файлов локализации и обеспечение их синхронности (наличие всех ключей во всех файлах) важно для качественного пользовательского опыта на разных языках.