Рекомендации ИБ: Управление доступом и секретами в MLSec
Рекомендации ИБ: Управление доступом и секретами в MLSec
Версия: 2.0 · Обновлено: 2026-04-23 · Теги: Vault, SOPS, Keycloak, RBAC, PoLP, audit, NHI, dynamic secrets · Tier: 1-4
TL;DR. HashiCorp Vault / OpenBao, SOPS, Keycloak, RBAC / ABAC, Row-Level Security, ротация секретов, audit log для ML-нагрузок. Отдельная ветвь - Non-Human Identity для агентов и автоматизации. Секрет, лежащий в env-переменной 6 месяцев, - это не секрет.
Навигация: обзор · инструменты · RBAC/ABAC · dynamic secrets · NHI для агентов · новый раздел интерактивных сценариев leak-атак · новый раздел "С чего начать по вашей роли".
1. Краткое содержание разделов
Глава охватывает десять связанных направлений. Ниже сводка ключевых идей каждого из них.
1.1. Цели и риски незащищённых секретов в ML-процессах
Незащищённые секреты (API-ключи, токены ML-трекинг-систем, пароли баз данных, ключи объектных хранилищ) в ML-пайплайнах формируют критические векторы атак:
- Компрометация обучающей инфраструктуры - злоумышленник, получивший токен MLflow или ключ S3, может подменить обучающие данные (data poisoning) или внедрить backdoor в модель.
- Утечка модельных артефактов - доступ к Model Registry позволяет извлечь проприетарные модели (model theft) или провести атаку model inversion для восстановления обучающих данных.
- Несанкционированный доступ к персональным данным - секреты к хранилищам с PII/PHI открывают путь к массовым утечкам, влекущим за собой нарушение GDPR и иных регуляторных требований.
- Lateral movement - статические, долгоживущие секреты, единожды скомпрометированные, позволяют атакующему перемещаться между компонентами инфраструктуры без ограничений по времени.
Цель - полностью исключить статические секреты из кода, артефактов и логов; минимизировать время жизни каждого секрета; обеспечить аудит каждого обращения к секрету.
1.2. Безопасное хранение ключей и токенов (Vault, SOPS)
HashiCorp Vault выступает центральным хранилищем секретов с поддержкой:
- KV v2 - статические секреты (API-токены MLflow, ключи S3) с версионированием.
- Database Secrets Engine - динамические учётные данные для PostgreSQL, MySQL, MongoDB с автоматическим отзывом по TTL.
- PKI Engine - выпуск TLS-сертификатов для межсервисного mTLS.
- Transit Engine - шифрование данных «на лету» без хранения открытого текста в Vault; используется как backend для SOPS.
Mozilla SOPS дополняет Vault, обеспечивая шифрование файлов конфигурации (YAML, JSON, ENV) непосредственно в Git-репозиториях. SOPS шифрует только значения, оставляя ключи читаемыми - это сохраняет возможность code review и diff.
1.3. Интеграция управления секретами с GitLab CI/CD
GitLab поддерживает нативную интеграцию с Vault через JWT-аутентификацию: каждый CI/CD-job получает JWT-токен через id_tokens: (стандарт с GitLab 15.7; legacy CI_JOB_JWT удалён в GitLab 17.0 в мае 2024 года), который обменивается на временный Vault-токен с ограниченными правами. Секреты можно получать через:
- Директиву
secrets:в.gitlab-ci.yml(нативная интеграция). - Vault CLI в
before_scriptс явной аутентификацией через JWT. - SOPS с Vault Transit backend для расшифровки конфигов.
Дополнительно используются masked variables (маскирование в логах) и protected variables (доступ только из protected branches/tags).
1.4. Автоматическая ротация секретов и PoLP
Динамические секреты Vault с коротким TTL - основной механизм ротации. Рекомендуемые TTL:
| Компонент | TTL | Обоснование |
|---|---|---|
| CI/CD job credentials | 1h | Ограничено временем выполнения job |
| Учётные данные для обучения модели | 4h | Длительные тренировки GPU-задач |
| Model serving credentials | 15m | Минимизация окна компрометации |
| Мониторинг и аудит | 24h | Непрерывный сбор метрик |
| SSH OTP для GPU-нод | 30m | Разовые операции администрирования |
Статические секреты (API-токены MLflow, webhook-секреты) ротируются по расписанию: критичные - еженедельно, остальные - ежемесячно.
1.5. Принцип наименьших привилегий (PoLP) в MLSec
PoLP применяется на каждом уровне стека:
- Vault - гранулярные HCL-политики с явными
denyна нерелевантные пути. - Kubernetes RBAC - ServiceAccount с минимальными verbs (только
get,listбезcreate/delete). - PostgreSQL RLS - Row-Level Security на таблицах с данными обучения.
- Keycloak - роли с минимальными scopes, привязанные к конкретным стадиям ML-цикла.
1.6. Аудит доступа
Аудит строится на централизованном сборе логов из всех компонентов:
| Источник | Что логируется | Формат | Интеграция |
|---|---|---|---|
| Vault Audit Log | Все операции с секретами | JSON (file + syslog) | Filebeat -> ELK |
| GitLab Audit Events | Deploy, изменение переменных, доступ | JSON API | Webhook -> ELK |
| Keycloak Events | Аутентификация, авторизация, TOKEN_EXCHANGE | JSON/Syslog | Logstash |
| PostgreSQL logs + RLS | Доступ к данным обучения | SQL Log | Filebeat |
| Kubernetes Audit | API-вызовы к кластеру | JSON | Fluent Bit -> ELK |
| MinIO Access Logs | Доступ к артефактам и датасетам | JSON | Filebeat |
| Istio EnvoyAccessLog | Межсервисные вызовы | JSON | Fluent Bit |
| MLflow | Эксперименты, модели, переходы стадий | PostgreSQL | Custom exporter |
1.7. Политики доступа к данным, моделям и пайплайнам
Политики доступа строятся на трёх осях:
- Данные - классификация по уровням чувствительности (Public, Internal, Confidential, Restricted) с привязкой к ролям через Vault-политики и PostgreSQL RLS.
- Модели - контроль жизненного цикла (Experiment -> Staging -> Pre-production -> Production) с обязательным security gate перед промоцией в production.
- Пайплайны - Separation of Duties через GitLab CODEOWNERS и multi-stage approvals.
1.8. Межсервисная аутентификация (Keycloak, Istio)
- Keycloak выступает IdP (Identity Provider), выдавая JWT-токены сервисным аккаунтам через Client Credentials Grant. TTL access-токена - 5 минут, refresh - 30 минут.
- Istio обеспечивает mTLS между всеми сервисами (PeerAuthentication с mode: STRICT) и контроль доступа через AuthorizationPolicy на уровне путей и HTTP-методов.
- Vault аутентифицирует сервисы через JWT/OIDC auth method, привязанный к Keycloak.
1.9. Пример типовой архитектуры и ролей
flowchart TB
IDP["Keycloak IdP<br/>Roles: data-engineer, ml-engineer,<br/>mlops, secops, pm"]
subgraph PLANE[Control plane]
direction LR
CI["GitLab CI/CD"]
V["Vault<br/>Secrets / PKI"]
K8S["Kubernetes<br/>RBAC"]
end
MLP["ML Platform<br/>MLflow / Model Registry<br/>Training Data"]
IDP -- OIDC / JWT --> CI
IDP -- OIDC / JWT --> V
IDP -- OIDC / JWT --> K8S
CI --> MLP
V --> MLP
K8S --> MLP
Каждый компонент получает доступ только через Keycloak-аутентификацию и Vault-авторизацию. Прямой доступ к хранилищам секретов и данных запрещён.
1.10. Универсальные практики для open-source инфраструктуры
Все рекомендованные инструменты (Vault, SOPS, Keycloak, Istio, OPA, GitLab CE) - полностью open-source и self-hosted, что обеспечивает:
- Полный контроль над данными и секретами без зависимости от облачных провайдеров.
- Соответствие требованиям локализации данных.
- Возможность глубокой кастомизации под специфику ML-инфраструктуры.
- Воспроизводимость инфраструктуры через IaC (Terraform, Ansible, Helm).
2. Верификация инструментов и практик
2.1. Оценка зрелости предлагаемого стека
| Инструмент | Версия | Назначение | Зрелость | Альтернативы |
|---|---|---|---|---|
| HashiCorp Vault | 1.15+ | Централизованное хранилище секретов | Production-ready, широко используется в enterprise | CyberArk Conjur, AWS Secrets Manager |
| Mozilla SOPS | 3.8+ | Шифрование конфигов в Git | Стабильный, активно поддерживается | Sealed Secrets, git-crypt |
| Keycloak | 24+ | IdP, SSO, OIDC/SAML | Production-ready, CNCF-проект | Authentik, Dex, Zitadel |
| Istio | 1.20+ | Service mesh, mTLS | Production-ready, CNCF graduated | Linkerd, Cilium |
| OPA/Gatekeeper | 0.60+ | Policy-as-Code | Production-ready, CNCF graduated | Kyverno, Polaris |
| GitLab CE | 16+ | CI/CD, source control | Production-ready | GitHub Actions, Jenkins |
2.2. Критерии верификации
Каждый инструмент проверен по следующим критериям:
- Поддержка динамических секретов - Vault Database и PKI Engines подтверждённо генерируют credentials с автоматическим отзывом по TTL.
- JWT-интеграция - GitLab CI
id_tokensкорректно аутентифицируются в Vault через JWT auth method. - Шифрование at-rest - SOPS с Vault Transit backend обеспечивает шифрование конфигурационных файлов без хранения ключей в репозитории.
- mTLS - Istio PeerAuthentication в режиме STRICT принудительно включает mTLS между всеми сервисами в namespace.
- Row-Level Security - PostgreSQL RLS корректно ограничивает доступ к строкам на основе роли текущего пользователя.
- Policy-as-Code - OPA Rego-политики интегрируются в CI/CD для автоматической проверки соответствия перед деплоем.
2.3. Известные ограничения
| Ограничение | Влияние | Митигация |
|---|---|---|
| Vault - single point of failure | Недоступность секретов при отказе | HA-кластер (Raft), auto-unseal через HSM/Cloud KMS |
| SOPS - ручная ротация ключей шифрования | Устаревание ключей | Автоматизация через CI/CD job с Vault Transit key rotation |
| Keycloak - высокое потребление ресурсов | Проблемы производительности при масштабировании | Горизонтальное масштабирование, Infinispan кластер |
| Istio - операционная сложность | Рост накладных расходов на сопровождение | Ambient mesh (sidecarless), постепенное внедрение |
| OPA - кривая обучения Rego | Задержки во внедрении | Готовые библиотеки политик, обучение команды |
3. Рекомендации ИБ
3.1. Обязательное внедрение HashiCorp Vault
Приоритет: КРИТИЧЕСКИЙ
Vault является центральным элементом управления секретами. Без него невозможно обеспечить динамическую выдачу, ротацию и аудит секретов.
Требования к внедрению:
- Развернуть Vault в HA-конфигурации (минимум 3 ноды) с Raft-хранилищем.
- Настроить auto-unseal через HSM или облачный KMS (недопустимо хранить unseal-ключи на тех же серверах).
- Включить все Engines, необходимые для ML-стека:
- KV v2 - для статических секретов (API-токены MLflow, webhook-секреты).
- Database - для динамических credentials PostgreSQL, MongoDB.
- PKI - для выпуска TLS-сертификатов (если Istio не покрывает все сценарии).
- Transit - как backend для SOPS и для шифрования чувствительных полей данных.
- SSH OTP - для доступа к GPU-нодам и training-серверам.
- Настроить JWT auth method для интеграции с GitLab CI/CD.
- Настроить OIDC auth method для интеграции с Keycloak (интерактивный доступ).
- Включить audit-логирование на оба backend (file + syslog) для отказоустойчивости.
Пример активации ключевых компонентов:
# HA-кластер Vault с Raft
vault operator raft list-peers
# Включение движков секретов
vault secrets enable -path=secret kv-v2
vault secrets enable database
vault secrets enable pki
vault secrets enable transit
vault secrets enable ssh
# JWT auth для GitLab CI
vault auth enable jwt
vault write auth/jwt/config \
jwks_url="https://gitlab.company.com/-/jwks" \
bound_issuer="https://gitlab.company.com"
# OIDC auth для Keycloak
vault auth enable oidc
vault write auth/oidc/config \
oidc_discovery_url="https://keycloak.company.com/realms/ml-platform" \
oidc_client_id="vault-client" \
oidc_client_secret="$OIDC_SECRET" \
default_role="default"
3.2. Политика Zero Trust для ML-инфраструктуры
Приоритет: КРИТИЧЕСКИЙ
ML-инфраструктура должна работать в модели Zero Trust - ни один компонент не доверяет другому по умолчанию, каждый запрос аутентифицируется и авторизуется.
Принципы:
- Verify explicitly - каждый вызов между сервисами ML-платформы аутентифицируется через mTLS (Istio) или JWT (Keycloak).
- Least privilege access - каждый компонент получает минимально необходимый набор прав (см. раздел 3.4).
- Assume breach - архитектура проектируется с учётом возможной компрометации любого отдельного компонента. Blast radius минимизируется через сегментацию, короткие TTL и гранулярные политики.
Конкретные меры:
- Запретить прямой доступ к базам данных из training-кода - только через Vault динамические credentials.
- Запретить хранение секретов в переменных окружения Kubernetes - только через Vault Agent Injector.
- Запретить межсервисное взаимодействие без mTLS - Istio PeerAuthentication в режиме STRICT.
- Запретить доступ к Model Registry без JWT-токена с соответствующим scope.
- Внедрить network policies в Kubernetes для ограничения сетевого доступа между namespace.
# Istio: обязательный mTLS для всех ML-сервисов
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: ml-strict-mtls
namespace: ml-production
spec:
mtls:
mode: STRICT
---
# Kubernetes: Network Policy для изоляции ML-namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ml-production-isolation
namespace: ml-production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
purpose: api-gateway
- namespaceSelector:
matchLabels:
purpose: monitoring
egress:
- to:
- namespaceSelector:
matchLabels:
purpose: vault
- namespaceSelector:
matchLabels:
purpose: database
3.3. Формализованная классификация данных (4 уровня)
Приоритет: ВЫСОКИЙ
Все данные, используемые в ML-процессах, классифицируются по четырём уровням. Классификация определяет требования к шифрованию, контролю доступа и аудиту.
| Уровень | Описание | Примеры в ML | Шифрование | Доступ | Аудит |
|---|---|---|---|---|---|
| Public | Открытые данные | Публичные датасеты, синтетические данные, open-source модели | At-rest | Все роли ML-платформы | Стандартный |
| Internal | Внутренние данные | Агрегированные метрики, анонимизированные датасеты, параметры обучения | At-rest + in-transit | ML Engineers, Data Engineers | Стандартный |
| Confidential | Конфиденциальные данные | Псевдонимизированные данные с бизнес-контекстом, проприетарные модели, feature stores | At-rest + in-transit + field-level | Senior ML Engineers, Data Engineers (с обоснованием) | Обязательный |
| Restricted | Ограниченные данные | PII, PHI, финансовые данные, модели с встроенными PII | At-rest + in-transit + field-level + Vault Transit | Data Engineers + явное одобрение Юрист | Обязательный + real-time alerts |
Реализация через Vault-политики:
# Политика доступа по классификации данных
path "secret/data/datasets/public/*" {
capabilities = ["read", "list"]
}
path "secret/data/datasets/internal/*" {
capabilities = ["read"]
}
path "secret/data/datasets/confidential/*" {
capabilities = ["deny"]
}
path "secret/data/datasets/restricted/*" {
capabilities = ["deny"]
}
Реализация через PostgreSQL RLS:
-- Включение Row-Level Security
ALTER TABLE training_data ENABLE ROW LEVEL SECURITY;
-- Политика для ML-инженера: только public и internal
CREATE POLICY ml_engineer_data_access ON training_data
FOR SELECT
TO ml_engineer_role
USING (
classification IN ('public', 'internal')
);
-- Политика для Data Engineer: полный доступ
CREATE POLICY data_engineer_full_access ON training_data
FOR ALL
TO data_engineer_role
USING (true);
-- View-based доступ для изоляции столбцов с PII
CREATE VIEW training_data_anonymized AS
SELECT id, feature_1, feature_2, label, classification
FROM training_data
WHERE classification IN ('public', 'internal');
-- PII-столбцы (name, email, phone) не включены в view
GRANT SELECT ON training_data_anonymized TO ml_engineer_role;
3.4. Обязательное применение PoLP на всех уровнях
Приоритет: КРИТИЧЕСКИЙ
Принцип наименьших привилегий применяется на каждом уровне стека без исключений.
Разделение прав по стадиям ML-цикла:
| Роль | Данные | Модели | Пайплайны | Production | Секреты |
|---|---|---|---|---|---|
| Data Scientist (Train) | R (анонимизированные) | R/W (experiments) | R | - | R (training secrets) |
| Data Engineer | R/W (все уровни) | - | R/W (data pipelines) | - | R (data secrets) |
| ML Engineer (Deploy) | R (обезличенные) | R/W (staging) | R | - | R (staging secrets) |
| MLOps Engineer | R | R/W (production) | R/W | Deploy (с approval) | R (prod secrets, ограничено) |
| Security/Compliance (Audit) | Audit only | Audit only | R/W (security policies) | Audit only | R (audit paths) |
| CI/CD Service Account | R (datasets) | W (artifacts) | Execute | Controlled (с gates) | R (ci/cd secrets) |
Правило: ни одна роль не должна иметь прав, выходящих за рамки её прямых обязанностей. Доступ к production-секретам запрещён для ролей, не связанных с деплоем.
Реализация в Kubernetes RBAC:
# ServiceAccount для ML-сервиса с минимальными правами
apiVersion: v1
kind: ServiceAccount
metadata:
name: ml-serving-sa
namespace: ml-production
annotations:
vault.hashicorp.com/role: "ml-serving"
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-inject-secret-db: "database/creds/ml-serving-role"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ml-serving-role
namespace: ml-production
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get"]
# Явный запрет: нет доступа к secrets Kubernetes напрямую
# Секреты получаются только через Vault Agent Injector
Конструктор Vault-политики для роли ML Engineer (Deploy)
Включайте только те capabilities, которые реально нужны роли. Справа считается «privilege footprint» (чем меньше — тем ближе к PoLP) и появляются нарушения принципа least-privilege.
{
"id": "ch08-vault-policy",
"title": "Vault policy для ML Engineer (Deploy → Staging)",
"description": "Каждый toggle — конкретная capability на конкретный path. Меньше включено — лучше PoLP. Включайте только то, что действительно требуется по матрице ролей §4.1.",
"scoreLabel": "Privilege footprint (меньше — лучше)",
"scoreMax": 100,
"groups": [
{
"name": "Datasets",
"items": [
{ "id": "ds-public-read", "label": "secret/data/datasets/public/* → read", "weight": 5, "default": true, "description": "Базовое чтение публичных датасетов — обычно нужно." },
{ "id": "ds-internal-read", "label": "secret/data/datasets/internal/* → read", "weight": 8, "default": true, "description": "Внутренние данные — часто нужно для validation/staging тестов." },
{ "id": "ds-conf-read", "label": "secret/data/datasets/confidential/* → read", "weight": 12, "description": "Confidential data — НЕ должно быть доступно ML Engineer (Deploy). Это роль Data Scientist." },
{ "id": "ds-restricted-read", "label": "secret/data/datasets/restricted/* → read", "weight": 18, "description": "Restricted (PII, биометрия) — категорически нет. Используйте Data Scientist роль с DPIA." },
{ "id": "ds-write", "label": "secret/data/datasets/* → create/update", "weight": 15, "description": "Запись датасетов — это роль Data Engineer. ML Engineer не должен модифицировать датасеты." }
]
},
{
"name": "Models",
"items": [
{ "id": "m-staging-rw", "label": "secret/data/models/staging/* → read/create/update", "weight": 6, "default": true, "recommended": true, "description": "Профильная capability — деплой в staging." },
{ "id": "m-staging-delete", "label": "secret/data/models/staging/* → delete", "weight": 10, "description": "Удаление staging-моделей — для cleanup. Лучше доверить CI/CD job, не интерактивному пользователю." },
{ "id": "m-prod-read", "label": "secret/data/models/production/* → read", "weight": 5, "default": true, "description": "Read-only на production — для отладки." },
{ "id": "m-prod-write", "label": "secret/data/models/production/* → write", "weight": 20, "description": "Запись в production — только для MLOps Engineer с MFA + approval." }
]
},
{
"name": "Infrastructure secrets",
"items": [
{ "id": "infra-db-staging", "label": "database/creds/ml-staging-role → read", "weight": 5, "default": true, "recommended": true, "description": "Динамические креды для staging БД — TTL 1 час." },
{ "id": "infra-db-prod", "label": "database/creds/ml-prod-role → read", "weight": 18, "description": "Production БД — нет для роли Deploy (Staging)." },
{ "id": "infra-cloud-staging", "label": "aws/creds/ml-staging → read", "weight": 6, "description": "Cloud creds для staging — если используется." },
{ "id": "infra-cloud-prod", "label": "aws/creds/ml-prod → read", "weight": 18, "description": "Production cloud — категорически нет." }
]
},
{
"name": "Vault administration",
"items": [
{ "id": "vault-policies", "label": "sys/policies/* → list/read/create", "weight": 25, "description": "Управление политиками Vault — только Vault Admins, не ML Engineer." },
{ "id": "vault-auth", "label": "auth/* → create", "weight": 25, "description": "Регистрация новых auth methods — admin-only." },
{ "id": "vault-leases", "label": "sys/leases/lookup → read (свои leases)", "weight": 3, "default": true, "description": "Просмотр своих lease'ов — безопасно." }
]
}
],
"rules": [
{ "id": "no-restricted", "type": "forbid-all", "items": ["ds-restricted-read"], "message": "ML Engineer (Deploy) не должен иметь доступа к restricted/PII данным — это роль Data Scientist с DPIA." },
{ "id": "no-prod-write", "type": "forbid-all", "items": ["m-prod-write"], "message": "Запись в production-модели — это роль MLOps Engineer, не Deploy. Promotion идёт через signed event с MFA." },
{ "id": "no-prod-cloud", "type": "forbid-all", "items": ["infra-cloud-prod"], "message": "Production cloud creds — для MLOps Engineer. Deploy в staging не требует prod-cloud." },
{ "id": "no-vault-admin", "type": "forbid-all", "items": ["vault-policies"], "message": "Управление Vault-политиками — только Vault Admin. ML Engineer не должен иметь sys/policies." },
{ "id": "min-staging", "type": "require-all", "items": ["m-staging-rw"], "message": "Без write на staging models роль не выполняет своё назначение." }
],
"thresholds": [
{ "from": 0, "to": 25, "label": "Идеальный PoLP — минимум attack surface", "tone": "ok" },
{ "from": 25, "to": 50, "label": "Целевой уровень — стандартный профиль ML Engineer (Deploy)", "tone": "info" },
{ "from": 50, "to": 75, "label": "Повышенный footprint — proceed with caution, требует обоснования", "tone": "warn" },
{ "from": 75, "to": 100, "label": "Чрезмерные привилегии — типичный путь к incident, фактически Vault Admin", "tone": "err" }
]
}
3.5. Динамические секреты для CI/CD
Приоритет: КРИТИЧЕСКИЙ
Каждый CI/CD job должен получать уникальные, короткоживущие credentials через Vault. Статические токены в GitLab CI variables допустимы только для начальной аутентификации в Vault.
Схема интеграции GitLab CI + Vault:
# Вариант 1: нативная интеграция через secrets keyword
variables:
VAULT_SERVER_URL: "https://vault.company.internal"
train_model:
stage: train
id_tokens:
VAULT_ID_TOKEN:
aud: https://vault.company.internal
secrets:
MLFLOW_TOKEN:
vault: ml/cicd/mlflow_token@secret
token: $VAULT_ID_TOKEN
S3_ACCESS_KEY:
vault: ml/cicd/s3_access_key@secret
token: $VAULT_ID_TOKEN
script:
- python train.py
# Вариант 2: динамические credentials БД через Vault CLI
train_with_dynamic_creds:
stage: train
id_tokens:
VAULT_ID_TOKEN:
aud: https://vault.company.internal
script:
- export VAULT_TOKEN=$(vault write -field=token auth/jwt/login
role=ml-cicd-role jwt=$VAULT_ID_TOKEN)
- export DB_CREDS=$(vault read -format=json database/creds/ml-training-role)
- export DB_USER=$(echo $DB_CREDS | jq -r '.data.username')
- export DB_PASS=$(echo $DB_CREDS | jq -r '.data.password')
- python train.py --db-user=$DB_USER --db-pass=$DB_PASS
after_script:
# Credentials автоматически отзываются по TTL,
# но можно отозвать явно для немедленной очистки
- vault lease revoke -prefix database/creds/ml-training-role
# Вариант 3: SOPS + Vault Transit для зашифрованных конфигов
decrypt_and_train:
stage: train
image: python:3.13
before_script:
- apt-get update && apt-get install -y sops
# GitLab 15.7+: задайте id_tokens с aud=$VAULT_ADDR в job-блоке —
# переменная VAULT_ID_TOKEN будет инжектирована автоматически.
# Legacy CI_JOB_JWT удалён в GitLab 17.0 (май 2024).
- export VAULT_TOKEN=$(vault write -field=token
auth/jwt/login role=ml-cicd-role jwt=$VAULT_ID_TOKEN)
- sops -d config/model_config.enc.yaml > config/model_config.yaml
script:
- python train.py --config config/model_config.yaml
after_script:
- rm -f config/model_config.yaml
Правила безопасности переменных GitLab CI:
| Тип переменной | Применение | Видимость в логах | Доступность |
|---|---|---|---|
| Обычная | Нечувствительные данные (MODEL_NAME, BATCH_SIZE) | Видна | Все ветки |
| Masked | Токены, пароли | Скрыта (замена на ***) |
Все ветки |
| Protected + Masked | Production-секреты | Скрыта | Только protected branches |
| File | Сертификаты, ключи | Записывается в файл | Настраивается |
3.6. Обязательная MFA для всех интерактивных аккаунтов
Приоритет: ВЫСОКИЙ
Все интерактивные (человеческие) аккаунты в ML-инфраструктуре должны использовать многофакторную аутентификацию (MFA).
Область применения:
- GitLab - вход в UI, операции с protected branches.
- Keycloak - вход в административную консоль и ML-платформу.
- Vault UI - интерактивный доступ к секретам (для отладки и аудита).
- Kubernetes Dashboard (если используется) - доступ к кластеру.
Конфигурация Keycloak:
{
"realm": "ml-platform",
"requiredActions": ["CONFIGURE_TOTP"],
"otpPolicyType": "totp",
"otpPolicyAlgorithm": "HmacSHA256",
"otpPolicyDigits": 6,
"otpPolicyPeriod": 30,
"otpPolicyInitialCounter": 0,
"bruteForceProtected": true,
"maxFailureWaitSeconds": 900,
"maxDeltaTimeSeconds": 43200,
"failureFactor": 5,
"permanentLockout": false,
"waitIncrementSeconds": 60
}
Исключения: сервисные аккаунты (machine-to-machine) аутентифицируются через Client Credentials Grant с коротким TTL access-токена (5 минут) и refresh-токена (30 минут) и не требуют MFA.
Какой MFA-метод подходит вашему сценарию
{
"id": "ch08-mfa-method",
"title": "Подбор MFA-метода для роли в ML-инфраструктуре",
"start": "q1",
"nodes": {
"q1": {
"type": "question",
"text": "Какой уровень доступа у пользователя?",
"sub": "MFA-метод должен быть пропорционален риску. Production secrets требуют hardware token; обычный read-only — TOTP.",
"choices": [
{ "label": "Production secrets (Vault prod, sys/policies, model registry write)", "next": "q2-prod" },
{ "label": "Sensitive read (audit logs, restricted datasets metadata)", "next": "q2-sensitive" },
{ "label": "Стандартный ML-инженер (staging, internal data)", "next": "q2-standard" },
{ "label": "Read-only / dashboards / SaaS-продукты ML-команды", "next": "leaf-totp" }
]
},
"q2-prod": {
"type": "question",
"text": "Готова ли организация раздать hardware tokens (~$50/чел)?",
"choices": [
{ "label": "Да — есть бюджет на YubiKey / Titan / SoloKey для ~50 человек с prod-доступом", "next": "leaf-yubikey" },
{ "label": "Нет, но есть managed devices с TPM (Mac / iPhone / managed Windows)", "next": "leaf-passkey" },
{ "label": "Нет — только software-tokens", "hint": "Не рекомендуется для prod", "next": "leaf-totp-with-warning" }
]
},
"q2-sensitive": {
"type": "question",
"text": "Пользователи в основном работают с одного устройства или с разных?",
"choices": [
{ "label": "Одно устройство (managed laptop)", "next": "leaf-passkey" },
{ "label": "Разные устройства (BYOD, работа из дома + офиса)", "next": "leaf-totp" }
]
},
"q2-standard": {
"type": "question",
"text": "Есть ли SSO с уже установленной MFA на корпоративном IdP?",
"choices": [
{ "label": "Да — IdP MFA проверяется на уровне SSO (Keycloak / Okta / AD FS)", "next": "leaf-sso-mfa" },
{ "label": "Нет — каждый сервис аутентифицируется отдельно", "next": "leaf-totp" }
]
},
"leaf-yubikey": {
"type": "leaf",
"tone": "ok",
"title": "Hardware token (YubiKey / Titan) — золотой стандарт",
"summary": "FIDO2 / WebAuthn — phishing-resistant, не требует battery, не подвержен malware на устройстве. Стоимость ~$50/токен компенсируется снижением incident risk.",
"details": [
"FIDO2 (резидентные ключи) — поддержка большинства IdP с 2023",
"Backup token каждому пользователю (потеря основного ≠ потеря доступа)",
"Регистрация в IdP с supervisor approval — нельзя по email только",
"Революция YubiKey 5C / 5Ci с NFC — работает на iOS / Android / desktop",
"Compliance: соответствует NIST SP 800-63B AAL3 — высший уровень"
]
},
"leaf-passkey": {
"type": "leaf",
"tone": "info",
"title": "Passkey (FIDO2 на TPM устройства)",
"summary": "Современная альтернатива hardware token: использует TPM/Secure Enclave устройства. Phishing-resistant. Не требует отдельного device.",
"details": [
"Apple iOS 16+ / macOS 13+, Android 9+, Windows 10+ с TPM 2.0",
"Sync через iCloud / Google / Microsoft Account (выбирайте корпоративную опцию для compliance)",
"Backup: registered passkey на 2-х устройствах (laptop + phone)",
"Compliance: NIST AAL2 minimum, AAL3 при device attestation",
"Подходит когда не хочется выдавать hardware tokens"
]
},
"leaf-sso-mfa": {
"type": "leaf",
"tone": "info",
"title": "SSO MFA — единая точка проверки",
"summary": "Если IdP корректно проверяет MFA, то downstream-сервисы (MLflow, Jupyter, Vault через OIDC) наследуют её. Главное — установить session timeout.",
"details": [
"Session timeout 8 часов для standard, 1 час для prod-доступа",
"Step-up authentication: при доступе к prod path — re-prompt MFA даже в активной сессии",
"Audit log: все step-up events отдельной строкой для SIEM-корреляции",
"Конфигурация Keycloak: см. §3.12 hardening"
]
},
"leaf-totp": {
"type": "leaf",
"tone": "warn",
"title": "TOTP (Google / Microsoft / 1Password Authenticator)",
"summary": "Базовый уровень MFA. Подходит для read-only / standard ML-engineer. Phishing-vulnerable, но лучше чем ничего.",
"details": [
"Bruteforce protection: 5 попыток → 15 мин lockout (см. конфиг выше)",
"Backup: рекомендовать сохранение initial secret в password manager (1Password / Bitwarden)",
"Не использовать SMS — проще phishing’у, требует phone porting attack для bypass",
"Compliance: NIST AAL2 — приемлемо для большинства Tier-3 систем"
]
},
"leaf-totp-with-warning": {
"type": "leaf",
"tone": "err",
"title": "TOTP для prod — НЕ рекомендуется",
"summary": "Phishing-resistance критична для production-доступа. TOTP можно «отдать» атакующему через AiTM (adversary-in-the-middle, например, evilginx). Hardware token — must.",
"details": [
"AiTM атаки на TOTP — задокументированы и регулярны (см. отчёты CrowdStrike, Mandiant 2023–2024)",
"Если YubiKey невозможен — минимум passkey (TPM-bound)",
"Если совсем нечего — TOTP + IP-restriction + step-up на каждое prod-action + анализ behavior anomalies",
"Регуляторика: 716-П / NIST AAL3 не пройдут TOTP-only для production"
]
}
}
}
3.7. Ротация секретов: автоматизация и мониторинг
Приоритет: ВЫСОКИЙ
Матрица ротации:
| Тип секрета | Механизм ротации | Периодичность | Ответственный |
|---|---|---|---|
| БД credentials (dynamic) | Vault Database Engine, TTL | Автоматически при каждом запросе (TTL=1h) | Vault |
| SSH-ключи (OTP) | Vault SSH Engine | Одноразовые (TTL=30m) | Vault |
| API-токены MLflow | Скрипт ротации + Vault KV | Еженедельно | CI/CD job |
| S3/MinIO access keys | Скрипт ротации + Vault KV | Еженедельно | CI/CD job |
| TLS-сертификаты | Vault PKI / cert-manager | Автоматически (TTL=30d) | Vault / cert-manager |
| Keycloak client secrets | Keycloak Admin API | Ежемесячно | CI/CD job |
| Vault root token | Ручная ротация | После каждого использования | Security team |
| SOPS encryption keys | Vault Transit key rotation | Ежеквартально | Security team |
Скрипт автоматической ротации статических секретов:
#!/bin/bash
# rotate_static_secrets.sh
# Вызывается по cron или из CI/CD scheduled pipeline
set -euo pipefail
VAULT_ADDR="https://vault.company.internal"
LOG_FILE="/var/log/ml-secops/rotation.log"
rotate_mlflow_token() {
NEW_TOKEN=$(python3 /opt/scripts/generate_mlflow_token.py)
vault kv put secret/ml/cicd/mlflow_token \
value="$NEW_TOKEN" \
rotated_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
rotated_by="rotation-service"
echo "$(date -u): MLflow token rotated successfully" >> "$LOG_FILE"
}
rotate_s3_keys() {
NEW_KEYS=$(python3 /opt/scripts/rotate_minio_keys.py)
ACCESS_KEY=$(echo "$NEW_KEYS" | jq -r '.access_key')
SECRET_KEY=$(echo "$NEW_KEYS" | jq -r '.secret_key')
vault kv put secret/ml/cicd/s3_credentials \
access_key="$ACCESS_KEY" \
secret_key="$SECRET_KEY" \
rotated_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "$(date -u): S3 credentials rotated successfully" >> "$LOG_FILE"
}
rotate_mlflow_token
rotate_s3_keys
# Уведомление в Slack/Mattermost
curl -X POST "$WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d '{"text":"Secrets rotation completed successfully"}'
Мониторинг ротации:
- Alerting при неудачной ротации (Prometheus + Alertmanager).
- Дашборд со статусом всех секретов и датой последней ротации (Grafana).
- Alert при приближении TTL статического секрета к порогу (7 дней до плановой ротации).
Калькулятор: TTL динамических секретов vs operational overhead
{
"id": "ch08-secret-ttl",
"title": "TTL Vault dynamic secret: trade-off compromise window vs Vault load",
"description": "Покрутите ползунок TTL — увидите, как меняется compromise window (сколько у атакующего времени), нагрузка на Vault, и UX-разрывы пользователей. Цифры — для типичного банка с 50–100 ML-инженерами и 200–500 CI-job/день.",
"min": 1,
"max": 1440,
"step": 1,
"default": 60,
"unit": " мин",
"axisLabel": "TTL динамического credentials",
"tracks": [
{ "label": "Compromise window (мин)", "compute": "x", "format": "fixed1", "tone": "err", "hint": "Если creds украли через 1 минуту после issue — атакующий имеет (TTL - 1) мин до автоматической revoke. Прямое прокси для blast radius." },
{ "label": "Vault renew RPS", "compute": "1500 / x", "format": "fixed1", "tone": "info", "hint": "Каждый клиент рено TTL/2. На 60 мин ≈ 25 RPS на 1500 одновременных клиентов. На 5 мин — 300 RPS, требует HA Vault кластер." },
{ "label": "% job-разрывов из-за expiration mid-job", "compute": "x < 30 ? 100 / x : 0.5 + 50 / x", "format": "%", "tone": "warn", "hint": "Long-running ML-jobs (training) могут пережить expiration — нужен renew loop в коде. На <15 мин типично теряется ~7% jobs без правильного renew." },
{ "label": "Compliance score (716-П / NIST)", "compute": "x <= 60 ? 100 : (x <= 240 ? 70 : (x <= 720 ? 30 : 10))", "format": "%", "tone": "ok", "hint": "716-П / NIST AI 600 для Tier-1 ожидает TTL ≤ 1 ч для credentials с прямым доступом к ПДн. Tier-3 — до 4 ч приемлемо." }
],
"regions": [
{ "from": 1, "to": 15, "label": "Параноидальный — для Tier-1 / production-deploy жобов; требует HA Vault", "tone": "ok" },
{ "from": 15, "to": 60, "label": "Industry sweet spot — баланс security / operational, для CI-job и Tier-2", "tone": "info" },
{ "from": 60, "to": 240, "label": "Расслабленный — для долгих training jobs или dev environments", "tone": "warn" },
{ "from": 240, "to": 1440, "label": "Слишком долго для prod — фактически long-lived token, не подходит для Tier-1/2", "tone": "err" }
]
}
3.8. Аудит доступа: централизация в SIEM
Приоритет: ВЫСОКИЙ
Все логи доступа агрегируются в централизованной SIEM-системе (ELK/OpenSearch, Splunk или аналог) для корреляции событий и обнаружения аномалий.
Включение аудита Vault:
# Файловый аудит-лог (основной)
vault audit enable file file_path=/var/log/vault/audit.log
# Syslog-аудит (резервный, для интеграции с SIEM)
vault audit enable syslog tag="vault" facility="AUTH"
Формат записи аудита Vault (пример):
{
"time": "2025-01-15T10:23:45Z",
"type": "request",
"auth": {
"token_type": "service",
"policies": ["ml-engineer"],
"metadata": {"role": "ml-engineer", "username": "ivanov"}
},
"request": {
"operation": "read",
"path": "secret/data/ml/training/config",
"remote_address": "10.0.1.42"
}
}
Ключевые алерты для SIEM:
| Событие | Severity | Действие |
|---|---|---|
Доступ к restricted данным |
HIGH | Немедленное уведомление Security team |
| Неудачная аутентификация (>5 за 10 мин) | HIGH | Блокировка + расследование |
| Доступ к секретам вне рабочего времени | MEDIUM | Уведомление + логирование |
| Чтение production-секретов из non-protected branch | CRITICAL | Блокировка + немедленное расследование |
| Массовое чтение секретов (>20 путей за 1 мин) | HIGH | Ревокация токена + расследование |
| Отказ Vault audit backend | CRITICAL | Vault прекращает обслуживание запросов (by design) |
| Изменение Vault-политики | MEDIUM | Уведомление Security team |
| Новый сервисный аккаунт в Keycloak | MEDIUM | Верификация Security team |
Экспорт событий GitLab в SIEM:
# GitLab CI job для экспорта аудит-событий
export_audit_events:
stage: audit
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
script:
- |
curl --header "PRIVATE-TOKEN: $GITLAB_AUDIT_TOKEN" \
"https://gitlab.company.com/api/v4/audit_events?\
entity_type=Group&entity_id=$CI_GROUP_ID&\
created_after=$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)" \
| jq '.[] | select(.details.action |
test("deploy|secret|variable|member|permission"))' \
>> /var/log/gitlab/ml_audit.json
- filebeat -e -c /etc/filebeat/gitlab-audit.yml
Настройка экспорта событий Keycloak:
# keycloak.conf
spi-events-listener-jboss-logging-success-level=info
spi-events-listener-jboss-logging-error-level=warn
# Типы событий для мониторинга ML SecOps:
# LOGIN, LOGIN_ERROR -- интерактивная аутентификация
# CLIENT_LOGIN -- межсервисная аутентификация
# TOKEN_EXCHANGE -- обмен токенов (delegation)
# PERMISSION_TOKEN -- запрос permission ticket
# REGISTER, UPDATE_PROFILE -- изменение учётных данных
# GRANT_CONSENT, REVOKE_GRANT -- управление согласиями
Песочница: detection-rule для аномального доступа в Vault
Vault audit log пишет каждое чтение секрета как JSON-строку. Detection-правило в SIEM ищет паттерны: чтение prod-секрета не-CI-аккаунтом, доступ из необычных источников, всплески запросов. Правьте regex и смотрите, какие тестовые строки правильно классифицируются.
{
"id": "ch08-vault-anomaly",
"title": "Detection-rule: чтение production-секрета интерактивным пользователем",
"description": "Цель — поймать события, когда не-CI пользователь читает prod-секреты (потенциально компрометация). Не блокировать легитимные CI-jobs или admin-действия из maintenance-окна.",
"defaultPattern": "(?=.*\"display_name\":\"(?!ci-)[^\"]+\").*\"path\":\"secret/data/(?:ml/production|datasets/restricted)/",
"flags": "g",
"tests": [
{ "id": "alert1", "shouldMatch": true, "label": "Подозрительно: интерактивный пользователь читает prod-секрет", "text": "{\"time\":\"2026-05-03T10:23:14Z\",\"type\":\"response\",\"auth\":{\"display_name\":\"alice@acme.com\"},\"request\":{\"path\":\"secret/data/ml/production/openai-api-key\",\"operation\":\"read\"}}" },
{ "id": "alert2", "shouldMatch": true, "label": "Подозрительно: чтение restricted dataset metadata", "text": "{\"time\":\"2026-05-03T11:05:22Z\",\"type\":\"response\",\"auth\":{\"display_name\":\"bob@acme.com\"},\"request\":{\"path\":\"secret/data/datasets/restricted/customer-pii-2026\",\"operation\":\"read\"}}" },
{ "id": "ok1", "shouldMatch": false, "label": "Легитимно: CI job читает prod (имя ci-*)", "text": "{\"time\":\"2026-05-03T10:00:00Z\",\"type\":\"response\",\"auth\":{\"display_name\":\"ci-deploy-12345\"},\"request\":{\"path\":\"secret/data/ml/production/db-creds\",\"operation\":\"read\"}}" },
{ "id": "ok2", "shouldMatch": false, "label": "Легитимно: alice читает свой staging-секрет", "text": "{\"time\":\"2026-05-03T09:15:00Z\",\"type\":\"response\",\"auth\":{\"display_name\":\"alice@acme.com\"},\"request\":{\"path\":\"secret/data/ml/staging/db-creds\",\"operation\":\"read\"}}" },
{ "id": "ok3", "shouldMatch": false, "label": "Легитимно: общедоступные данные", "text": "{\"time\":\"2026-05-03T08:00:00Z\",\"type\":\"response\",\"auth\":{\"display_name\":\"alice@acme.com\"},\"request\":{\"path\":\"secret/data/datasets/public/imagenet-mirror\",\"operation\":\"read\"}}" },
{ "id": "alert3", "shouldMatch": true, "label": "Подозрительно: компрометированный SA с не-ci именем", "text": "{\"time\":\"2026-05-03T03:45:00Z\",\"type\":\"response\",\"auth\":{\"display_name\":\"sa-old-test\"},\"request\":{\"path\":\"secret/data/ml/production/aws-creds\",\"operation\":\"read\"}}" }
]
}
Это упрощённый regex для иллюстрации. В production используется парсинг JSON в SIEM (Splunk SPL / Elastic ES|QL) с агрегацией по time window и rate-anomaly detection — single regex недостаточен.
3.9. Политики доступа как код (Policy-as-Code с OPA)
Приоритет: ВЫСОКИЙ
Все политики доступа хранятся в Git-репозитории и применяются автоматически через CI/CD. Ручное изменение политик на серверах запрещено.
Пример OPA-политики для ML-пайплайнов:
package ml_access_policy
import future.keywords.in
# Запрет: деплой в production без security approval
deny[msg] {
input.resource.type == "model_deployment"
input.resource.environment == "production"
not input.metadata.security_approved == true
msg := "Production deployment requires security team approval"
}
# Запрет: доступ к restricted данным без обоснования
deny[msg] {
input.resource.type == "dataset"
input.resource.classification == "restricted"
not input.request.justification
msg := "Access to restricted data requires written justification"
}
# Предупреждение: TTL секрета превышает 24h
warn[msg] {
input.resource.type == "vault_secret"
input.resource.ttl > 86400
msg := sprintf("Secret TTL %v seconds exceeds 24h limit", [input.resource.ttl])
}
# Запрет: сервисный аккаунт с правами на запись в production
deny[msg] {
input.resource.type == "service_account"
input.resource.namespace == "ml-production"
some verb in input.resource.verbs
verb == "create"
msg := "Service accounts in production must not have create permissions"
}
# Запрет: модель без подписи артефакта
deny[msg] {
input.resource.type == "model_promotion"
input.resource.target_stage == "Production"
not input.metadata.artifact_hash
msg := "Model artifact must be signed before promotion to production"
}
Интеграция OPA в GitLab CI:
opa_policy_check:
stage: validate
image: openpolicyagent/opa:latest
script:
- |
VIOLATIONS=$(opa eval \
-d policies/ \
-i deployment_request.json \
"data.ml_access_policy.deny" \
--format raw)
if [ "$VIOLATIONS" != "[]" ]; then
echo "Policy violations detected:"
echo "$VIOLATIONS" | jq '.'
exit 1
fi
- |
WARNINGS=$(opa eval \
-d policies/ \
-i deployment_request.json \
"data.ml_access_policy.warn" \
--format raw)
if [ "$WARNINGS" != "[]" ]; then
echo "Policy warnings:"
echo "$WARNINGS" | jq '.'
fi
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_MERGE_REQUEST_ID'
3.10. Offboarding-процедура
Приоритет: КРИТИЧЕСКИЙ
При увольнении сотрудника или отзыве доступа все credentials отзываются немедленно и одновременно во всех системах.
Offboarding ML-инженера: 10 обязательных шагов
{
"id": "ch08-offboarding",
"title": "Offboarding ML-engineer: чеклист последнего дня",
"description": "Каждая запись — конкретный шаг с командой / API-вызовом, ответственным и SLA. Используйте как runbook при срочном offboarding (увольнение with cause, отстранение).",
"shuffle": false,
"cards": [
{
"tag": "T-0 минут · Identity",
"front": "Disable IdP account",
"subFront": "Keycloak / AD FS / Okta",
"back": "Команда (Keycloak): `kcadm.sh update users/$USER_ID -s enabled=false -r mlsec`.\n\nЭффект: новые SSO-логины блокируются, но активные сессии живут до session-timeout.\n\nОтветственный: HR-system автоматический trigger; SLA — день увольнения, в час уведомления HR."
},
{
"tag": "T-0 · Sessions",
"front": "Revoke active sessions",
"subFront": "Принудительное logout",
"back": "Команда: `kcadm.sh delete users/$USER_ID/sessions -r mlsec` + revoke во всех downstream-сервисах через TokenExchange revocation.\n\nКритично: иначе active сессия в Jupyter / GitLab / Vault может прожить часами.\n\nSLA: <5 мин после disable account."
},
{
"tag": "T-0 · Vault",
"front": "Revoke Vault tokens & leases",
"subFront": "Все creds выданные пользователю",
"back": "Команда: `vault token revoke -mode=path \"auth/oidc/login/$USERNAME\"` для интерактивных + `vault lease revoke -prefix \"database/creds/ml-staging-role/$USERNAME\"` для динамических.\n\nЭффект: каскадный revoke всех дочерних tokens и leases.\n\nSLA: <5 мин."
},
{
"tag": "T-0 · Cloud",
"front": "Disable cloud IAM principals",
"subFront": "AWS IAM / GCP IAM / Yandex IAM",
"back": "Команды:\n• AWS: `aws iam delete-login-profile --user-name $USER` + `aws iam list-access-keys ... | aws iam delete-access-key`\n• GCP: `gcloud iam service-accounts disable $USER@$PROJECT.iam`\n\nКритично для ML: training-jobs могут использовать длинные access keys — все ротировать."
},
{
"tag": "T-15 мин · Code",
"front": "Remove from Git repos",
"subFront": "GitLab / GitHub project-membership",
"back": "API: `DELETE /projects/:id/members/:user_id` (GitLab) / `DELETE /repos/:org/:repo/collaborators/:user` (GitHub).\n\nСохранить commits — авторство сохраняется по email/SSH-ключу, не по членству.\n\nДополнительно: revoke Personal Access Tokens, SSH keys через API."
},
{
"tag": "T-15 мин · K8s",
"front": "Revoke Kubernetes RBAC",
"subFront": "RoleBinding & ServiceAccount",
"back": "Если пользователь имел RBAC через RoleBinding (subjects: User: alice@acme.com) — удалить subject из RoleBinding.\n\nЕсли использовал ServiceAccount-токен — `kubectl delete sa $SA -n $NAMESPACE` + проверить, что job’ы под этим SA уже отозваны.\n\nДля кросс-кластерной аутентификации (Keycloak + OIDC) — достаточно IdP disable."
},
{
"tag": "T-30 мин · Data",
"front": "Revoke data-plane access",
"subFront": "MLflow / Snowflake / S3 buckets",
"back": "Каждый data-tool требует своего API:\n• MLflow: `mlflow.tracking.MlflowClient().delete_user(...)` (если custom auth)\n• Snowflake: `DROP USER alice;`\n• S3: bucket-policy update + IAM principal revoke\n\nКоординация: chemistry между tools часто плохая; формализованный list-of-tools обязателен."
},
{
"tag": "T-1 час · Devices",
"front": "Wipe / quarantine corporate devices",
"subFront": "MDM-команда",
"back": "Через MDM (Jamf / Intune / Workspace ONE) — selective wipe или full wipe в зависимости от corporate policy.\n\nДля BYOD: revoke device certificate (mTLS), revoke MDM-enrollment.\n\nРиск: на устройстве могут оставаться cached secrets (Vault tokens в keychain, Kubernetes config). Wipe обязателен."
},
{
"tag": "T-1 час · Audit",
"front": "Trigger 30-day enhanced audit",
"subFront": "SIEM rule: track ex-employee identity",
"back": "В SIEM создать temporary correlation rule: alert на ЛЮБОЙ event с identity = ex-employee email/UID в течение 30 дней.\n\nЦель: словить cached сессии / забытые tokens / попытки атаки.\n\nSplunk / ELK / QRadar — все поддерживают временные правила.\n\nДополнительно: forensic-снимок home-directory на shared storage (legal hold если терминирован with cause)."
},
{
"tag": "T-24 часа · Verification",
"front": "Run offboarding verification",
"subFront": "Automated check across all systems",
"back": "Скрипт обходит все интегрированные tools и проверяет, что ex-employee действительно нигде не имеет доступа.\n\nКатегории checks:\n• IdP — account disabled / sessions revoked\n• Vault — нет активных tokens / leases\n• Cloud — нет API keys / console access\n• Git — не member ни в одном репо\n• K8s — нет subjects в RoleBindings\n• Data tools — нет user records\n\nFailure → ручное расследование + запись в audit log."
}
]
}
Автоматизированный скрипт offboarding:
#!/bin/bash
# offboard_user.sh
# Использование: ./offboard_user.sh <username> <keycloak_user_id>
set -euo pipefail
USER=$1
KC_USER_ID=$2
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
ADMIN_USER=$(whoami)
echo "[$TIMESTAMP] Starting offboarding for user: $USER"
# 1. Отзыв всех Vault-токенов пользователя
echo "Revoking Vault tokens..."
for accessor in $(vault list -format=json auth/token/accessors | jq -r '.[]'); do
TOKEN_META=$(vault token lookup -accessor "$accessor" -format=json 2>/dev/null || true)
TOKEN_USER=$(echo "$TOKEN_META" | jq -r '.data.meta.username // empty')
if [ "$TOKEN_USER" == "$USER" ]; then
vault token revoke -accessor "$accessor"
echo " Revoked token accessor: $accessor"
fi
done
# 2. Деактивация в Keycloak
echo "Disabling Keycloak account..."
curl -s -X PUT \
"https://keycloak.company.com/admin/realms/ml-platform/users/$KC_USER_ID" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
# 3. Закрытие всех сессий Keycloak
echo "Closing Keycloak sessions..."
curl -s -X POST \
"https://keycloak.company.com/admin/realms/ml-platform/users/$KC_USER_ID/logout" \
-H "Authorization: Bearer $ADMIN_TOKEN"
# 4. Ревокация GitLab Personal Access Tokens
echo "Revoking GitLab tokens..."
TOKENS=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_ADMIN_TOKEN" \
"https://gitlab.company.com/api/v4/personal_access_tokens?user_id=$GITLAB_USER_ID")
for TOKEN_ID in $(echo "$TOKENS" | jq -r '.[].id'); do
curl -s --request DELETE \
"https://gitlab.company.com/api/v4/personal_access_tokens/$TOKEN_ID" \
--header "PRIVATE-TOKEN: $GITLAB_ADMIN_TOKEN"
echo " Revoked GitLab PAT: $TOKEN_ID"
done
# 5. Удаление SSH-ключей из GitLab
echo "Removing SSH keys..."
SSH_KEYS=$(curl -s --header "PRIVATE-TOKEN: $GITLAB_ADMIN_TOKEN" \
"https://gitlab.company.com/api/v4/users/$GITLAB_USER_ID/keys")
for KEY_ID in $(echo "$SSH_KEYS" | jq -r '.[].id'); do
curl -s --request DELETE \
"https://gitlab.company.com/api/v4/users/$GITLAB_USER_ID/keys/$KEY_ID" \
--header "PRIVATE-TOKEN: $GITLAB_ADMIN_TOKEN"
done
# 6. Удаление kubeconfig / RBAC bindings
echo "Removing Kubernetes access..."
kubectl delete rolebinding -l "user=$USER" --all-namespaces 2>/dev/null || true
kubectl delete clusterrolebinding -l "user=$USER" 2>/dev/null || true
# 7. Документирование в аудит-лог
echo "Recording audit entry..."
vault kv put "secret/audit/offboarding/$USER" \
completed_at="$TIMESTAMP" \
processed_by="$ADMIN_USER" \
systems="vault,keycloak,gitlab,kubernetes"
echo "[$TIMESTAMP] Offboarding completed for $USER"
echo "$(date -u): Offboarding completed for $USER by $ADMIN_USER" \
>> /var/log/ml-secops/offboarding.log
Контрольный список offboarding:
- Все Vault-токены отозваны.
- Keycloak-аккаунт деактивирован, сессии закрыты.
- GitLab PAT отозваны, SSH-ключи удалены.
- Kubernetes RBAC bindings удалены.
- Доступ к VPN/SSH отозван.
- Событие задокументировано в аудит-лог.
- Верификация: попытка аутентификации с отозванными credentials неуспешна.
3.11. Separation of Duties в CI/CD
Приоритет: ВЫСОКИЙ
Ни один человек или сервисный аккаунт не должен иметь возможность единолично провести изменение от коммита до production-деплоя.
Реализация через GitLab CODEOWNERS:
# .gitlab/CODEOWNERS
# Данные --- только Data Engineers
/data/** @data-engineers-team
# Обучение --- ML Engineers + обязательный security review
/training/** @ml-engineers-team @security-team
# Деплой --- только MLOps
/deployment/** @mlops-team
# Политики безопасности --- только SecOps
/security-policies/** @secops-team
/vault-policies/** @secops-team
# Конфигурации инфраструктуры --- DevOps + SecOps
/infrastructure/** @devops-team @secops-team
Multi-stage pipeline с обязательными gates:
stages:
- validate # Автоматическая валидация
- train # Обучение модели (ML Engineer)
- security_gate # Проверка безопасности (автоматическая)
- staging # Деплой в staging (MLOps)
- approval # Ручное одобрение (Security + PM)
- deploy # Деплой в production (MLOps)
security_gate:
stage: security_gate
script:
- python run_security_tests.py --model-version $MODEL_VERSION
- python check_adversarial_robustness.py
- python check_data_leakage.py
- python verify_model_signature.py
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
production_approval:
stage: approval
script:
- echo "Awaiting security team and PM approval"
environment:
name: production
action: prepare
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
allow_failure: false
deploy_production:
stage: deploy
needs: ["security_gate", "production_approval"]
script:
# Верификация подписи артефакта модели
- STORED_HASH=$(vault kv get -field=hash
secret/ml/models/v${MODEL_VERSION})
- CURRENT_HASH=$(sha256sum model.pkl | awk '{print $1}')
- |
if [ "$STORED_HASH" != "$CURRENT_HASH" ]; then
echo "Model artifact integrity check FAILED!"
exit 1
fi
- ./deploy_model.sh $MODEL_VERSION production
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
3.12. Рекомендации по Keycloak hardening
Приоритет: ВЫСОКИЙ
Keycloak как центральный IdP требует дополнительного усиления безопасности.
Конфигурация realm для ML-платформы:
| Параметр | Значение | Обоснование |
|---|---|---|
| Access Token Lifespan | 5 минут | Минимизация окна компрометации |
| Refresh Token Lifespan | 30 минут | Баланс UX и безопасности |
| SSO Session Idle | 30 минут | Автоматический logout при бездействии |
| SSO Session Max | 8 часов | Максимальная длительность рабочей сессии |
| Brute Force Protection | Включена | 5 попыток, блокировка 15 минут |
| Required Actions | CONFIGURE_TOTP | Обязательная MFA для всех пользователей |
| Password Policy | length(12), upperCase(1), digit(1), specialChars(1), notUsername | Минимальные требования |
| Client Authentication | Confidential | Все clients - confidential, без public clients |
Роли Keycloak для ML-платформы:
flowchart LR
REALM["Realm: ml-platform"]
subgraph CLIENTS[Clients]
direction TB
C1["ml-training-service<br/>training-data-reader<br/>experiment-writer"]
C2["ml-serving-service<br/>model-reader<br/>prediction-writer"]
C3["ml-monitoring<br/>metrics-reader<br/>audit-reader"]
C4["vault-client<br/>vault-user → Vault OIDC"]
end
subgraph REALMROLES[Realm Roles]
direction TB
R1[data-scientist]
R2[ml-engineer]
R3[mlops-engineer]
R4[secops]
end
REALM --> CLIENTS
REALM --> REALMROLES
R1 -.->|"training-data-reader, experiment-writer"| C1
R2 -.->|"training-data-reader, model-reader"| C1
R2 -.->|model-reader| C2
R3 -.->|"model-reader, prediction-writer"| C2
R4 -.->|"audit-reader, metrics-reader"| C3
Дополнительные меры hardening:
- Отключить все неиспользуемые Identity Providers.
- Включить HTTPS-only для всех endpoints.
- Отключить realm
masterдля пользователей (использовать только для администрирования Keycloak). - Настроить Content Security Policy headers.
- Ограничить Redirect URIs для каждого client строго по списку.
- Включить Audit Logging для всех event types.
- Регулярно обновлять Keycloak (отслеживать CVE).
Дальше — практика и артефакты
Полная версия главы «Рекомендации ИБ: Управление доступом и секретами в MLSec» с готовыми артефактами, шаблонами, чек-листами и подробными процедурами доступна по подписке.
- Полный доступ ко всем главам и обновлениям 30 дней
- Готовые playbook'и, шаблоны и runbook'и под копирование
- Поддержка автора — paywall не для заработка, а для развития справочника
Можно отменить в любой момент. Возврат средств в первые 7 дней без вопросов.
Упоминается в (10)
- Рекомендации ИБ: Обзор и Архитектура ML SecOps
- Глава 24. Безопасность векторных баз данных
- Threat Modeling для ML-систем: Рекомендации ИБ
- Безопасность код-ассистентов и AI-агентов разработки
- Защита и Red Teaming LLM-агентов
- Рекомендации ИБ: Red Teaming, тестирование моделей и CI/CD интеграция
- MLSec Recommendations. Индекс документов
- Российская специфика MLSec. Deep Dive
- Безопасность обучающей инфраструктуры
- Внедрение MLSec и AI Security в российских компаниях. Практический плейбук