Senior Go Interview Prep - Core Go: https://go.vbloher.org/docs/01-core-go/ - Механика defer в Go: https://go.vbloher.org/docs/01-core-go/defer/ - Встраивание структур и интерфейсов (Embedding): https://go.vbloher.org/docs/01-core-go/embedding/ - Ошибки в Go: error, wrapping, errors.Is/As/Join: https://go.vbloher.org/docs/01-core-go/errors/ - Дженерики в Go (1.18+): https://go.vbloher.org/docs/01-core-go/generics/ - Интерфейсы в Go: https://go.vbloher.org/docs/01-core-go/interfaces/ - Устройство map в Go: https://go.vbloher.org/docs/01-core-go/maps/ - panic / recover: механика, раскрутка стека и runtime-паники: https://go.vbloher.org/docs/01-core-go/panic-recover/ - Указатели в Go: https://go.vbloher.org/docs/01-core-go/pointers/ - Рефлексия в Go (reflect): https://go.vbloher.org/docs/01-core-go/reflection/ - Внутреннее устройство слайсов в Go: https://go.vbloher.org/docs/01-core-go/slices/ - Строки, руны и байты в Go: https://go.vbloher.org/docs/01-core-go/strings-runes-bytes/ - Система типов Go: defined types, alignment, memory layout: https://go.vbloher.org/docs/01-core-go/type-system/ - Concurrency: https://go.vbloher.org/docs/02-concurrency/ - sync/atomic: https://go.vbloher.org/docs/02-concurrency/atomic/ - Буферизованные vs небуферизованные каналы: https://go.vbloher.org/docs/02-concurrency/buffered-unbuffered/ - Канал vs Mutex: когда что выбрать: https://go.vbloher.org/docs/02-concurrency/channel-vs-mutex/ - Каналы: устройство hchan: https://go.vbloher.org/docs/02-concurrency/channels/ - Утечки горутин, дедлоки, livelock, starvation: https://go.vbloher.org/docs/02-concurrency/common-leaks-deadlocks/ - sync.Cond: https://go.vbloher.org/docs/02-concurrency/cond/ - context: https://go.vbloher.org/docs/02-concurrency/context/ - Горутины: жизненный цикл, стоимость, стек: https://go.vbloher.org/docs/02-concurrency/goroutines-lifecycle/ - sync.Mutex и sync.RWMutex: https://go.vbloher.org/docs/02-concurrency/mutex-rwmutex/ - sync.Once: https://go.vbloher.org/docs/02-concurrency/once/ - Паттерны конкурентности: https://go.vbloher.org/docs/02-concurrency/patterns/ - Race Detector (гонки данных и -race): https://go.vbloher.org/docs/02-concurrency/race-detector/ - Планировщик GMP: https://go.vbloher.org/docs/02-concurrency/scheduler-gmp/ - select: https://go.vbloher.org/docs/02-concurrency/select/ - sync.WaitGroup: https://go.vbloher.org/docs/02-concurrency/waitgroup/ - Runtime и память: https://go.vbloher.org/docs/03-runtime-memory/ - Паттерны аллокаций и снижение давления на GC: https://go.vbloher.org/docs/03-runtime-memory/allocation-patterns/ - Escape Analysis: когда переменная убегает в кучу: https://go.vbloher.org/docs/03-runtime-memory/escape-analysis/ - Сборщик мусора Go: concurrent tri-color mark-sweep: https://go.vbloher.org/docs/03-runtime-memory/gc/ - Тюнинг GC: GOGC и GOMEMLIMIT: https://go.vbloher.org/docs/03-runtime-memory/gogc-gomemlimit/ - GOMAXPROCS: параллелизм планировщика и проблема контейнеров: https://go.vbloher.org/docs/03-runtime-memory/gomaxprocs/ - Утечки горутин (goroutine leaks): https://go.vbloher.org/docs/03-runtime-memory/goroutine-leaks/ - Утечки памяти в Go (несмотря на GC): https://go.vbloher.org/docs/03-runtime-memory/memory-leaks/ - Модель памяти Go (Go Memory Model): happens-before и синхронизация: https://go.vbloher.org/docs/03-runtime-memory/memory-model/ - pprof: профилирование CPU, памяти и блокировок в Go: https://go.vbloher.org/docs/03-runtime-memory/pprof/ - Execution Tracer и runtime/trace: тайминги вместо агрегатов: https://go.vbloher.org/docs/03-runtime-memory/runtime-tracing/ - Стек vs Куча: где живут данные в Go: https://go.vbloher.org/docs/03-runtime-memory/stack-vs-heap/ - Тестирование: https://go.vbloher.org/docs/04-testing/ - testify, assert/require и golden files: https://go.vbloher.org/docs/04-testing/assertions-testify/ - Бенчмарки в Go: https://go.vbloher.org/docs/04-testing/benchmarks/ - Покрытие, -race и флаки-тесты: https://go.vbloher.org/docs/04-testing/coverage-race/ - Нативный fuzzing в Go (1.18+): https://go.vbloher.org/docs/04-testing/fuzzing/ - Интеграционные тесты, testcontainers-go, TestMain: https://go.vbloher.org/docs/04-testing/integration-testcontainers/ - Моки, стабы и тестируемость: https://go.vbloher.org/docs/04-testing/mocks/ - Table-driven тесты, subtests и параллельность: https://go.vbloher.org/docs/04-testing/table-driven/ - Backend: https://go.vbloher.org/docs/05-backend/ - Аутентификация и авторизация: AuthN/AuthZ, сессии vs токены, RBAC/ABAC, API keys, mTLS, секреты: https://go.vbloher.org/docs/05-backend/auth-authz/ - Graceful Shutdown HTTP/gRPC сервера в Go: https://go.vbloher.org/docs/05-backend/graceful-shutdown/ - gRPC: типы RPC, интерсепторы, контекст, метаданные, error model: https://go.vbloher.org/docs/05-backend/grpc/ - JWT (JSON Web Token): https://go.vbloher.org/docs/05-backend/jwt/ - Middleware-паттерн в Go: https://go.vbloher.org/docs/05-backend/middleware/ - net/http: Server, Handler, ServeMux, таймауты, Client и контекст: https://go.vbloher.org/docs/05-backend/net-http/ - OAuth2: роли, grant types, OIDC, токены и типовые ошибки: https://go.vbloher.org/docs/05-backend/oauth2/ - OpenAPI/Swagger, code generation, contract-first vs code-first, валидация: https://go.vbloher.org/docs/05-backend/openapi/ - Protocol Buffers: схемы, wire format, эволюция и совместимость: https://go.vbloher.org/docs/05-backend/protobuf/ - REST: принципы, версионирование, идемпотентность, статусы, пагинация, ошибки: https://go.vbloher.org/docs/05-backend/rest/ - Сети и протоколы: https://go.vbloher.org/docs/06-networking/ - Пулы соединений: http.Transport, БД, утечки: https://go.vbloher.org/docs/06-networking/connection-pooling/ - DNS: записи, резолвинг, кэширование, DNS в Go: https://go.vbloher.org/docs/06-networking/dns/ - Версии HTTP: 1.1, 2, 3: https://go.vbloher.org/docs/06-networking/http-versions/ - TCP/IP: модель, транспорт и что важно бэкендеру: https://go.vbloher.org/docs/06-networking/tcp-ip/ - TLS: handshake, сертификаты, mTLS, производительность: https://go.vbloher.org/docs/06-networking/tls/ - UDP и надёжность поверх UDP: https://go.vbloher.org/docs/06-networking/udp/ - WebSocket: upgrade, фреймы, масштабирование: https://go.vbloher.org/docs/06-networking/websocket/ - Базы данных: https://go.vbloher.org/docs/07-databases/ - Пул соединений к PostgreSQL в Go: database/sql, pgx, pgxpool, PgBouncer: https://go.vbloher.org/docs/07-databases/connection-pooling-pgx/ - Взаимоблокировки (Deadlocks) в PostgreSQL: https://go.vbloher.org/docs/07-databases/deadlocks/ - Индексы в PostgreSQL: https://go.vbloher.org/docs/07-databases/indexes/ - Уровни изоляции транзакций в PostgreSQL: https://go.vbloher.org/docs/07-databases/isolation-levels/ - MVCC в PostgreSQL: версии строк, видимость, VACUUM и bloat: https://go.vbloher.org/docs/07-databases/mvcc/ - Обзор NoSQL и Redis: https://go.vbloher.org/docs/07-databases/nosql-redis/ - Партиционирование таблиц в PostgreSQL: https://go.vbloher.org/docs/07-databases/partitioning/ - Архитектура PostgreSQL: https://go.vbloher.org/docs/07-databases/postgresql-architecture/ - Планирование и оптимизация запросов в PostgreSQL: https://go.vbloher.org/docs/07-databases/query-planning/ - Репликация в PostgreSQL: https://go.vbloher.org/docs/07-databases/replication/ - Шардирование (горизонтальное масштабирование): https://go.vbloher.org/docs/07-databases/sharding/ - Транзакции в PostgreSQL и Go (database/sql, pgx): https://go.vbloher.org/docs/07-databases/transactions/ - Распределённые системы: https://go.vbloher.org/docs/08-distributed-systems/ - CAP теорема: https://go.vbloher.org/docs/08-distributed-systems/cap-theorem/ - Circuit Breaker: https://go.vbloher.org/docs/08-distributed-systems/circuit-breaker/ - Консенсус и Raft: репликация состояния в присутствии отказов: https://go.vbloher.org/docs/08-distributed-systems/consensus-raft/ - Модели согласованности: https://go.vbloher.org/docs/08-distributed-systems/consistency/ - Гарантии доставки сообщений: at-most-once / at-least-once / exactly-once: https://go.vbloher.org/docs/08-distributed-systems/delivery-guarantees/ - Eventual Consistency: https://go.vbloher.org/docs/08-distributed-systems/eventual-consistency/ - Идемпотентность в распределённых системах: https://go.vbloher.org/docs/08-distributed-systems/idempotency/ - Apache Kafka: https://go.vbloher.org/docs/08-distributed-systems/kafka/ - Transactional Outbox: https://go.vbloher.org/docs/08-distributed-systems/outbox/ - RabbitMQ: AMQP 0-9-1, маршрутизация, надёжность доставки и сравнение с Kafka: https://go.vbloher.org/docs/08-distributed-systems/rabbitmq/ - Ретраи: backoff, jitter, budgets и идемпотентность: https://go.vbloher.org/docs/08-distributed-systems/retries/ - Saga Pattern: https://go.vbloher.org/docs/08-distributed-systems/saga/ - Observability: https://go.vbloher.org/docs/09-observability/ - Grafana: https://go.vbloher.org/docs/09-observability/grafana/ - Метрики: RED, USE, Golden Signals: https://go.vbloher.org/docs/09-observability/metrics/ - OpenTelemetry: https://go.vbloher.org/docs/09-observability/opentelemetry/ - Prometheus: https://go.vbloher.org/docs/09-observability/prometheus/ - SLI / SLO / SLA: https://go.vbloher.org/docs/09-observability/slo-sli/ - Структурированное логирование (slog): https://go.vbloher.org/docs/09-observability/structured-logging/ - Distributed Tracing: https://go.vbloher.org/docs/09-observability/tracing/ - System Design: https://go.vbloher.org/docs/10-system-design/ - Analytics Pipeline: https://go.vbloher.org/docs/10-system-design/analytics-pipeline/ - Chat System: https://go.vbloher.org/docs/10-system-design/chat/ - Фреймворк System Design интервью: https://go.vbloher.org/docs/10-system-design/framework/ - Notification Service: https://go.vbloher.org/docs/10-system-design/notification-service/ - Order Service: https://go.vbloher.org/docs/10-system-design/order-service/ - Payment Service: https://go.vbloher.org/docs/10-system-design/payment-service/ - Rate Limiter: https://go.vbloher.org/docs/10-system-design/rate-limiter/ - URL Shortener: https://go.vbloher.org/docs/10-system-design/url-shortener/ - DevOps: https://go.vbloher.org/docs/11-devops/ - CI/CD: пайплайны, стадии, стратегии деплоя: https://go.vbloher.org/docs/11-devops/cicd/ - Облака (AWS / GCP) для бэкендера: https://go.vbloher.org/docs/11-devops/cloud-aws-gcp/ - Docker для Go-разработчика: https://go.vbloher.org/docs/11-devops/docker/ - GitHub Actions и GitLab CI: https://go.vbloher.org/docs/11-devops/github-gitlab-ci/ - Kubernetes для Go-разработчика: https://go.vbloher.org/docs/11-devops/kubernetes/ - Terraform / Infrastructure as Code: https://go.vbloher.org/docs/11-devops/terraform/ - Алгоритмы: https://go.vbloher.org/docs/12-algorithms/ - Типовые алгоритмические задачи и паттерны: https://go.vbloher.org/docs/12-algorithms/common-problems/ - Асимптотическая сложность (Big-O): https://go.vbloher.org/docs/12-algorithms/complexity/ - Структуры данных в Go: https://go.vbloher.org/docs/12-algorithms/data-structures/ - Специфика live-coding на Go: https://go.vbloher.org/docs/12-algorithms/go-specifics/ - Behavioral: https://go.vbloher.org/docs/13-behavioral/ - Конфликты, разногласия и работа со стейкхолдерами: https://go.vbloher.org/docs/13-behavioral/conflicts/ - Как проходит senior-интервью: этапы, оценка, оффер: https://go.vbloher.org/docs/13-behavioral/interview-flow/ - Лидерство и менторство: https://go.vbloher.org/docs/13-behavioral/leadership-mentoring/ - Типовые поведенческие вопросы для Senior: https://go.vbloher.org/docs/13-behavioral/senior-questions/ > Модуль: Backend · Уровень: Middle+/Senior ## TL;DR - **Аутентификация (AuthN)** — «кто ты?» (проверка личности). **Авторизация (AuthZ)** — «что тебе можно?» (проверка прав). Это два независимых шага: сначала AuthN, потом AuthZ. - **Сессии (stateful)**: server-side store + opaque cookie. Легко отозвать, но требует общего стора и липкости/репликации. **JWT (stateless)**: self-contained подпись, масштабируется горизонтально, но **невозможно мгновенно отозвать** без денилиста. Не путайте «stateless токен» с «нет состояния вообще». - **Cookie**: всегда `HttpOnly`, `Secure`, `SameSite`. Защита от XSS (HttpOnly) и CSRF (SameSite/anti-CSRF token) — это разные угрозы, нужны оба слоя. - **Пароли**: только `argon2id` (предпочтительно) / `bcrypt` / `scrypt` с уникальной солью. Никогда plaintext/MD5/SHA-256 без KDF. Сравнение — constant-time. - **RBAC** (роли) для простых иерархий; **ABAC** (атрибуты + политики) для контекстных решений; **ReBAC** (Zanzibar/SpiceDB) для графов отношений («владелец документа», sharing). - **API keys** — для machine-to-machine: хранить только хэш, скоупить, ротировать. **mTLS** — взаимная аутентификация по сертификатам, типично в service mesh. - **Секреты**: не в git, не хардкодить. Env vars — минимум; Vault/Secrets Manager + ротация + encryption at rest — для прода. Принцип наименьших привилегий + defense in depth везде. ## Теория ### 1. Аутентификация (AuthN) vs Авторизация (AuthZ) Это самая базовая и самая часто путаемая пара понятий. | Аспект | Аутентификация (AuthN) | Авторизация (AuthZ) | |---|---|---| | Вопрос | «Кто ты?» | «Что тебе разрешено?» | | Цель | Подтвердить личность | Принять решение о доступе | | Когда | Первый шаг | После успешной AuthN | | Артефакты | Пароль, OTP, сертификат, биометрия | Роли, разрешения, политики, ACL | | Ошибка | `401 Unauthorized` (несмотря на название — это про AuthN!) | `403 Forbidden` | | Меняется ли | Редко (личность стабильна) | Часто (права меняются динамически) | > **Тонкость HTTP-статусов**: `401 Unauthorized` исторически назван неверно — это «не аутентифицирован» (нет/невалидны креды). `403 Forbidden` — «аутентифицирован, но прав не хватает». На senior это любят спрашивать. ```go // Псевдо-pipeline: сначала AuthN, потом AuthZ func AuthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // AuthN: кто ты? user, err := authenticate(r) // парсит токен/сессию -> личность if err != nil { w.WriteHeader(http.StatusUnauthorized) // 401: не доказал, кто ты return } ctx := context.WithValue(r.Context(), userKey{}, user) next.ServeHTTP(w, r.WithContext(ctx)) }) } func RequirePermission(perm string, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user := r.Context().Value(userKey{}).(*User) // AuthZ: что тебе можно? if !user.Can(perm) { w.WriteHeader(http.StatusForbidden) // 403: ты известен, но нельзя return } next.ServeHTTP(w, r) }) } ``` **Идентификация vs аутентификация**: идентификация — это заявление «я Алиса» (username). Аутентификация — доказательство этого заявления (пароль/токен). Email != аутентификация. ### 2. Сессии (stateful) vs токены (stateless JWT) #### Сессии (stateful) Сервер генерирует случайный непрозрачный (opaque) session ID, кладёт его в `Set-Cookie`, а связанные данные хранит на сервере (Redis/Postgres/память). ```go // Создание сессии sid := randomToken(32) // криптослучайный, 256 бит store.Set(ctx, "sess:"+sid, SessionData{UserID: u.ID, ...}, 24*time.Hour) http.SetCookie(w, &http.Cookie{ Name: "sid", Value: sid, HttpOnly: true, Secure: true, SameSite: http.SameSiteLaxMode, Path: "/", MaxAge: 86400, }) ``` #### Токены (stateless, JWT) JWT = `base64url(header).base64url(payload).base64url(signature)`. Сервер подписывает (HMAC `HS256` или асимметрично `RS256`/`ES256`/`EdDSA`) и при каждом запросе **проверяет подпись**, не обращаясь к хранилищу. Данные (claims) лежат в payload — он **подписан, но не зашифрован** (base64, читается любым). ```go import "github.com/golang-jwt/jwt/v5" claims := jwt.MapClaims{ "sub": u.ID, "exp": time.Now().Add(15 * time.Minute).Unix(), // короткий TTL! "iat": time.Now().Unix(), "scope": "read:orders write:orders", } tok := jwt.NewWithClaims(jwt.SigningMethodES256, claims) signed, _ := tok.SignedString(privateKey) // Проверка — ОБЯЗАТЕЛЬНО фиксируем допустимые алгоритмы parsed, err := jwt.Parse(signed, func(t *jwt.Token) (any, error) { if _, ok := t.Method.(*jwt.SigningMethodECDSA); !ok { return nil, fmt.Errorf("unexpected alg: %v", t.Header["alg"]) // защита от alg-confusion } return publicKey, nil }, jwt.WithValidMethods([]string{"ES256"})) ``` #### Таблица сравнения | Критерий | Сессии (stateful) | JWT (stateless) | |---|---|---| | Хранение состояния | Сервер (Redis/БД) | Клиент (токен сам несёт claims) | | Масштабирование | Нужен общий стор или липкие сессии | Любая нода проверяет по ключу — горизонтально просто | | Revocation (отзыв) | Мгновенно: удалил из стора | Нельзя до `exp` без денилиста/короткого TTL | | Размер на запрос | Маленький cookie (ID) | Большой (весь payload в каждом запросе) | | Утечка данных | На сервере, клиент видит только ID | Payload читается любым (не класть PII/секреты) | | Изменение прав | Сразу при следующем запросе | Только после ре-выпуска токена | | Транспорт | Обычно cookie | Cookie или `Authorization: Bearer` | | Типичный риск | CSRF (если cookie) | XSS (если хранить в localStorage) | #### Revocation JWT — основная боль JWT нельзя «отозвать» по дизайну. Решения: - **Короткий TTL access-токена** (5–15 мин) + долгоживущий **refresh-токен**, который хранится stateful (в БД) и отзывается. Это де-факто стандарт. - **Денилист (blocklist)** отозванных `jti` в Redis — но это возвращает stateful-зависимость, теряется главное преимущество. - **Token versioning**: в claims кладём `token_version`, сравниваем с версией в БД (инкремент = отзыв всех токенов юзера). Но это снова чтение БД на каждый запрос. > Вывод: «полностью stateless» auth — миф для систем, где нужен мгновенный logout/ban. Refresh-токен всё равно stateful. #### Где хранить токен на клиенте (CSRF vs XSS) | Место хранения | XSS-риск | CSRF-риск | Комментарий | |---|---|---|---| | `localStorage` | **Высокий** (JS читает токен) | Нет | Любой XSS = кража токена | | `HttpOnly` cookie | Низкий (JS не читает) | **Есть** | Нужен SameSite + anti-CSRF token | | In-memory (JS-переменная) | Средний | Нет | Теряется при reload, нужен refresh-flow | Лучший практичный вариант для SPA: **access-токен в памяти** + **refresh-токен в `HttpOnly` `Secure` `SameSite` cookie**. ### 3. Cookie-атрибуты ``` Set-Cookie: sid=abc; HttpOnly; Secure; SameSite=Lax; Path=/; Domain=example.com; Max-Age=86400 ``` | Атрибут | Назначение | |---|---| | `HttpOnly` | Cookie недоступна из JS (`document.cookie`) — митигирует кражу при XSS | | `Secure` | Отправляется только по HTTPS — защита от перехвата по сети | | `SameSite=Strict` | Не шлётся при любых кросс-сайтовых запросах (даже по клику из письма) — максимум защиты от CSRF, но ломает «переход извне» | | `SameSite=Lax` | Шлётся при top-level навигации GET, но не при POST/iframe/fetch с другого сайта — разумный дефолт (и дефолт браузеров) | | `SameSite=None` | Шлётся всегда; **требует `Secure`**. Для кросс-доменных сценариев (виджеты, сторонние API) | | `Domain` | Какие хосты получают cookie. Без `Domain` — только точный хост; с `Domain=example.com` — и поддомены | | `Path` | Ограничивает cookie путём URL | | `__Host-` / `__Secure-` префиксы | Браузер форсит требования: `__Host-` требует `Secure`, `Path=/`, без `Domain` — сильная защита от подмены | > `SameSite` снижает CSRF, но **не заменяет** anti-CSRF токены полностью (старые браузеры, `SameSite=None`, GET-мутации). Defense in depth: `SameSite=Lax` + double-submit/synchronizer token для мутаций. ### 4. Хранение паролей **Никогда** не храните plaintext. **Никогда** не используйте «голые» хэши (MD5, SHA-1, SHA-256) — они быстрые, что и нужно атакующему для брутфорса/радужных таблиц. Нужен **медленный KDF с солью**. | Алгоритм | Тип | Параметры | Рекомендация | |---|---|---|---| | `argon2id` | Memory-hard | memory, iterations, parallelism | **Предпочтительно** (победитель PHC, стоек к GPU/ASIC) | | `scrypt` | Memory-hard | N, r, p | Хорошо, если нет argon2 | | `bcrypt` | CPU-hard | cost (work factor) | Ок, но лимит пароля **72 байта** (молча обрезает!) | | PBKDF2 | CPU-hard | iterations | Допустим (FIPS), но слабее против GPU | ```go import "golang.org/x/crypto/argon2" // Регистрация salt := make([]byte, 16) rand.Read(salt) // crypto/rand! hash := argon2.IDKey([]byte(password), salt, 3, // time (iterations) 64*1024, // memory KiB (64 MB) 4, // parallelism 32) // keyLen // Хранить: alg, params, salt, hash (формат PHC-строки $argon2id$v=19$m=...,t=...,p=...$salt$hash) // Проверка — constant-time сравнение! import "crypto/subtle" candidate := argon2.IDKey([]byte(input), salt, 3, 64*1024, 4, 32) ok := subtle.ConstantTimeCompare(candidate, hash) == 1 ``` **Ключевые моменты:** - **Соль** — уникальная на каждый пароль, делает радужные таблицы бесполезными. Хранится рядом с хэшем (не секрет). - **Pepper** (опционально) — глобальный секрет, добавляемый к паролю, хранится отдельно (в Vault/HSM, не в БД). Защищает при утечке только БД. - **Constant-time сравнение** (`subtle.ConstantTimeCompare`) — обычное `==` для байтов сравнивает с ранним выходом и течёт время → **timing attack**. Для самого хэша argon2 сравнивает фиксированную длину, но для токенов/HMAC это критично. - **Параметры со временем растут** — храните их вместе с хэшем, чтобы апгрейдить (rehash при логине, если параметры устарели). > **bcrypt 72-байтный лимит**: всё после 72 байта игнорируется. Если предхэшировать паролем длиннее (например base64(SHA-256(pwd))) — следите за null-байтами, bcrypt обрезает по `\0`. Argon2 этого лишён. ### 5. Модели авторизации: RBAC vs ABAC vs ReBAC | Модель | Идея | Решение основано на | Когда применять | Минусы | |---|---|---|---|---| | **RBAC** | Пользователь → роли → разрешения | Роль субъекта | Простые иерархии (admin/editor/viewer), enterprise | «Role explosion», плохо выражает контекст («только свои заказы») | | **ABAC** | Политики над атрибутами субъекта/ресурса/среды | Атрибуты + правила | Контекстные/динамические решения (время, гео, отдел, статус документа) | Сложно отлаживать/аудировать, «почему доступ?» | | **ReBAC** | Граф отношений между субъектами и ресурсами | Связь («owner of», «member of team») | Sharing, иерархии ресурсов, Google-Docs-like | Нужна спец. инфраструктура (Zanzibar/SpiceDB), сложность | #### RBAC ```go type Role struct { Name string Permissions []string // "orders:read", "orders:write" } func (u *User) Can(perm string) bool { for _, r := range u.Roles { if slices.Contains(r.Permissions, perm) { return true } } return false } ``` #### ABAC — политика как функция атрибутов ```go // "Менеджер может читать заказы своего региона в рабочее время" func canReadOrder(subj Subject, res Order, env Env) bool { return subj.Role == "manager" && subj.Region == res.Region && env.Hour >= 9 && env.Hour < 18 } ``` На практике политики выносят в движок: **OPA/Rego**, **Cedar** (AWS), **Casbin**. #### ReBAC — Zanzibar / SpiceDB / OpenFGA Авторизация как проверка достижимости в графе отношений. Запись (tuple): `document:readme#viewer@user:alice`. Проверка: «может ли alice читать readme?» обходит граф (`viewer` ⊇ `editor` ⊇ `owner`, наследование через папки/группы). ``` // SpiceDB-схема (упрощённо) definition document { relation owner: user relation viewer: user | group#member permission read = viewer + owner permission write = owner } ``` Применяют, когда права определяются **отношениями объектов**, а не статичными ролями (общие папки, вложенные команды). Решает проблему консистентности и «горячих» проверок на масштабе (как Google для Docs/Drive/YouTube). > На senior полезно: эти модели **не взаимоисключающие**. Часто RBAC для грубых ролей + ABAC/ReBAC для тонких решений. Главный архитектурный выбор — **выносить ли AuthZ в централизованный сервис/sidecar** (PDP — Policy Decision Point) или оставить в приложении (PEP — Policy Enforcement Point). ### 6. API keys (machine-to-machine) API key — длинный непрозрачный секрет для аутентификации сервиса/интеграции (не человека, у которого есть пароль+MFA). **Правила:** - Генерация: криптослучайный, с префиксом для опознания (`sk_live_...`) — помогает сканерам секретов и саппорту. - **Хранение: только хэш** (как пароль, но т.к. ключ высокоэнтропийный — достаточно быстрого `SHA-256`, KDF не обязателен). Показываем сырой ключ **один раз** при создании. - **Скоупинг**: ключ привязан к набору разрешений/ресурсов (наименьшие привилегии). Read-only ключ != admin ключ. - **Ротация**: поддержка нескольких активных ключей одновременно, чтобы менять без даунтайма (issue new → migrate → revoke old). - **Метаданные**: last_used, created_at, expiry — для аудита и протухания. ```go func newAPIKey() (display, hash string) { raw := randomToken(32) display = "sk_live_" + raw // отдаём пользователю ОДИН раз sum := sha256.Sum256([]byte(raw)) hash = hex.EncodeToString(sum[:]) // в БД храним только это return } // Проверка входящего ключа — найти по хэшу, constant-time на хэше ``` > API key слабее OAuth/mTLS (один long-lived секрет, легко утекает в логи/git). Для M2M в проде предпочтительнее **OAuth2 client credentials** (короткие токены) или **mTLS**. ### 7. mTLS (взаимный TLS) Обычный TLS: клиент проверяет сертификат сервера. **mTLS**: ещё и сервер проверяет сертификат клиента. Обе стороны аутентифицируют друг друга криптографически. ```go // Сервер требует клиентский сертификат caPool := x509.NewCertPool() caPool.AppendCertsFromPEM(caPEM) srv := &http.Server{ TLSConfig: &tls.Config{ ClientCAs: caPool, ClientAuth: tls.RequireAndVerifyClientCert, // обязателен валидный клиентский cert MinVersion: tls.VersionTLS13, }, } // Личность клиента — из r.TLS.PeerCertificates[0].Subject (CN / SAN) ``` **Когда применять:** - **Service mesh** (Istio/Linkerd): автоматический mTLS между подами, sidecar-прокси выпускает/ротирует короткоживущие сертификаты (SPIFFE/SPIRE) — zero-trust внутри кластера. - B2B-интеграции с высокими требованиями (финтех, банки). - Внутренние M2M, где не хотим раздавать долгоживущие секреты. **Плюсы:** нет «секрета-пароля» в запросе, личность встроена в транспорт, сложно подделать. **Минусы:** управление сертификатами (выпуск, ротация, отзыв через CRL/OCSP), сложнее отлаживать, нагрузка на инфраструктуру PKI. ### 8. Где хранить секреты | Способ | Плюсы | Минусы | |---|---|---| | Хардкод в коде | — | **Никогда.** Утечёт в git/историю навсегда | | `.env` файл | Просто локально | Легко закоммитить, нет ротации/аудита | | Env vars (в рантайме) | Просто, 12-factor | Видны в `/proc`, дампах, child-процессах, нет ротации | | Secrets Manager (Vault, AWS/GCP) | Ротация, аудит, encryption at rest, dynamic secrets, ACL | Сложнее, зависимость, латентность | | KMS/HSM | Ключи не покидают модуль | Дорого, для крипто-операций | **Best practices:** - **Не коммитить**: `.gitignore` для `.env`, секрет-сканеры в CI (gitleaks, trufflehog), pre-commit hooks. Если секрет попал в git — он скомпрометирован, **ротировать**, а не просто удалять коммит (история остаётся). - **Encryption at rest**: секреты в хранилище шифруются (Vault использует master key/seal, облака — KMS). - **Ротация**: автоматическая, регулярная. Vault dynamic secrets выдают короткоживущие креды БД по запросу (выдал на 1 час — отозвал). - **Доступ по наименьшим привилегиям**: сервис читает только свои секреты (Vault policies, IAM роли). Аудит-лог каждого доступа. - **Не логировать**: маскировать секреты в логах/трейсах/ошибках. - **Workload identity**: вместо статичных кредов — IAM роль пода/инстанса (AWS IRSA, GCP Workload Identity), Vault auth по Kubernetes SA. Секрет «выдаётся» по доказанной личности, ничего не хардкодится. ```go // Антипаттерн const dbPassword = "p@ssw0rd" // НИКОГДА // Лучше: из секрет-менеджера в рантайме, с возможностью обновления secret, err := vaultClient.KVv2("secret").Get(ctx, "myapp/db") pwd := secret.Data["password"].(string) ``` ### 9. Сквозные принципы - **Принцип наименьших привилегий (PoLP)**: каждый субъект (юзер, сервис, токен, IAM-роль) имеет минимум прав для задачи. Дефолт — deny. Скоупы у токенов/ключей. - **Defense in depth**: несколько независимых слоёв. Пример для cookie-auth: TLS + `HttpOnly` + `Secure` + `SameSite` + anti-CSRF token + rate limiting + короткий TTL. Падение одного слоя не компрометирует систему. - **Fail closed**: при ошибке/неопределённости — запрещать, а не разрешать. - **Zero trust**: не доверять сети по умолчанию, аутентифицировать каждый запрос (даже внутренний — отсюда mTLS в mesh). ## Подводные камни / gotchas - **`alg: none` и alg-confusion в JWT**: если не фиксировать допустимые алгоритмы при верификации, атакующий ставит `alg: none` (без подписи) или подменяет `RS256`→`HS256`, используя публичный ключ как HMAC-секрет. Всегда `jwt.WithValidMethods([...])`. - **JWT payload не зашифрован**: base64, не base64-«шифр». Не класть пароли, PII, секреты — только идентификаторы. - **«Stateless logout»**: пользователь нажал «выйти», но JWT валиден до `exp`. Без денилиста/короткого TTL токен продолжает работать — частая дыра. - **`==` для сравнения токенов/HMAC**: timing attack. Используйте `subtle.ConstantTimeCompare`. - **bcrypt 72 байта**: длинные пароли молча обрезаются; пароль из менеджера паролей может «совпасть» с другим. Использовать argon2id или предхэшировать аккуратно. - **`math/rand` для токенов/солей**: предсказуемо. Только `crypto/rand`. - **`SameSite=None` без `Secure`**: браузер отбросит cookie. И `None` отключает CSRF-защиту самой cookie. - **Хранение JWT в `localStorage`**: один XSS = кража всех токенов. Лучше HttpOnly cookie или память. - **Логирование токенов/ключей/паролей**: утечка через логи/APM/Sentry — частый инцидент. Маскировать. - **Отсутствие ротации refresh-токенов**: без rotation украденный refresh живёт долго. Применять refresh token rotation + reuse detection (повторное использование старого = компрометация, отозвать всю цепочку). - **`403` vs `404` для приватных ресурсов**: иногда отдают `404` вместо `403`, чтобы не раскрывать существование ресурса (enumeration). Зависит от модели угроз. - **Доверие клиентским claims без проверки**: роль/права в JWT валидны только если подпись проверена и issuer/audience совпадают (`iss`, `aud`, `exp`, `nbf`). - **Секреты в env при `docker inspect`/crash dump**: env-переменные видны многим; для high-security — секрет-менеджер с in-memory доставкой. ## Вопросы на собеседовании **В:** В чём разница между аутентификацией и авторизацией, и какие HTTP-статусы им соответствуют? **О:** AuthN — проверка личности («кто ты»), AuthZ — проверка прав («что можно»). Сначала AuthN, потом AuthZ. `401 Unauthorized` — про AuthN (нет/невалидны креды), `403 Forbidden` — про AuthZ (аутентифицирован, но прав нет). Название 401 исторически вводит в заблуждение. **В:** Когда выбрать сессии, а когда JWT? **О:** Сессии — когда нужен мгновенный отзыв, простая модель, и есть общий стор (Redis); особенно для классических web-приложений с cookie. JWT — когда нужно горизонтальное масштабирование без общего стора, межсервисная передача identity, мобильные/SPA клиенты. Но «чистый stateless» ломается на logout/ban — почти всегда добавляют stateful refresh-токены. Гибрид: короткий JWT access + stateful refresh. **В:** Как отозвать JWT до истечения срока? **О:** По дизайну нельзя без состояния. Варианты: короткий TTL + отзываемый refresh-токен (стандарт); денилист `jti` в Redis (возвращает stateful); token_version в claims со сверкой в БД. Любой реальный отзыв требует серверного состояния — это компромисс с самой идеей stateless. **В:** Чем отличаются угрозы XSS и CSRF, и как защититься? **О:** XSS — выполнение чужого JS на странице (крадёт токены, действует от имени юзера). Защита: CSP, экранирование, `HttpOnly` cookie (JS не прочитает). CSRF — браузер автоматически шлёт cookie на запрос, инициированный чужим сайтом. Защита: `SameSite=Lax/Strict`, anti-CSRF токены (synchronizer/double-submit), проверка Origin/Referer. Это разные угрозы — нужны оба слоя (defense in depth). **В:** Как правильно хранить пароли и что не так с SHA-256? **О:** Только медленный KDF с уникальной солью: argon2id (предпочтительно), bcrypt, scrypt. SHA-256/MD5 быстрые и без соли → уязвимы к брутфорсу на GPU и радужным таблицам. Соль уникальна на пароль, хранится с хэшем. Сравнение constant-time. Параметры KDF растут со временем (rehash при логине). Опционально pepper в отдельном хранилище. **В:** RBAC vs ABAC vs ReBAC — когда что? **О:** RBAC — статичные роли/разрешения, простые иерархии, но «role explosion» и слабо выражает контекст. ABAC — политики над атрибутами (субъект/ресурс/среда), хорош для динамических/контекстных решений (время, гео, статус), сложнее аудит. ReBAC (Zanzibar/SpiceDB/OpenFGA) — авторизация как граф отношений, идеален для sharing и иерархий ресурсов (Google Docs). На практике комбинируют; ключевое решение — централизованный PDP или логика в приложении. **В:** Что такое mTLS и где он уместен? **О:** Взаимный TLS: не только клиент проверяет сервер, но и сервер проверяет клиентский сертификат — обе стороны криптографически аутентифицированы. Уместен в service mesh (Istio/Linkerd с SPIFFE/SPIRE, авто-ротация коротких сертов, zero-trust), B2B-интеграциях, внутреннем M2M. Личность встроена в транспорт, нет «секрета-пароля» в запросе; цена — управление PKI (выпуск/ротация/отзыв). **В:** Где хранить секреты в проде и почему не в env-переменных? **О:** Лучше секрет-менеджер (Vault, AWS/GCP Secrets Manager): ротация, аудит, encryption at rest, dynamic secrets, ACL по наименьшим привилегиям. Env-переменные просты, но видны в `/proc`, дампах, child-процессах, без ротации/аудита. Никогда не коммитить (сканеры в CI, ротация при утечке). Идеал — workload identity (IAM-роль пода) вместо статичных кредов. **В:** Как безопасно реализовать API keys для M2M? **О:** Криптослучайный ключ с префиксом (`sk_live_`), хранить только хэш (SHA-256 достаточно из-за высокой энтропии), показывать сырой ключ один раз. Скоупить под минимальные права, поддержать несколько активных ключей для бесшовной ротации, метаданные (last_used, expiry). Для прода предпочтительнее OAuth2 client credentials или mTLS — API key это один долгоживущий секрет, легко утекает. **В:** Какие атаки на JWT-верификацию вы знаете? **О:** `alg: none` (подпись отключена), alg-confusion `RS256`→`HS256` (публичный ключ используется как HMAC-секрет). Защита: всегда фиксировать допустимые алгоритмы (`WithValidMethods`), не доверять `alg` из заголовка, проверять `iss`/`aud`/`exp`/`nbf`. Также: незашифрованный payload (не класть секреты), отсутствие отзыва. ## На что копают на senior+ - **Архитектура AuthZ на масштабе**: где принимается решение — PEP в каждом сервисе vs централизованный PDP (OPA-sidecar, авторизационный сервис), как избежать «горячих» проверок и обеспечить консистентность (проблема, которую решает Zanzibar через зукиперы/тайм-стемпы). - **Полный refresh-flow**: rotation, reuse detection, привязка refresh к устройству/family, отзыв всей цепочки при компрометации, sliding vs absolute expiration. - **Identity federation**: OIDC поверх OAuth2, как валидировать токены IdP (JWKS, ротация ключей, кэш), SSO, SAML vs OIDC, audience/issuer изоляция между сервисами. - **Zero trust / SPIFFE**: workload identity, SVID, авто-ротация сертов в mesh, отказ от долгоживущих секретов вовсе. - **Криптогигиена**: выбор HS vs RS vs ES vs EdDSA, управление и ротация подписывающих ключей, key rolling без даунтайма (kid в заголовке), HSM/KMS для приватных ключей. - **Threat modeling конкретного флоу**: умение разложить логин/refresh на угрозы (STRIDE), назвать слои defense in depth и failure modes (fail closed). - **Секреты как процесс, а не настройка**: динамические креды, lease/TTL, аудит доступа, реакция на инцидент (ротация всего при утечке), отделение PoLP по сервисам. - **Производительность и кэш авторизации**: как кэшировать решения без ослабления отзыва, инвалидция при изменении прав, decision logs для аудита и отладки «почему доступ».