MidТеория21 min

Управление данными и compliance

GDPR, PCI DSS, классификация данных, audit logging и обеспечение соответствия

Что такое Data Governance

Data governance — совокупность процессов, политик и стандартов для управления данными в организации. Включает: кто владеет данными, как они классифицированы, кто имеет доступ, как долго хранятся.

Ключевые регуляции

Регуляция Сфера Регион Штрафы
GDPR Персональные данные EU/EEA До 4% годового оборота
PCI DSS Данные платёжных карт Глобально Штрафы + потеря права принимать платежи
HIPAA Медицинские данные США До $1.5M за категорию нарушения
SOC 2 Безопасность сервисов Глобально Потеря доверия клиентов
CCPA Персональные данные Калифорния $2,500-$7,500 за нарушение

Классификация данных

Уровень Описание Примеры Меры защиты
Public Общедоступные Маркетинговые материалы Минимальные
Internal Внутренние Внутренние документы Контроль доступа
Confidential Конфиденциальные Финансы, контракты Шифрование + ACL
Restricted Строго ограниченные PII, данные карт, медицина Шифрование + аудит + MFA

GDPR: ключевые требования

Права субъектов данных

Право Описание Техническая реализация
Right to Access Получить свои данные Data export API
Right to Rectification Исправить данные Edit API
Right to Erasure Удалить данные Data deletion pipeline
Right to Portability Перенести данные Export в стандартном формате
Right to Object Отказ от обработки Consent management

Реализация GDPR Data Export

<?php

declare(strict_types=1);

namespace App\Privacy;

final readonly class GdprDataExporter
{
    public function __construct(
        private UserRepository $users,
        private OrderRepository $orders,
        private ActivityLogRepository $activityLogs,
    ) {}

    /**
     * Export all personal data for a user (GDPR Article 20).
     *
     * @return array<string, mixed> Machine-readable personal data
     */
    public function exportUserData(string $userId): array
    {
        $user = $this->users->findById($userId);

        if ($user === null) {
            throw new \RuntimeException('User not found');
        }

        return [
            'export_metadata' => [
                'exported_at' => (new \DateTimeImmutable())->format(\DATE_ATOM),
                'format_version' => '1.0',
                'data_controller' => 'Example Company Ltd.',
            ],
            'personal_information' => [
                'name' => $user->getName(),
                'email' => $user->getEmail(),
                'phone' => $user->getPhone(),
                'address' => $user->getAddress(),
                'registered_at' => $user->getCreatedAt()->format(\DATE_ATOM),
            ],
            'orders' => $this->exportOrders($userId),
            'activity_log' => $this->exportActivityLog($userId),
            'consents' => $this->exportConsents($userId),
        ];
    }

    /**
     * Delete all personal data (GDPR Article 17 — Right to Erasure).
     */
    public function deleteUserData(string $userId): DeletionReport
    {
        $deletedItems = [];

        // Anonymize orders (keep for accounting, remove PII)
        $orderCount = $this->orders->anonymizeByUser($userId);
        $deletedItems['orders_anonymized'] = $orderCount;

        // Delete activity logs
        $logCount = $this->activityLogs->deleteByUser($userId);
        $deletedItems['activity_logs_deleted'] = $logCount;

        // Delete user account
        $this->users->delete($userId);
        $deletedItems['user_deleted'] = true;

        return new DeletionReport(
            userId: $userId,
            deletedAt: new \DateTimeImmutable(),
            items: $deletedItems,
        );
    }

    private function exportOrders(string $userId): array
    {
        $orders = $this->orders->findByUser($userId);

        return array_map(static fn($order) => [
            'order_id' => $order->getId(),
            'date' => $order->getCreatedAt()->format(\DATE_ATOM),
            'total' => $order->getTotal(),
            'status' => $order->getStatus(),
        ], $orders);
    }

    private function exportActivityLog(string $userId): array
    {
        return $this->activityLogs->findByUser($userId, limit: 1000);
    }

    private function exportConsents(string $userId): array
    {
        // Return all consent records
        return [];
    }
}

final readonly class DeletionReport
{
    public function __construct(
        public string $userId,
        public \DateTimeImmutable $deletedAt,
        public array $items,
    ) {}
}
## PCI DSS

PCI DSS (Payment Card Industry Data Security Standard) — стандарт безопасности для обработки платёжных карт.

12 требований PCI DSS

# Требование Категория
1 Firewall для защиты данных карт Сеть
2 Не использовать vendor defaults Конфигурация
3 Защита хранимых данных карт Хранение
4 Шифрование при передаче Транспорт
5 Антивирус на всех системах Защита
6 Безопасная разработка Разработка
7 Ограничение доступа к данным Доступ
8 Уникальный ID для каждого пользователя Идентификация
9 Физическая безопасность Физика
10 Мониторинг и логирование Мониторинг
11 Регулярное тестирование Тестирование
12 Политика безопасности Управление

Совет: Если возможно, используйте PCI-compliant платёжного провайдера (Stripe, Braintree). Данные карт не проходят через ваш сервер, что значительно снижает scope PCI DSS.

Audit Logging

Audit logging — запись всех значимых действий для обеспечения подотчётности и compliance.

Что логировать

Категория События
Аутентификация Login, logout, failed attempts, MFA
Авторизация Access granted, access denied
Данные Create, read, update, delete PII
Конфигурация Settings changes, role changes
Система Startup, shutdown, errors

Audit Logger

<?php

declare(strict_types=1);

namespace App\Audit;

final readonly class AuditLogger
{
    public function __construct(
        private AuditRepository $repository,
    ) {}

    /**
     * Log an auditable event.
     */
    public function log(AuditEvent $event): void
    {
        $this->repository->store($event);
    }

    /**
     * Log data access (for GDPR compliance).
     */
    public function logDataAccess(
        string $actorId,
        string $dataSubjectId,
        string $dataType,
        string $action,
        string $justification,
    ): void {
        $this->log(new AuditEvent(
            eventType: 'data_access',
            actorId: $actorId,
            resourceType: $dataType,
            resourceId: $dataSubjectId,
            action: $action,
            metadata: [
                'justification' => $justification,
                'data_classification' => 'restricted',
            ],
        ));
    }

    /**
     * Log configuration change.
     */
    public function logConfigChange(
        string $actorId,
        string $setting,
        mixed $oldValue,
        mixed $newValue,
    ): void {
        $this->log(new AuditEvent(
            eventType: 'config_change',
            actorId: $actorId,
            resourceType: 'configuration',
            resourceId: $setting,
            action: 'update',
            metadata: [
                'old_value' => $oldValue,
                'new_value' => $newValue,
            ],
        ));
    }
}

final readonly class AuditEvent
{
    public \DateTimeImmutable $timestamp;

    public function __construct(
        public string $eventType,
        public string $actorId,
        public string $resourceType,
        public string $resourceId,
        public string $action,
        public array $metadata = [],
    ) {
        $this->timestamp = new \DateTimeImmutable();
    }
}
## Data Retention Policies
Тип данных Срок хранения Основание
Транзакции 7 лет Налоговое законодательство
Логи доступа 1-3 года PCI DSS, SOC 2
Персональные данные До отзыва согласия GDPR
Audit logs 3-7 лет Compliance
Сессионные данные 30 дней Операционные нужды

Автоматическая очистка данных

<?php

declare(strict_types=1);

namespace App\Retention;

final readonly class DataRetentionService
{
    /**
     * Identifiers cannot be parameterized, so allowed targets are whitelisted.
     * Interpolating a table or column name straight into SQL is an injection
     * point even when every value is bound.
     *
     * @var array<string, string> Table => timestamp column
     */
    private const array RETENTION_TARGETS = [
        'session_logs' => 'created_at',
        'access_logs' => 'created_at',
    ];

    public function __construct(
        private Connection $db,
        private AuditLogger $audit,
    ) {}

    /**
     * Apply retention policies — delete expired data.
     * Run as a daily cron job.
     *
     * @return array<string, int> Number of deleted records per table
     */
    public function applyRetentionPolicies(): array
    {
        $results = [];

        // Delete session logs older than 30 days
        $results['session_logs'] = $this->deleteOlderThan('session_logs', 30);

        // Delete access logs older than 365 days
        $results['access_logs'] = $this->deleteOlderThan('access_logs', 365);

        // Anonymize completed orders older than 2 years (keep for stats, remove PII)
        $results['orders_anonymized'] = $this->anonymizeOrders(daysOld: 730);

        // Log the retention run
        $this->audit->log(new AuditEvent(
            eventType: 'retention_policy',
            actorId: 'system',
            resourceType: 'data_retention',
            resourceId: 'daily_run',
            action: 'delete',
            metadata: $results,
        ));

        return $results;
    }

    private function deleteOlderThan(string $table, int $days): int
    {
        $column = self::RETENTION_TARGETS[$table]
            ?? throw new \InvalidArgumentException("Table '{$table}' is not a retention target");

        $cutoff = (new \DateTimeImmutable())->modify("-{$days} days")->format('Y-m-d');

        return $this->db->executeStatement(
            "DELETE FROM {$table} WHERE {$column} < :cutoff",
            ['cutoff' => $cutoff],
        );
    }

    private function anonymizeOrders(int $daysOld): int
    {
        $cutoff = (new \DateTimeImmutable())->modify("-{$daysOld} days")->format('Y-m-d');

        return $this->db->executeStatement(
            "UPDATE orders SET
                customer_name = 'ANONYMIZED',
                customer_email = NULL,
                shipping_address = NULL
             WHERE created_at < :cutoff AND customer_name != 'ANONYMIZED'",
            ['cutoff' => $cutoff],
        );
    }
}
## Consent Management
<?php

declare(strict_types=1);

namespace App\Privacy;

enum ConsentPurpose: string
{
    case Marketing = 'marketing';
    case Analytics = 'analytics';
    case ThirdPartySharing = 'third_party_sharing';
    case Profiling = 'profiling';
    case Newsletter = 'newsletter';
}

final readonly class ConsentManager
{
    public function __construct(
        private Connection $db,
        private AuditLogger $audit,
    ) {}

    /**
     * Record user consent (GDPR requires proof of consent).
     */
    public function grantConsent(
        string $userId,
        ConsentPurpose $purpose,
        string $source,
        // Passed in explicitly: reading $_SERVER here would record whatever
        // the front controller happened to leave there, including a proxy
        // header an attacker controls.
        ?string $ipAddress = null,
    ): void {
        $this->db->insert('user_consents', [
            'user_id' => $userId,
            'purpose' => $purpose->value,
            'granted' => true,
            'source' => $source,
            'granted_at' => (new \DateTimeImmutable())->format('Y-m-d H:i:s'),
            'ip_address' => $ipAddress,
        ]);

        $this->audit->logDataAccess($userId, $userId, 'consent', 'grant', $purpose->value);
    }

    /**
     * Check if user has given consent for a specific purpose.
     */
    public function hasConsent(string $userId, ConsentPurpose $purpose): bool
    {
        $result = $this->db->fetchOne(
            'SELECT granted FROM user_consents
             WHERE user_id = :userId AND purpose = :purpose
             ORDER BY granted_at DESC LIMIT 1',
            ['userId' => $userId, 'purpose' => $purpose->value],
        );

        return $result === true || $result === 1 || $result === '1';
    }

    /**
     * Revoke consent.
     */
    public function revokeConsent(string $userId, ConsentPurpose $purpose): void
    {
        $this->db->insert('user_consents', [
            'user_id' => $userId,
            'purpose' => $purpose->value,
            'granted' => false,
            'source' => 'user_request',
            'granted_at' => (new \DateTimeImmutable())->format('Y-m-d H:i:s'),
        ]);

        $this->audit->logDataAccess($userId, $userId, 'consent', 'revoke', $purpose->value);
    }
}
## Итоги
Концепция Суть
Data Classification Разделение данных по уровню чувствительности
GDPR Права субъектов данных, consent, data export
PCI DSS 12 требований для работы с платёжными картами
Audit Logging Запись всех значимых действий
Data Retention Автоматическое удаление устаревших данных
Consent Management Управление согласиями пользователей