Senior: питання на співбесіді з теми «Інфраструктура й секрети»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
4 питання
WAF (Web Application Firewall) - фільтр HTTP-запитів перед застосунком. Хмарні WAF (Cloudflare, AWS WAF) працюють на краю мережі, ще до вашого сервера.
Що він добре робить:
- поглинає DDoS-атаки мережевого рівня й об'ємні HTTP-флуди - ваш сервер їх не бачить;
- блокує відомі шаблони атак (керовані правила для SQL-ін'єкцій, XSS, відомих CVE популярного ПЗ) - захист «на час», поки вразливість не виправлена в коді;
- відсікає ботів і сканери: запити до
/.env,/wp-admin,/phpmyadmin, підозрілі User-Agent, перевірки на кшталт Managed Challenge; - обмеження частоти на рівні краю - для логіну, пошуку, API;
- геоблокування, списки IP, правила за ASN.
Чого WAF не робить:
- не виправляє вразливостей у логіці застосунку: BOLA (чужий id в URL), відсутня авторизація, бізнес-логіка - для WAF це звичайні коректні запити;
- не бачить усього: зашифровані дані в тілі, нестандартні формати, атаки, розтягнуті в часі;
- обходиться кодуванням і варіаціями payload-ів - сигнатурний захист не буває повним.
WAF - додатковий шар, а не заміна безпечному коду.
Головна пастка - відкритий origin. Якщо IP-адреса сервера відома (історичні DNS-записи, піддомен без проксі, заголовки листів, сертифікати в журналах Certificate Transparency), зловмисник іде напряму на сервер, оминаючи WAF і захист від DDoS.
Як закрити origin:
- фаєрвол приймає 80/443 лише з діапазонів IP Cloudflare (список публікується й змінюється - його треба оновлювати);
- Authenticated Origin Pulls (mTLS між Cloudflare і сервером) - сервер перевіряє клієнтський сертифікат Cloudflare;
- Cloudflare Tunnel - сервер узагалі не має відкритих вхідних портів, з'єднання ініціює агент з сервера;
- нова IP-адреса після підключення проксі, якщо стара вже «засвітилася»;
- усі піддомени, що ведуть на той самий сервер, - через проксі.
Справжній IP клієнта: за проксі Laravel бачить IP Cloudflare. Потрібно довіряти заголовкам лише від проксі (trustProxies з діапазонами Cloudflare) і брати CF-Connecting-IP - інакше ліміти частоти й журнали працюватимуть з неправильними адресами.
Докладніше в документації: Cloudflare: захист origin-сервера
Класичний підхід: у секретах CI лежать статичні ключі хмарного облікового запису (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY) з правами на деплой.
Проблеми статичних ключів:
- живуть роками - їх рідко ротують, бо це болісно;
- витікають: у журналах збирання, через скомпрометовану залежність чи action у пайплайні, через неуважний
echo, через форк з доступом до секретів; - працюють звідусіль - вкрадений ключ можна використати з будь-якого комп'ютера;
- часто мають завеликі права («адміністратор, щоб точно запрацювало»).
OIDC (OpenID Connect) - без збережених секретів:
- на кожен запуск пайплайну CI-платформа (GitHub Actions, GitLab CI) видає короткоживучий підписаний токен з даними про запуск: репозиторій, гілка, середовище, workflow;
- хмарний провайдер (AWS, GCP, Azure) довіряє CI-платформі як провайдеру ідентичності й обмінює цей токен на тимчасові облікові дані на хвилини;
- довіра обмежена умовами: «лише репозиторій
org/shop, лише гілкаmain, лише оточенняproduction».
permissions:
id-token: write # дозволити запуску отримати OIDC-токен
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/deploy-shop
aws-region: eu-central-1
Що це дає:
- нема чого красти - у секретах CI немає довгоживучих ключів;
- облікові дані живуть хвилини і прив'язані до конкретного запуску;
- точні умови: пул-реквест з форку чи інша гілка роль не отримають.
Що важливо налаштувати правильно:
- умови довіри (claims) мають бути вузькими - лише
repoбез гілки чи середовища дозволить будь-якій гілці (зокрема з необережного PR) деплоїти в продакшен; - права ролі - найменші: деплой конкретного сервісу, а не адміністратор облікового запису;
- захист оточень у CI (обов'язкове затвердження для
production).
Решта гігієни CI: закріплення сторонніх actions за хешем коміту (а не тегом), мінімальні permissions для GITHUB_TOKEN, обережність з pull_request_target, секрети не потрапляють у журнали.
У хмарі застосунок отримує права не через ключі в .env, а через роль, прикріплену до сервера чи контейнера (IAM-роль інстансу, workload identity). Облікові дані видає сервіс метаданих за внутрішньою адресою 169.254.169.254.
Чому це зручно: немає статичних ключів, тимчасові облікові дані оновлюються автоматично.
Чому це небезпечно без захисту: будь-яка вразливість, що дозволяє змусити сервер зробити HTTP-запит (SSRF - «завантажити зображення за URL», генерація PDF з HTML, вебхук на адресу користувача), дає зловмиснику доступ до метаданих:
http://169.254.169.254/latest/meta-data/iam/security-credentials/app-role
і облікових даних ролі. Кілька відомих масштабних витоків даних почалися саме так.
Захист сервісу метаданих:
- IMDSv2 обов'язковий (AWS): спершу
PUT-запит за сесійним токеном, потім запити з заголовком токена. Типова SSRF дозволяє лишеGETбез власних заголовків - і токен отримати не може. Для нових інстансів IMDSv2 варто вимагати явно (HttpTokens=required); - hop limit = 1 - відповідь сервісу метаданих не проходить через додатковий мережевий «стрибок»: контейнер без host-мережі не дістане метадані хоста;
- у Kubernetes - IRSA/workload identity замість ролі вузла: кожен под отримує власну роль.
Принцип найменших прав для ролі:
- лише потрібні дії й ресурси:
s3:PutObjectіs3:GetObjectнаarn:aws:s3:::shop-uploads/*, а неs3:*на*; - окремі ролі для веб-застосунку, воркерів черг, задач бекапу - компрометація одного не відкриває решту;
- жодних прав на IAM (створення користувачів і ролей) у застосунку;
- умови: обмеження за VPC, тегами, мережею;
- регулярний аудит невикористаних прав (IAM Access Analyzer та аналоги).
Захист від SSRF у коді лишається обов'язковим: перевірка URL і IP після розв'язання DNS, заборона приватних діапазонів і 169.254.0.0/16, вимкнені редиректи.
Виявлення: журнали хмарного провайдера (CloudTrail) з підозрілими діями від імені ролі застосунку (наприклад, виклики з невідомих IP) - сигнал до негайного реагування.
Докладніше в документації: AWS: налаштування сервісу метаданих (IMDS)
Ротація секретів потрібна регулярно (обмежити шкоду від непоміченого витоку) і негайно після підозри на компрометацію. Складність - не зламати роботу під час заміни.
Загальний принцип - період, коли діють обидва ключі:
- створити новий секрет;
- налаштувати систему приймати і старий, і новий;
- перевести всіх, хто використовує секрет, на новий;
- відкликати старий.
Так працює ротація ключів API партнерів, паролів до бази (два користувачі чи дві паролі в PostgreSQL), підписів вебхуків.
APP_KEY у Laravel шифрує cookie (зокрема сесійну), зашифровані атрибути моделей (каст encrypted), дані Crypt::encrypt(). Проста заміна ключа:
- розлогінить усіх користувачів - старі cookie неможливо розшифрувати;
- зламає зашифровані в базі дані - їх більше неможливо прочитати.
М'яка ротація через APP_PREVIOUS_KEYS:
APP_KEY="base64:новий..."
APP_PREVIOUS_KEYS="base64:старий..."
- шифрування - завжди новим ключем;
- розшифрування - спершу новим, потім по черзі попередніми.
Користувачі лишаються в системі, а сесії поступово перешифровуються новим ключем.
Але дані в базі самі не перешифруються. Значення, зашифровані старим ключем, лишаються такими, доки їх не перезапишуть. Перед тим як прибрати старий ключ із APP_PREVIOUS_KEYS, потрібна міграція даних: прочитати й зберегти кожне зашифроване значення (команда, що проходить по моделях частинами). Інакше видалення старого ключа - знову втрата даних.
Що ще залежить від APP_KEY: підписані URL (URL::signedRoute) - старі посилання стануть недійсними, якщо підпис не перевіряється попередніми ключами; знімки стану Livewire підписані ключем застосунку - відкриті сторінки отримають помилку при наступній дії.
Організаційно:
- менеджер секретів (Vault, AWS Secrets Manager, Doppler, хмарні сховища) - центральне місце, журнал доступу, автоматична ротація для баз даних;
- перелік, де використовується кожен секрет - без нього ротація перетворюється на пошук, що зламалося;
- регламент реагування: хто і як ротує секрети при витоку, скільки часу це займає - відпрацьоване заздалегідь, а не вперше під час інциденту.
Докладніше в документації: Laravel: м'яка ротація ключів шифрування