Настройка мандатного управления доступом(МРД) с Kerberos аутентификацией в ЕПП, Apache2, Postgres#

Исходные данные#

Имеется сервер контроллера домена FreeIPA:

  • имя домена astra.aaa

  • администратор домена admin@astra.aaa

  • пользователь домена user-01@astra.aaa

  • имя сервера dc-01.astra.aaa

  • сервер имеет постоянный IP-адрес, например, 192.168.1.20

Веб-сервер располагается отдельно:

  • имя сервера websrv-01.astra.aaa

  • сервер должен быть введен в домен

  • на сервере установлен и настроен web-сервер Apache2

  • сервер имеет постоянный IP-адрес, например, 192.168.1.21

Сервер базы данных располагается отдельно:

  • имя сервера dbsrv-01.astra.aaa

  • сервер должен быть введен в домен

  • на сервере должна быть установлена и настроенная СУБД Postgresql

  • сервер имеет постоянный IP-адрес, например, 192.168.1.23

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

  • имя компьютера pc-01.astra.aaa

  • компьютер должен быть введен в домен

  • компьютер имеет постоянный IP-адрес, например, 192.168.1.22

Основная концепция, реализация веб-приложения на языке программирования PHP с МРД#

Контроллер домена Freeipa, веб сервер Apache2 и СУБД Postgres предварительно настроены для использования МРД и kerberos аутентификации.

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

Веб сервер Apache2 производит аутентификацию пользователя и если она прошла успешно, то обработчик запроса переключается в контекст пользователя, включая классификационную метку МРД его сеанса.

Далее запускается приложение и создается делегируемый kerberos кэш. Если аутентификации прошла неуспешно, выдается ошибка.

Далее веб сервер Apache2 передаёт запрос веб приложению PHP.

Веб приложение PHP добавляет в окружение переменную KRB5CCNAME.

Далее коннектор php-pgsql, в режиме GSS, производит запрос к БД Postgresql с передачей контекста пользователя, включая классификационную метку МРД.

СУБД Postgres так же производит kerberos аутентификацию, авторизацию по правилам МРД и возвращает результаты запроса.

Настройка контроллера домена#

Для настройки контроллера домена необходимо перейти по ссылке и выполнить действия по инструкции:

Настройка компьютера пользователя#

Для настройки компьютера пользователя необходимо перейти по ссылке и выполнить действия по инструкции:

Настройка сервера базы данных#

Для настройки сервера базы данных необходимо перейти по ссылке и выполнить действия по инструкции:

Установка и настройка веб-сервера Apache2#

Для установки и настройки веб-сервера Apache2 необходимо перейти по ссылке и выполнить действия по инструкции:

Установка веб-приложения#

Для установки и настройки веб-приложения необходимо:

Пункт 1#

  • установить следующие пакеты пакет:

sudo apt install php php-pgsql

Пункт 2#

  • создать файл index.php по пути /var/www/public_html:

sudo nano /var/www/public_html/index.php

Пункт 3#

  • файл index.php должен иметь следующее содержимое:

<?php
// Конфигурация подключения к БД
putenv("KRB5CCNAME=" . $_SERVER['KRB5CCNAME']);
$dbConfig = [
    'host' => 'test181-dbsrv-01.astra.bbb',
    'port' => '5433',
    'dbname' => 'demoprimer',
];
// Подключение к базе данных
try {
    $dsn = "pgsql:host={$dbConfig['host']};port={$dbConfig['port']};dbname={$dbConfig['dbname']}";
    $db = new PDO($dsn, '');
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    die("Ошибка подключения к базе данных: " . $e->getMessage());
}

// Переменные для сообщений
$operation = isset($_GET['operation']) ? $_GET['operation'] : null;
$message = '';

// Обработка удаления записи ДО получения списка записей
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['delete'])) {
    try {
        $stmt = $db->prepare("DELETE FROM s1.t1 WHERE id = ?");
        $stmt->execute([$_GET['delete']]);
        // Перенаправление с параметром операции
        header("Location: ".strtok($_SERVER['REQUEST_URI'], '?')."?operation=deleted");
        exit();
    } catch (PDOException $e) {
        die("Ошибка при удалении записи: " . $e->getMessage());
    }
}

// Обработка остальных CRUD операций
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    session_start();
    $currentUser = $_SERVER['REMOTE_USER'];
    if (isset($_POST['create'])) {
        // Создание новой записи
        $stmt = $db->prepare("INSERT INTO s1.t1 (insert_user, insert_date, classificator)
                            VALUES (?, CURRENT_TIMESTAMP, ?)");
        $stmt->execute([$currentUser, $_POST['classificator']]);
        // Перенаправление с параметром операции
        header("Location: ".$_SERVER['PHP_SELF']."?operation=created");
        exit();
    } elseif (isset($_POST['update'])) {
        // Обновление записи
        $stmt = $db->prepare("UPDATE s1.t1
                            SET classificator = ?
                            WHERE id = ?");
        $stmt->execute([$_POST['classificator'], $_POST['id']]);
        // Перенаправление с параметром операции
        header("Location: ".$_SERVER['PHP_SELF']."?operation=updated");
        exit();
    }
}

// Получение всех записей
$records = $db->query("SELECT maclabel, *, extract(epoch from insert_date) as insert_ts
                    FROM s1.t1
                    ORDER BY insert_date DESC")->fetchAll(PDO::FETCH_ASSOC);

// Получение записи для редактирования
$editRecord = null;
if (isset($_GET['edit'])) {
    $stmt = $db->prepare("SELECT * FROM s1.t1 WHERE id = ?");
    $stmt->execute([$_GET['edit']]);
    $editRecord = $stmt->fetch(PDO::FETCH_ASSOC);
}
?>
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Демонстрационный пример</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; line-height: 1.6; }
        h1, h2 { margin: 0 0 20px; }
        table { width: 100%; border-collapse: collapse; margin-bottom: 20px; border-radius: 8px; overflow: hidden; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
        th, td { padding: 12px 15px; text-align: left; border: 1px solid #ddd; }
        th { background-color: #f2f2f2; font-weight: 600; }
        tr:nth-child(even) { background-color: #f9f9f9; }
        tr:hover { background-color: #f1f1f1; }
        form { margin-bottom: 20px; padding: 20px; background: #f9f9f9; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.05); }
        label { display: block; margin-bottom: 8px; font-weight: 500; }
        input { margin-bottom: 15px; width: 100%; max-width: 500px; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 16px; box-sizing: border-box; }
        input:focus { outline: none; border-color: #0079C1; box-shadow: 0 0 5px rgba(0,121,193,0.3); }
        button { background: #0079C1; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: 500; }
        .button { padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: 500; text-decoration: none; display: inline-block; margin-right: 8px; color: white; }
        button:hover { background: #00588D; }
        .actions { white-space: nowrap; }
        .uuid { font-family: monospace; font-size: 0.9em; }
        .form-row { margin-bottom: 15px; }
        .button-cancel { background: #6c757d; }
        .button-cancel:hover { background: #5a6268; }
        .button-edit { background: #0079C1; }
        .button-edit:hover { background: #00588D; }
        .button-delete { background: #dc3545; }
        .button-delete:hover { background: #bb2d3b; }

        /* Стили для toast-уведомлений */
        .toast {
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 15px 25px;
            border-radius: 4px;
            color: white;
            background-color: #28a745;
            box-shadow: 0 0 10px rgba(0,0,0,0.1);
            z-index: 1000;
            opacity: 0;
            transition: opacity 0.3s ease-in-out;
        }
        .toast.show {
            opacity: 1;
        }
        .toast.error {
            background-color: #dc3545;
        }
    </style>
</head>
<body>
    <h1>Демонстрационный пример</h1>

    <!-- Toast-уведомление -->
    <div id="toast" class="toast"></div>

    <!-- Форма для создания/редактирования -->
    <form method="POST">
        <h2><?= $editRecord ? 'Редактировать запись' : 'Добавить новую запись' ?></h2>
        <?php if ($editRecord): ?>
            <input type="hidden" name="id" value="<?= htmlspecialchars($editRecord['id']) ?>">
        <?php endif; ?>
        <div class="form-row">
            <label for="classificator">Наименование:</label>
            <input type="text" id="classificator" name="classificator" placeholder="Введите наименование" required
                value="<?= htmlspecialchars($editRecord['classificator'] ?? '') ?>">
        </div>
        <div class="form-row">
            <?php if ($editRecord): ?>
                <button type="submit" name="update">Обновить</button>
                <a href="?" class="button button-cancel">Отмена</a>
            <?php else: ?>
                <button type="submit" name="create">Создать</button>
            <?php endif; ?>
        </div>
    </form>

    <!-- Таблица с записями -->
    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>MAC</th>
                <th>Наименование</th>
                <th>Создано</th>
                <th>Кем создано</th>
                <th>Действия</th>
            </tr>
        </thead>
        <tbody>
            <?php foreach ($records as $record): ?>
            <tr>
                <td><?= htmlspecialchars($record['id']) ?></td>
                <td><?= htmlspecialchars($record['maclabel']) ?></td>
                <td><?= htmlspecialchars($record['classificator']) ?></td>
                <td>
                    <div><?= date('d-m-Y H:i:s', $record['insert_ts']) ?></div>
                </td>
                <td><?= htmlspecialchars(strtolower($record['insert_user'])) ?></td>
                <td class="actions">
                    <a href="?edit=<?= urlencode($record['id']) ?>" class="button button-edit">Редактировать</a>
                    <a href="?delete=<?= urlencode($record['id']) ?>" class="button button-delete" onclick="return confirm('Вы уверены?')">Удалить</a>
                </td>
            </tr>
            <?php endforeach; ?>
        </tbody>
    </table>

    <script>
        // Функция для показа toast-уведомления
        function showToast(message, isError = false) {
            const toast = document.getElementById('toast');
            toast.textContent = message;
            toast.className = isError ? 'toast error show' : 'toast show';

            setTimeout(() => {
                toast.className = 'toast';
            }, 3000);
        }

        // Проверка параметра operation в URL и показ соответствующего сообщения
        document.addEventListener('DOMContentLoaded', function() {
            const urlParams = new URLSearchParams(window.location.search);
            const operation = urlParams.get('operation');

            if (operation === 'created') {
                showToast('Запись успешно создана');
            } else if (operation === 'updated') {
                showToast('Запись успешно обновлена');
            } else if (operation === 'deleted') {
                showToast('Запись успешно удалена');
            }

            // Удаляем параметр operation из URL без перезагрузки страницы
            if (operation) {
                const cleanUrl = window.location.protocol + '//' + window.location.host + window.location.pathname;
                window.history.replaceState({}, document.title, cleanUrl);
            }
        });
    </script>
</body>
</html>

Сценарий проверки работы МРД в веб-приложении#

Важно

Документация дорабатывается по мере развития продуктов Группы Астра и по пожеланиям пользователей.

Ваши пожелания и замечания направляйте на почту docs@astralinux.ru