Это краткий production-ориентированный чек-лист по защите multi-tenant SaaS-приложений. Он основан на конкретных рекомендациях, ориентирован на код и отдаёт приоритет мерам защиты, которые измеримо снижают риск.
TL;DR: чек-лист
- Идентификация: SSO/OIDC, MFA, passwordless, где возможно
- Сессии: Secure+HttpOnly+SameSite, короткий срок действия, refresh tokens с ротацией
- Авторизация: явная привязка к tenant + RBAC/ABAC; default-deny
- Изоляция tenant: org_id везде, DB RLS или изоляция на уровне сервиса
- Секреты: управляемое хранилище + ротация; никаких секретов в git
- HTTPS + HSTS; строгие security headers; CSP (nonces или hashes)
- CSRF для маршрутов, изменяющих состояние; строгий CORS для API
- Rate limits для каждого IP и аккаунта; контроль ботов/злоупотреблений
- Защита данных: шифрование at rest + на уровне полей, где необходимо; корректная настройка KMS
- Загрузка файлов: MIME sniffing, ограничения размера, AV-сканирование, signed URLs, приватные buckets
- Webhooks: HMAC-подписи, идемпотентность, повторы с jitter, минимальные привилегии
- Наблюдаемость: структурированные audit logs, alerts об аномалиях, хранилище с защитой от незаметного изменения
- Цепочка поставок: зафиксированные зависимости, SBOM, SCA/SAST, сканирование секретов в CI
- Cloud/IAM: минимальные привилегии, границы, break-glass-доступ, строгая изоляция между окружениями
- DR/BCP: резервные копии, проверенное восстановление, определённые RPO/RTO, ключи на базе HSM/KMS
1) Идентификация и доступ (SSO/OIDC + MFA)
Отдавайте предпочтение федеративной идентификации (OIDC/SAML) с обязательным MFA. Если вы предлагаете локальную аутентификацию, обеспечьте использование надёжных паролей и progressive profiling.
Пример OIDC (псевдоконфигурация):
oidc:
issuer: "https://accounts.example-idp.com"
client_id: "YOUR_CLIENT_ID"
client_secret: "${OIDC_CLIENT_SECRET}" # from secret store
redirect_uris:
- "https://app.example.com/auth/callback"
scopes: ["openid", "profile", "email", "offline_access"]
prompt: "login" # force fresh auth for sensitive actions
max_age: 300 # re-auth window for step-up operations
Лучшие практики:
- Обеспечьте MFA в IdP или политике приложения для административных/привилегированных ролей
- Используйте короткоживущие access tokens (5–15 мин), ротируйте refresh tokens, привязывайте tokens к клиенту
- Храните списки отзыва; завершайте сессии при изменении пароля/2FA
2) Авторизация и привязка к tenant
Именно в авторизации происходит большинство утечек данных SaaS. Защищайте весь доступ к данным с помощью явной привязки к tenant и политик default-deny.
- Последовательно используйте
tenant_id/org_idв каждой таблице и на каждой границе API - Применяйте RBAC/ABAC с понятными названиями разрешений; избегайте разбросанных по коду произвольных проверок “is_admin”
- Добавляйте guard clauses в начале обработки запросов для проверки членства в tenant + разрешения
Пример Row Level Security (RLS) в Postgres:
-- Enable RLS
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
-- Current tenant is set per session/connection:
-- SELECT set_config('app.current_tenant', 'acme', true);
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.current_tenant')::uuid);
-- Optional: restrict writes similarly
CREATE POLICY tenant_isolation_write ON invoices
FOR INSERT WITH CHECK (tenant_id = current_setting('app.current_tenant')::uuid);
Альтернатива — изоляция на уровне сервиса:
- Выделенная БД для каждого tenant для клиентов с высокими рисками
- Или как минимум выделенная schema; сопоставьте операционную сложность и радиус поражения
3) Секреты и конфигурация
- Используйте управляемое хранилище секретов (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, 1Password Connect)
- Ротируйте ключи/tokens по расписанию и при уходе сотрудников
- Никогда не записывайте секреты в logs; используйте структурированное журналирование по allow-list
.env (только для dev):
OIDC_CLIENT_SECRET="change-me"
STRIPE_WEBHOOK_SECRET="whsec_..."
DATABASE_URL="postgresql://user:pass@localhost:5432/app"
4) HTTPS, HSTS и security headers
Устанавливайте строгие headers на уровне всей платформы. Для браузерных приложений обеспечьте CSP с nonces или hashes.
Пример (универсальный псевдо-middleware):
// Express-style example
app.use((req, res, next) => {
res.setHeader("X-Frame-Options", "SAMEORIGIN");
res.setHeader("Referrer-Policy", "no-referrer");
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader(
"Strict-Transport-Security",
"max-age=31536000; includeSubDomains; preload"
);
// CSP with nonce (attach to res.locals.nonce earlier)
res.setHeader(
"Content-Security-Policy",
`default-src 'self'; script-src 'self' 'nonce-${res.locals.nonce}'; style-src 'self' 'unsafe-inline'; img-src 'self' data:`
);
next();
});
5) Сессии и cookies
- Используйте cookies Secure+HttpOnly; SameSite=Lax или Strict для приложений в рамках одного сайта
- Делайте сессии короткими, ротируйте идентификаторы сессий при изменении привилегий и входе
- Рассмотрите server-side sessions (Redis) для отзыва и централизованного управления
Настройки cookies (независимо от framework):
{
"SESSION_COOKIE_SECURE": true,
"SESSION_COOKIE_HTTPONLY": true,
"SESSION_COOKIE_SAMESITE": "Lax",
"PERMANENT_SESSION_LIFETIME_MIN": 0
}
6) CSRF и CORS
- Защищайте от CSRF все небезопасные методы (POST/PUT/PATCH/DELETE) для потоков с browser-cookies
- CORS: ограничивайте известными origins; не используйте wildcard вместе с credentials
Строгий CORS (пример):
const allowed = new Set(["https://app.example.com"]);
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowed.has(origin)) {
res.setHeader("Access-Control-Allow-Origin", origin);
res.setHeader("Vary", "Origin");
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setHeader(
"Access-Control-Allow-Headers",
"authorization, content-type, x-request-id"
);
res.setHeader(
"Access-Control-Allow-Methods",
"GET, POST, PUT, PATCH, DELETE, OPTIONS"
);
}
if (req.method === "OPTIONS") return res.status(204).end();
next();
});
7) Rate limiting и предотвращение злоупотреблений
- Глобальный лимит (для каждого IP) плюс лимиты для чувствительных маршрутов (login, сброс пароля, signup)
- Квоты для каждого аккаунта/tenant, чтобы предотвращать злоупотребления со стороны noisy neighbor
- Добавляйте proof-of-work или CAPTCHA только при необходимости; сначала измерьте
Пример limiter:
// Pseudo-code
limiter.global("200/min");
limiter.route("/auth/login", "5/min");
limiter.route("/password/reset", "5/min");
limiter.keyFunc((req) => req.ip + ":" + req.user?.tenant_id);
8) Защита данных и шифрование
- Шифруйте данные at rest (стандартные настройки cloud) и при передаче (TLS 1.2+)
- Используйте шифрование на уровне полей для высокочувствительных PII/PHI/PCI (храните только ciphertext; ключи — в KMS/HSM)
- Используйте отдельные ключи KMS для окружений/tenant со строгими allow-list в IAM
Шифрование на уровне поля (псевдокод):
from cryptography.fernet import Fernet
def encrypt_field(value: str, key: bytes) -> str:
return Fernet(key).encrypt(value.encode()).decode()
def decrypt_field(token: str, key: bytes) -> str:
return Fernet(key).decrypt(token.encode()).decode()
9) Границы Cloud/IAM
- Разделяйте проекты/аккаунты для каждого окружения (prod по сравнению со staging и dev)
- Используйте IAM с минимальными привилегиями для приложений и CI; запрещайте по умолчанию; применяйте короткоживущие credentials
- Используйте VPC/service perimeters, где это поддерживается; контролируйте egress к одобренным endpoints
Пример политики IAM (подмножество bucket только для чтения):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::customer-uploads",
"arn:aws:s3:::customer-uploads/*"
],
"Condition": {
"StringEquals": { "aws:PrincipalTag/role": "uploader" }
}
}
]
}
10) Webhooks и исходящие интеграции
- Проверяйте подписи (HMAC или Ed25519) до начала обработки
- Обеспечьте ключи идемпотентности; выполняйте повторы с экспоненциальной задержкой + jitter
- Используйте allow-listed egress и DNS pinning, где это возможно для критически важных потоков
Проверка webhook (пример HMAC):
import hmac, hashlib, time
def verify_webhook(raw_body: bytes, signature: str, secret: bytes, tolerance_s: int = 300) -> bool:
# signature format: t=timestamp,v1=hex
parts = dict(p.split("=", 1) for p in signature.split(","))
ts = int(parts["t"])
if abs(time.time() - ts) > tolerance_s:
return False
expected = hmac.new(secret, f"{ts}.{raw_body.decode()}".encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
Идемпотентность (псевдокод):
CREATE TABLE webhook_events (
id TEXT PRIMARY KEY,
received_at TIMESTAMPTZ DEFAULT now(),
payload JSONB
);
-- INSERT ... ON CONFLICT DO NOTHING to avoid duplicates
11) Загрузка файлов
- Устанавливайте ограничения размера, allow-lists расширений и определяйте тип содержимого (content-type sniffing), а не полагайтесь только на headers
- Храните файлы в приватных buckets; предоставляйте доступ через короткоживущие signed URLs
- При необходимости выполняйте AV-сканирование непроверенных загрузок; удаляйте metadata (EXIF)
Шаблон Signed URL:
// Pseudo: issue a short-lived upload URL; never expose bucket credentials
const url = storage.createSignedUrl({
bucket: "customer-uploads",
key: `tenants/${tenantId}/${randomUuid()}.bin`,
expiresInSeconds: 300,
contentType: "application/octet-stream",
});
12) Наблюдаемость, аудит и alerts
- Создавайте структурированные JSON logs; включайте
tenant_id,user_id,ip,action,object,result - Используйте неизменяемый/audit log sink (WORM/S3 Object Lock) для критически важных событий безопасности
- Настройте alerts в реальном времени об аномалиях аутентификации, отказах в разрешениях, массовом экспорте/удалении
Audit helper (псевдокод):
import json, logging, sys
logger = logging.getLogger("audit")
logger.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter('%(message)s'))
logger.addHandler(handler)
def audit(event: str, **kwargs):
logger.info(json.dumps({"event": event, **kwargs}))
13) Безопасность цепочки поставок
- Фиксируйте зависимости; поддерживайте SBOM; запускайте SCA (например,
pip-audit,npm audit,osv-scanner) - Используйте SAST/linters для распространённых классов ошибок; подписывайте releases/artifacts (Sigstore)
- Выполняйте сканирование секретов в CI; прерывайте сборки при утечке credentials
Примеры шагов CI (универсальные):
osv-scanner ./ # or npm audit / pip-audit etc.
trivy fs --exit-code 1 .
gitleaks detect --no-banner --redact
14) Операционные меры контроля
- Шифруйте резервные копии отдельными ключами; регулярно проверяйте восстановление (автоматически)
- Определите RPO/RTO; документируйте и тестируйте runbooks для инцидентов
- Используйте break-glass accounts с hardware keys; отслеживайте и ротируйте их
15) Безопасные настройки по умолчанию и UX
- Используйте маршруты и функции default-deny, пока они явно не включены
- Feature flags: оценивайте на сервере с привязкой к tenant; не полагайтесь только на enforcement на стороне клиента
- Обеспечьте понятный и ориентированный на пользователя security UX: тайм-ауты сессий, управление устройствами, простое подключение/восстановление 2FA
Финальный чек-лист перед запуском
- SSO/OIDC работает; MFA обязательно для администраторов
- RBAC/ABAC с явной привязкой к tenant; default-deny проверен
- Изоляция tenant через RLS или разделение сервисов/БД
- HTTPS + HSTS включены; CSP с nonces/hashes применяется
- Cookies: Secure+HttpOnly+SameSite; ротация сессии при изменении привилегий
- CSRF для небезопасных методов; строгий CORS настроен
- Rate limits для auth и чувствительных endpoints; квоты для каждого tenant
- Шифрование at rest + на уровне полей, где необходимо; ключи в KMS/HSM
- Загрузки: MIME sniffing, ограничения размера, AV, signed URLs, приватные buckets
- Webhooks: проверка подписи, идемпотентность, повторы, минимальные привилегии
- Структурированные audit logs отправляются; alerts об аномалиях настроены; sink защищён от незаметного изменения
- Зависимости зафиксированы; SBOM создан; SCA/SAST + сканирование секретов не выявили проблем
- Резервные копии проверены; RPO/RTO документированы; break-glass controls внедрены