Цели модуля
После модуля студент сможет:
- Реализовывать HMAC с multi-key verification и replay protection
- Проектировать стратегии ротации секретов без downtime
- Применять принцип least privilege к сервисам и пользователям
- Настраивать mTLS для internal communication
- Понимать Zero Trust архитектуру
- Обеспечивать безопасную внешнюю коммуникацию
- Проектировать audit-friendly логирование
- Понимать supply chain security (SBOM, sigstore)
- Проводить базовый threat modeling
6.1 Security mindset для архитектора
Отличие Middle от Senior в security
Middle разработчик:
- Знает OWASP Top 10
- Применяет базовые защиты (параметризованные запросы, валидация)
- Использует HMAC для webhook verification
Senior архитектор:
- Проектирует security на уровне архитектуры
- Думает о threat model до написания кода
- Планирует ротацию секретов, key management
- Обеспечивает defense in depth
- Понимает compliance требования
Defense in depth
Многоуровневая защита: если один слой падает, другие защищают.
Уровень 1: Network (firewall, VPC, security groups)
Уровень 2: Transport (TLS, mTLS)
Уровень 3: Application (auth, validation, rate limiting)
Уровень 4: Data (encryption at rest, access control)
Уровень 5: Secrets (rotation, least privilege)
Уровень 6: Monitoring (audit logs, anomaly detection)
Каждый уровень независим. Компрометация одного не означает компрометацию всех.
Security by design
Security должен быть заложен в архитектуру с самого начала:
- Threat modeling до разработки
- Secure defaults (всё запрещено по умолчанию)
- Least privilege для всех компонентов
- Encryption everywhere (in transit, at rest)
- Audit logging для критичных операций
- Regular rotation для секретов
- Security testing в CI (SAST, DAST, SCA)
Compliance требования
Для разных доменов разные требования:
| Домен | Стандарты | Требования |
|---|---|---|
| Финансы | PCI DSS | Шифрование карточных данных, аудит |
| Здоровье | HIPAA | Защита медицинских данных, аудит |
| Персональные данные | GDPR, 152-ФЗ | Право на удаление, согласие |
| Госсектор | ГОСТ, ФСТЭК | Сертификация средств защиты |
Архитектор должен понимать требования домена и закладывать их в архитектуру.
6.2 HMAC углублённо
Recap: базовый HMAC
HMAC (Hash-based Message Authentication Code) — подпись сообщения с использованием секретного ключа и хеш-функции.
mac = HMAC-SHA256(secret, message)
Проверка: expected = HMAC-SHA256(secret, message) if hmac.Equal(received, expected) { valid }
Multi-key verification
При ротации ключей нужен период когда принимаются оба ключа (текущий и предыдущий):
gotype MultiKeyVerifier struct { keys [][]byte // [current, previous, ...] } func NewMultiKeyVerifier(keys ...[]byte) *MultiKeyVerifier { return &MultiKeyVerifier{keys: keys} } func (v *MultiKeyVerifier) Verify(message []byte, signature string) error { decoded, err := hex.DecodeString(signature) if err != nil { return fmt.Errorf("decode signature: %w", err) } for _, key := range v.keys { mac := hmac.New(sha256.New, key) mac.Write(message) expected := mac.Sum(nil) if hmac.Equal(decoded, expected) { return nil } } return errors.New("invalid signature") }
Key identification
Для оптимизации можно добавлять key ID в подпись:
go// Формат подписи: keyid.signature // Пример: v2.a3f5b8c9d2e1... func (v *MultiKeyVerifier) VerifyWithKeyID(message []byte, signature string) error { parts := strings.SplitN(signature, ".", 2) if len(parts) != 2 { return errors.New("invalid signature format") } keyID := parts[0] sig := parts[1] key, err := v.getKeyByID(keyID) if err != nil { return fmt.Errorf("unknown key: %w", err) } return v.verifyWithKey(message, sig, key) }
Это позволяет не перебирать все ключи, а сразу использовать нужный.
Replay protection
Злоумышленник может перехватить валидный запрос и повторить. Защита: timestamp + nonce.
Формат подписываемого сообщения:
message = webhook_id + "." + timestamp + "." + body
signature = HMAC-SHA256(secret, message)
Verification:
gofunc (v *Verifier) VerifyWebhook( body []byte, webhookID string, timestamp int64, signature string, ) error { // 1. Проверяем timestamp (не старше 5 минут) now := time.Now().Unix() if now - timestamp > 300 { return errors.New("timestamp too old") } if timestamp - now > 300 { return errors.New("timestamp in future") } // 2. Проверяем nonce (webhook ID не использовался) if v.nonceStore.Exists(webhookID) { return errors.New("webhook already processed") } // 3. Проверяем подпись message := fmt.Sprintf("%s.%d.%s", webhookID, timestamp, body) if err := v.Verify([]byte(message), signature); err != nil { return err } // 4. Запоминаем nonce v.nonceStore.Add(webhookID, time.Unix(timestamp, 0)) return nil }
Nonce store
Для хранения использованных nonce:
gotype NonceStore struct { mu sync.Mutex nonces map[string]time.Time ttl time.Duration } func NewNonceStore(ttl time.Duration) *NonceStore { return &NonceStore{ nonces: make(map[string]time.Time), ttl: ttl, } } func (s *NonceStore) Exists(nonce string) bool { s.mu.Lock() defer s.mu.Unlock() _, ok := s.nonces[nonce] return ok } func (s *NonceStore) Add(nonce string, ts time.Time) { s.mu.Lock() defer s.mu.Unlock() s.nonces[nonce] = ts } // Периодическая очистка старых nonce func (s *NonceStore) Cleanup() { s.mu.Lock() defer s.mu.Unlock() cutoff := time.Now().Add(-s.ttl) for nonce, ts := range s.nonces { if ts.Before(cutoff) { delete(s.nonces, nonce) } } }
Для распределённых систем используйте Redis вместо in-memory:
gofunc (s *RedisNonceStore) Add(ctx context.Context, nonce string, ttl time.Duration) (bool, error) { // SETNX: устанавливает только если ключ не существует return s.redis.SetNX(ctx, "nonce:"+nonce, "1", ttl).Result() }
Timing-safe comparison
ВСЕГДА используйте hmac.Equal для сравнения подписей:
go// ПЛОХО: уязвимо к timing attacks if signature == expected { ... } // ПРАВИЛЬНО: constant-time comparison if hmac.Equal(decoded, expected) { ... }
Timing attack: атакующий измеряет время сравнения и посимвольно угадывает подпись. hmac.Equal всегда выполняется за одинаковое время.
6.3 Secret rotation strategies
Зачем нужна ротация
Секреты могут быть скомпрометированы:
- Утечка через логи или ошибки в коде
- Доступ бывшего сотрудника
- Взлом секрет-менеджера
- Компрометация backup
- Insider threat
Регулярная ротация ограничивает окно экспозиции.
Периодичность ротации
| Секрет | Периодичность | Причина |
|---|---|---|
| HMAC ключи для webhook | 30-90 дней | Внешние интеграции |
| API ключи | 90 дней | Стандарт индустрии |
| Пароли БД | 90 дней | Compliance |
| TLS сертификаты | 90 дней | Let's Encrypt |
| Service account tokens | 24 часа | Short-lived |
| Encryption keys | 1 год | Дорого ротировать |
Двухфазная ротация
Фаза 1: Добавление нового ключа
Время T:
- current_key = key_v1
- Генерируем key_v2
- Сохраняем: current=key_v2, previous=key_v1
- Verifier принимает оба ключа
- Signer подписывает только key_v2
Фаза 2: Удаление старого ключа
Время T + 24h (после миграции всех клиентов):
- current_key = key_v2
- previous_key = null
- Verifier принимает только key_v2
Реализация ротации
gotype SecretRotator struct { provider repository.SecretProvider notifier Notifier logger *slog.Logger } func (r *SecretRotator) RotateHMACKey(ctx context.Context) error { // 1. Получаем текущий ключ и метаданные current, err := r.provider.GetSecret(ctx, "hmac-key") if err != nil { return fmt.Errorf("get current key: %w", err) } metadata, err := r.provider.GetMetadata(ctx, "hmac-key") if err != nil { return fmt.Errorf("get metadata: %w", err) } // Проверяем что пора ротировать if time.Since(metadata.CreatedAt) < RotationPeriod { r.logger.Info("rotation not due yet", "created_at", metadata.CreatedAt, "next_rotation", metadata.CreatedAt.Add(RotationPeriod), ) return nil } // 2. Генерируем новый ключ newKey, err := generateSecureKey(64) if err != nil { return fmt.Errorf("generate key: %w", err) } // 3. Сохраняем с previous key if err := r.provider.Rotate(ctx, "hmac-key", newKey, current.Value); err != nil { return fmt.Errorf("rotate key: %w", err) } // 4. Уведомляем команду r.notifier.Notify(ctx, Notification{ Title: "HMAC key rotated", Message: "New key active. Previous key valid for 24h.", Severity: SeverityInfo, }) r.logger.Info("hmac key rotated", "key_id", newKey.ID, "previous_key_id", current.ID, ) return nil }
Автоматическая ротация через cron job
В шаблоне проекта есть hmac-rotator cron job:
go// internal/handlers/jobs/hmac_rotator/handler.go func (h *Handler) Run(ctx context.Context) error { return h.rotator.RotateHMACKey(ctx) } // Расписание: каждый день в 3:00 func (h *Handler) Schedule() string { return "0 0 3 * * *" }
Ротация TLS сертификатов
Для TLS используем Let's Encrypt с автоматической ротацией:
go// certbot автоматически обновляет сертификаты каждые 60 дней // cron job: certbot renew
Для Kubernetes:
- cert-manager автоматически управляет сертификатами
- Автообновление за 30 дней до истечения
Ротация паролей БД
Для managed БД (Yandex, AWS RDS) используйте встроенную ротацию:
go// Yandex Lockbox: автоматическая ротация пароля БД // AWS Secrets Manager: автоматическая ротация через Lambda
Для self-hosted PostgreSQL:
1. Создаём нового пользователя с новым паролем
2. Обновляем конфиг сервиса
3. Перезапускаем сервис (rolling restart)
4. Удаляем старого пользователя
6.4 Least privilege для сервисов
Принцип least privilege
Каждый компонент получает минимально необходимые права:
- Не больше чем нужно для работы
- Не меньше чем нужно для работы
Least privilege для сервисных аккаунтов
Пример для Yandex Cloud:
hcl// ПЛОХО: сервис с ролью admin resource "yandex_iam_service_account" "app" { name = "app-sa" } resource "yandex_resourcemanager_folder_iam_member" "app_admin" { folder_id = var.folder_id role = "admin" // слишком много прав! member = "serviceAccount:${yandex_iam_service_account.app.id}" } // ПРАВИЛЬНО: минимальные роли resource "yandex_iam_service_account" "app" { name = "app-sa" } // Только чтение секретов resource "yandex_lockbox_secret_iam_binding" "app_viewer" { secret_id = yandex_lockbox_secret.app_secrets.id role = "lockbox.payloadViewer" members = ["serviceAccount:${yandex_iam_service_account.app.id}"] } // Только запись в бакет resource "yandex_storage_bucket_iam_binding" "app_writer" { bucket = yandex_storage_bucket.uploads.bucket role = "storage.writer" members = ["serviceAccount:${yandex_iam_service_account.app.id}"] }
Least privilege для пользователей БД
sql// ПЛОХО: superuser для приложения CREATE USER app WITH SUPERUSER PASSWORD 'secret'; // ПРАВИЛЬНО: минимальные права CREATE USER app WITH PASSWORD 'secret'; GRANT CONNECT ON DATABASE mydb TO app; GRANT USAGE ON SCHEMA public TO app; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app; GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO app; // НЕ даём: // - CREATE, DROP, ALTER // - Доступ к pg_catalog для записи // - SUPERUSER, CREATEDB, CREATEROLE
Least privilege для API
Разные роли — разные права:
gotype Role string const ( RoleAdmin Role = "admin" RoleUser Role = "user" RoleViewer Role = "viewer" ) var rolePermissions = map[Role]map[string]bool{ RoleAdmin: { "repos:create": true, "repos:read": true, "repos:update": true, "repos:delete": true, "users:manage": true, }, RoleUser: { "repos:create": true, "repos:read": true, "repos:update": true, "repos:delete": false, // не может удалять "users:manage": false, }, RoleViewer: { "repos:create": false, "repos:read": true, // только чтение "repos:update": false, "repos:delete": false, "users:manage": false, }, }
Middleware для проверки прав:
gofunc RequirePermission(permission string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user := GetUser(r.Context()) if !hasPermission(user.Role, permission) { writeProblem(w, http.StatusForbidden, "Forbidden", "insufficient permissions") return } next.ServeHTTP(w, r) }) } }
Least privilege для сети
Изоляция сервисов на уровне сети:
hcl// Security groups в Yandex Cloud / AWS // Приложение принимает трафик только от load balancer resource "yandex_vpc_security_group_rule" "app_ingress" { direction = "ingress" protocol = "TCP" port = 8080 source_security_group_id = yandex_vpc_security_group.lb.id } // БД принимает трафик только от приложения resource "yandex_vpc_security_group_rule" "db_ingress" { direction = "ingress" protocol = "TCP" port = 5432 source_security_group_id = yandex_vpc_security_group.app.id }
6.5 mTLS и Zero Trust
Что такое mTLS
Mutual TLS — обе стороны предъявляют сертификаты:
- Клиент проверяет сертификат сервера (как обычный TLS)
- Сервер проверяет сертификат клиента (дополнительно)
Это обеспечивает аутентификацию на транспортном уровне.
Когда использовать mTLS
- Internal communication между микросервисами
- Подключение к БД
- Внешние API с высоким уровнем безопасности
- Zero Trust архитектура
Настройка mTLS в Go
Сервер:
gotlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: clientCAPool, // CA для проверки клиентских сертификатов } srv := &http.Server{ Addr: ":8443", Handler: router, TLSConfig: tlsConfig, } srv.ListenAndServeTLS("server.crt", "server.key")
Клиент:
gocert, err := tls.LoadX509KeyPair("client.crt", "client.key") if err != nil { return fmt.Errorf("load client cert: %w", err) } tlsConfig := &tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: serverCAPool, // CA для проверки сервера MinVersion: tls.VersionTLS12, } client := &http.Client{ Transport: &http.Transport{ TLSClientConfig: tlsConfig, }, }
Zero Trust архитектура
Принцип: "Never trust, always verify."
В традиционной архитектуре:
- Внутри сети всё доверенно
- Периметр защищает от внешних угроз
В Zero Trust:
- Нет доверенной сети
- Каждый запрос аутентифицируется и авторизуется
- Шифрование везде (even internal)
Компоненты Zero Trust
-
Identity: аутентификация каждого запроса
- Service-to-service: mTLS, JWT
- User-to-service: OAuth 2.0, OIDC
-
Authorization: проверка прав для каждого запроса
- RBAC, ABAC
- Policy engine (OPA, Casbin)
-
Encryption: шифрование всего трафика
- TLS для внешнего
- mTLS для внутреннего
-
Microsegmentation: изоляция сервисов
- Network policies
- Service mesh (Istio, Linkerd)
Service mesh для mTLS
Для Kubernetes service mesh автоматизирует mTLS:
Istio:
- Автоматическая ротация сертификатов
- mTLS между всеми подами
- Traffic policies
- Observability
Пример PeerAuthentication в Istio:
yamlapiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default namespace: production spec: mtls: mode: STRICT # Требовать mTLS для всех
6.6 Secure external communication
HTTPS везде
Все внешние коммуникации только через HTTPS:
go// ПЛОХО: HTTP client.Get("http://api.example.com") // ПРАВИЛЬНО: HTTPS client.Get("https://api.example.com")
TLS конфигурация
Минимальная версия TLS 1.2:
gotlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, // Запрет слабых cipher suites CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, }, }
Certificate pinning (осторожно)
Для критичных интеграций можно pin'ить сертификаты:
go// ПИННинг публичного ключа expectedPin := "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Проверяем при handshake tlsConfig := &tls.Config{ VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { for _, rawCert := range rawCerts { cert, _ := x509.ParseCertificate(rawCert) pin := hashPublicKey(cert.RawSubjectPublicKeyInfo) if pin == expectedPin { return nil } } return errors.New("certificate pin mismatch") }, }
ОСТОРОЖНО: pinning ломается при ротации сертификатов. Используйте только для критичных интеграций с backup pins.
Валидация сертификатов
Никогда не отключайте валидацию сертификатов:
go// ПЛОХО: отключение валидации tlsConfig := &tls.Config{ InsecureSkipVerify: true, // НЕ ДЕЛАЙТЕ ТАК! } // ПРАВИЛЬНО: валидация с custom CA если нужно certPool := x509.NewCertPool() certPool.AppendCertsFromPEM(caCert) tlsConfig := &tls.Config{ RootCAs: certPool, }
Timeout для внешних вызовов
Всегда устанавливайте таймауты:
goclient := &http.Client{ Timeout: 30 * time.Second, Transport: &http.Transport{ TLSHandshakeTimeout: 10 * time.Second, ResponseHeaderTimeout: 10 * time.Second, IdleConnTimeout: 90 * time.Second, }, }
6.7 Audit logging углублённо
Что такое audit log
Audit log — журнал критичных операций для:
- Расследования инцидентов
- Compliance (PCI DSS, HIPAA, GDPR)
- Отслеживания действий пользователей
- Обнаружения аномалий
Что логировать в audit
Обязательные события:
- Аутентификация (успех/неудача)
- Авторизация (доступ к ресурсам)
- Создание/изменение/удаление данных
- Изменение прав и ролей
- Доступ к секретам
- Административные действия
Структура audit event
gotype AuditEvent struct { Timestamp time.Time `json:"timestamp"` EventID string `json:"event_id"` UserID string `json:"user_id"` UserAgent string `json:"user_agent"` IPAddress string `json:"ip_address"` Action string `json:"action"` // "repo.delete" Resource string `json:"resource"` // "repo-123" ResourceType string `json:"resource_type"` // "repo" Result string `json:"result"` // "success", "failure" Error string `json:"error,omitempty"` Metadata map[string]interface{} `json:"metadata,omitempty"` RequestID string `json:"request_id"` TraceID string `json:"trace_id"` }
Реализация audit logger
gotype AuditLogger struct { logger *slog.Logger // Отдельный writer для audit (не смешивать с обычными логами) writer io.Writer } func NewAuditLogger(writer io.Writer) *AuditLogger { return &AuditLogger{ logger: slog.New(slog.NewJSONHandler(writer, nil)), writer: writer, } } func (l *AuditLogger) Log(ctx context.Context, event AuditEvent) { event.Timestamp = time.Now() event.EventID = generateID() event.RequestID = middleware.GetRequestID(ctx) event.TraceID = tracing.GetTraceID(ctx) l.logger.Info("audit", "event_id", event.EventID, "user_id", event.UserID, "action", event.Action, "resource", event.Resource, "result", event.Result, "ip", event.IPAddress, ) }
Использование в сервисах
gofunc (s *RepoService) DeleteRepo(ctx context.Context, id string) error { userID := middleware.GetUserID(ctx) err := s.repo.Delete(ctx, id) // Audit log независимо от результата result := "success" errMsg := "" if err != nil { result = "failure" errMsg = err.Error() } s.auditLogger.Log(ctx, AuditEvent{ UserID: userID, Action: "repo.delete", Resource: id, ResourceType: "repo", Result: result, Error: errMsg, IPAddress: middleware.GetIPAddress(ctx), UserAgent: middleware.GetUserAgent(ctx), }) return err }
Хранение audit logs
Требования:
- Append-only (нельзя изменить или удалить)
- Долгий retention (1-7 лет в зависимости от compliance)
- Защита от несанкционированного доступа
- Отдельное хранилище от приложения
Варианты:
- Отдельная БД с restricted access
- Object storage с versioning (S3, Yandex Object Storage)
- Специализированные сервисы (CloudTrail, Yandex Audit Logs)
Неизменяемость audit logs
sql// Append-only таблица CREATE TABLE audit_events ( id UUID PRIMARY KEY, timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), event JSONB NOT NULL ); // Запрет UPDATE и DELETE REVOKE UPDATE, DELETE ON audit_events FROM app; // Или используем partitioning по времени // и удаляем только старые partitions
6.8 Supply chain security
Что такое supply chain attack
Атака через зависимости и инструменты сборки:
- Вредоносный пакет в registry
- Компрометированный CI/CD
- Подмена бинарника при деплое
Примеры:
- event-stream (npm, 2018)
- ua-parser-js (npm, 2021)
- SolarWinds (2020)
SBOM (Software Bill of Materials)
SBOM — список всех компонентов приложения:
- Прямые зависимости
- Транзитивные зависимости
- Версии
- Лицензии
Генерация SBOM в Go:
# Syft - генерация SBOM
syft packages dir:. -o json > sbom.json
# Или для Docker образа
syft packages myapp:latest -o spdx-json > sbom.json
Проверка зависимостей
# govulncheck - известные уязвимости в Go коде
govulncheck ./...
# gosec - статический анализ безопасности
gosec ./...
# Trivy - сканирование зависимостей
trivy fs --security-checks vuln ./
# npm audit, pip audit для других экосистем
Подпись артефактов
Используем sigstore/cosign для подписи Docker образов:
# Подпись образа
cosign sign --key cosign.key registry.example.com/myapp:v1.0.0
# Проверка подписи
cosign verify --key cosign.pub registry.example.com/myapp:v1.0.0
Проверка checksum
Для бинарных артефактов:
# При сборке генерируем checksum
sha256sum git-server > git-server.sha256
# При деплое проверяем
sha256sum -c git-server.sha256
Reproducible builds
Сборка должна быть воспроизводимой:
dockerfile# Фиксированные версии в go.mod # go.sum для проверки зависимостей # Фиксированные версии базовых образов в Dockerfile # Build args для версионирования FROM golang:1.23.4-alpine3.19 AS builder # Не FROM golang:latest
CI/CD security
-
Secrets management в CI
- Не хардкодить секреты в pipeline
- Использовать secret storage (GitHub Secrets, Vault)
-
Least privilege для CI
- Минимальные права для pipeline
- Read-only где возможно
-
Проверка кода перед merge
- SAST (gosec, Semgrep)
- SCA (Trivy, govulncheck)
- Code review
-
Подпись коммитов
- GPG подписи для коммитов
- Verified commits в git
6.9 Threat modeling
Что такое threat modeling
Процесс идентификации угроз и определения контрмер до написания кода.
STRIDE методология
| Категория | Угроза | Пример |
|---|---|---|
| Spoofing | Подмена личности | Фальшивый JWT токен |
| Tampering | Изменение данных | SQL injection |
| Repudiation | Отказ от действий | Нет audit log |
| Information Disclosure | Утечка данных | PII в логах |
| Denial of Service | Отказ в обслуживании | Нет rate limiting |
| Elevation of Privilege | Повышение прав | IDOR уязвимость |
Пример threat modeling для API
Шаг 1: Определяем компоненты
Client -> Load Balancer -> API Gateway -> Service -> Database
-> Cache
-> External API
Шаг 2: Идентифицируем угрозы по STRIDE
Spoofing:
- Подмена JWT токена -> Валидация подписи, expiration
- Подмена IP -> Не доверять X-Forwarded-For без проверки
Tampering:
- SQL injection -> Параметризованные запросы
- Изменение запроса в transit -> HTTPS
Information Disclosure:
- PII в логах -> Маскирование
- Ошибки с деталями -> Generic error messages
Denial of Service:
- Много запросов -> Rate limiting
- Большие payload -> MaxBytesReader
Шаг 3: Определяем контрмеры
Для каждой угрозы:
- Контрмера
- Приоритет (High/Medium/Low)
- Ответственный
- Дедлайн
DREAD для оценки рисков
| Фактор | Описание | Оценка 1-5 |
|---|---|---|
| Damage | Ущерб от эксплуатации | |
| Reproducibility | Легко воспроизвести | |
| Exploitability | Легко эксплуатировать | |
| Affected users | Сколько пользователей затронуто | |
| Discoverability | Легко обнаружить |
Risk = среднее значение. High: > 4, Medium: 2-4, Low: < 2.
Практический threat modeling
Проводите threat modeling:
- При проектировании нового сервиса
- При добавлении новой функциональности
- При изменении архитектуры
- Перед публичным релизом
Результат: документ с угрозами и контрмерами, приоритизированный backlog security задач.
Привязка к шаблону проекта
Где security в шаблоне
internal/security/
├── hmac/
│ ├── signer.go # HMAC signing
│ ├── verifier.go # HMAC verification
│ └── rotation.go # Ротация ключей
└── policy/
└── labels.go # Security labels
internal/handlers/jobs/hmac_rotator/
└── handler.go # HMAC rotator cron job
internal/adapters/lockbox/
└── secret_provider.go # Lockbox secrets
Что изучаем на примере шаблона
- HMAC signer и verifier с multi-key support
- Ротация ключей через cron job
- Lockbox для секретов
- Security labels для классификации данных
- PII masking в логах
Задание: изучите internal/security/
- Как реализован HMAC?
- Есть ли multi-key support?
- Как настроена ротация?
- Какие security policies?
💱 Компромиссы
- Жёсткий один ключ vs окно двухфазной ротации: удалив previous ключ сразу, вы закрываете окно атаки, но ломаете клиентов, ещё не перешедших на новый; 24 часа с двумя ключами дают ротацию без downtime, но скомпрометированный старый ключ тоже продолжает работать.
- In-memory nonce store vs Redis: локальный store быстр и не тянет
зависимостей, но в распределённой системе replay пройдёт на другом
инстансе; Redis с
SETNXзакрывает все инстансы сразу, но это сетевой хоп на каждый webhook и ещё одна точка отказа. - Least privilege vs широкие роли: минимальные гранты вроде
lockbox.payloadViewerи SELECT/INSERT ограничивают blast radius, но каждое новое действие требует правки ролей; admin-роль не мешает работе, но компрометация одного сервиса раскрывает всё. - mTLS на каждый хоп vs доверенная сеть: mutual TLS аутентифицирует обе стороны по Zero Trust, но сертификаты надо раздавать и ротировать — вручную это боль, нужен cert-manager или Istio; доверие внутри периметра проще, но взлом одного pod'а отдаёт всю сеть.
- Certificate pinning vs обычная валидация: pin публичного ключа защищает даже при компрометации CA, но ломается при ротации сертификата, если не заложены backup pins; стандартная проверка по RootCAs переживает любую ротацию, но доверяет каждому CA из пула.
Практическое задание
Задание 1: Multi-key HMAC verifier
Реализуйте verifier с поддержкой нескольких ключей:
gotype MultiKeyVerifier struct { keys map[string][]byte // keyID -> key } func (v *MultiKeyVerifier) Verify(message []byte, signature string) error
Поддержка формата: keyid.signature
Задание 2: Replay protection
Добавьте replay protection для webhook:
gofunc (v *Verifier) VerifyWebhook(body []byte, webhookID string, timestamp int64, signature string) error
С:
- Проверкой timestamp (не старше 5 минут)
- Nonce store для webhook ID
- Cleanup старых nonce
Задание 3: Secret rotation
Реализуйте ротацию HMAC ключей:
gofunc (r *SecretRotator) RotateHMACKey(ctx context.Context) error
С:
- Генерацией нового ключа
- Сохранением previous key
- Уведомлением команды
- Cron job для автоматической ротации
Задание 4: Least privilege для БД
Создайте пользователя БД с минимальными правами:
sqlCREATE USER app WITH PASSWORD 'secret'; GRANT CONNECT ON DATABASE mydb TO app; GRANT USAGE ON SCHEMA public TO app; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app;
Проверьте что пользователь не может:
- Создавать таблицы
- Удалять таблицы
- Изменять схему
Задание 5: mTLS
Настройте mTLS между двумя сервисами:
Сервер:
- RequireAndVerifyClientCert
- ClientCAs с CA для клиентских сертификатов
Клиент:
- Клиентский сертификат
- RootCAs с CA сервера
Протестируйте что запрос без клиентского сертификата отклоняется.
Задание 6: Audit logging
Реализуйте audit logger:
gofunc (l *AuditLogger) Log(ctx context.Context, event AuditEvent)
Добавьте audit logging для:
- Создания репозитория
- Удаления репозитория
- Изменения прав пользователя
- Доступа к секретам
Задание 7: SBOM
Сгенерируйте SBOM для вашего проекта:
syft packages dir:. -o json > sbom.json
Просканируйте на уязвимости:
trivy fs --security-checks vuln ./
Исправьте найденные критичные уязвимости.
Задание 8: Threat modeling
Проведите threat modeling для вашего сервиса по STRIDE:
- Нарисуйте диаграмму компонентов
- Идентифицируйте угрозы по каждой категории STRIDE
- Определите контрмеры
- Приоритизируйте по DREAD
- Создайте backlog security задач
Quiz "Проверь себя"
expected := computeMAC(message, key)
received, _ := hex.DecodeString(sigHex)
// сравнение подписей:✅ Итоги модуля
Студент должен:
- Реализовывать HMAC с multi-key и replay protection
- Проектировать ротацию секретов без downtime
- Применять least privilege на всех уровнях
- Настраивать mTLS для internal communication
- Понимать Zero Trust архитектуру
- Обеспечивать безопасную внешнюю коммуникацию
- Проектировать audit-friendly логирование
- Понимать supply chain security
- Проводить базовый threat modeling по STRIDE
- Ответить на все вопросы Quiz
🔗 Мостик к Модулю 7
Следующий модуль: Модуль 7. Storage and Data Strategy - schema evolution, read/write separation, backups and restore, NFS vs S3, data retention, disaster recovery strategy. Привязка к internal/adapters/nfs/, internal/adapters/s3/, internal/dr/ шаблона.