Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Middle: питання на співбесіді з теми «Інфраструктура й секрети»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

6 питань

php artisan env:encrypt шифрує файл оточення, щоб його можна було безпечно зберігати в репозиторії - разом з кодом, з історією змін і рев'ю, але без відкритих секретів.

php artisan env:encrypt --env=production
# створює .env.production.encrypted, виводить ключ (показується один раз)
  • шифр за замовчуванням - AES-256-CBC (можна змінити через --cipher);
  • ключ генерується автоматично або передається через --key;
  • --prune - видалити вихідний відкритий файл після шифрування;
  • --readable - шифрувати кожне значення окремо, залишаючи назви змінних відкритими: у diff видно, яка змінна змінилася, без розкриття значення.

Розшифрування на сервері під час деплою:

php artisan env:decrypt --env=production --key="$LARAVEL_ENV_ENCRYPTION_KEY"

Ключ береться з опції --key або змінної оточення LARAVEL_ENV_ENCRYPTION_KEY. Тобто на сервер (чи в CI) потрапляє один секрет замість десятків.

Що це дає:

  • секрети версіонуються разом з кодом - відомо, хто й коли змінив конфігурацію;
  • новий член команди отримує доступ до конфігурації оточення через один ключ;
  • немає «живих» .env, що пересилаються в месенджерах.

Обмеження й ризики:

  • увесь захист - в одному ключі. Його витік відкриває всі секрети з усієї історії репозиторію. Ключ зберігають у менеджері секретів CI чи хостингу, а не поруч з кодом;
  • ротація секретів все одно потрібна - шифрування файлу не скасовує заміну скомпрометованих значень;
  • історія: зашифровані версії лишаються в Git назавжди. Якщо ключ колись витік, старі версії з тоді чинними секретами теж розкриті;
  • доступ «все або нічого»: будь-хто з ключем бачить усі секрети оточення. Для розмежування доступу краще повноцінний менеджер секретів (Vault, AWS Secrets Manager, Doppler) чи SOPS з ключами для різних людей.

Добрий компроміс для невеликих команд: зашифрований .env.production у репозиторії, ключ - у змінних CI/хостингу, розшифрування на етапі деплою.

Докладніше в документації: Laravel: шифрування файлів оточення

Сертифікати - найчастіше від Let's Encrypt (безкоштовні, на 90 днів) через протокол ACME. Короткий термін - свідомий вибір: компрометований сертифікат швидше перестає діяти, а автоматизація стає обов'язковою.

Автоматизація замість ручного оновлення:

  • Caddy чи FrankenPHP отримують і оновлюють сертифікати автоматично, без жодних налаштувань;
  • Certbot з таймером systemd для Nginx/Apache;
  • хмарні балансувальники й CDN (Cloudflare, AWS) керують сертифікатами самі.

Прострочений сертифікат - одна з найпоширеніших причин «падіння» сайту, тому навіть з автоматизацією потрібен моніторинг терміну дії зі сповіщенням за 2-3 тижні.

Конфігурація TLS - не вигадувати власну, а брати перевірену: Mozilla SSL Configuration Generator генерує налаштування для Nginx, Apache, HAProxy під профіль:

  • Modern - лише TLS 1.3 (сучасні клієнти);
  • Intermediate - TLS 1.2 і 1.3 з надійними шифрами (рекомендований для більшості сайтів);
  • Old - для дуже старих клієнтів (уникати).

Що має бути в налаштуваннях:

  • вимкнені SSL 3.0, TLS 1.0 і 1.1;
  • шифри з прямою секретністю (ECDHE) і AEAD (AES-GCM, ChaCha20-Poly1305);
  • HSTS (Strict-Transport-Security) після того, як HTTPS працює стабільно;
  • редирект з HTTP на HTTPS для сайту (для API - краще відмова);
  • OCSP stapling - якщо CA його підтримує (Let's Encrypt відмовляється від OCSP на користь коротших сертифікатів і CRL).

Перевірка: SSL Labs Server Test (оцінка A/A+), testssl.sh для внутрішніх серверів.

Внутрішній трафік: TLS, що закінчується на балансувальнику чи Cloudflare, лишає незашифрованим шлях до сервера застосунку. Для Cloudflare - режим Full (strict) з сертифікатом на сервері (зокрема безкоштовним Origin CA), а не Flexible, де трафік до origin іде по HTTP.

Приватні ключі - доступні лише процесу вебсервера, не в репозиторії, не в бекапах без шифрування.

Докладніше в документації: Mozilla SSL Configuration Generator

Контейнер - не віртуальна машина: він ділить ядро з хостом, і помилки конфігурації можуть дати вихід за його межі. Основні правила:

1. Не працювати від root.

FROM php:8.5-fpm-alpine
# ...
RUN addgroup -S app && adduser -S app -G app
USER app

Вразливість у застосунку, що дає виконання коду, в контейнері від root - це root у контейнері, і значно ближче до хоста.

2. Мінімальний образ. Alpine чи slim-варіанти, без компіляторів, curl, git і пакетів «на всяк випадок». Менше програм - менше вразливостей і інструментів для зловмисника. Багатоетапне збирання (multi-stage) - залежності й ассети збираються в одному етапі, а в фінальний образ потрапляє лише результат.

3. Жодних секретів в образі. COPY .env . чи ARG API_KEY лишаються в шарах образу назавжди - будь-хто з доступом до образу їх дістане (docker history, розпакування шарів). Секрети - змінними оточення чи змонтованими файлами під час запуску; для збирання - --mount=type=secret. Плюс .dockerignore з .env, .git, node_modules.

4. Файлова система лише для читання, де можливо (read_only: true у Compose), з окремими томами для storage/ і тимчасових файлів.

5. Обмеження прав контейнера:

  • cap_drop: [ALL] і лише потрібні можливості;
  • no-new-privileges: true;
  • ніколи privileged: true і монтування /var/run/docker.sock у контейнер застосунку - це фактично root на хості.

6. Оновлення й сканування:

  • закріплені версії базових образів (а ще краще - дайджест) і регулярне перезбирання з оновленою базою;
  • сканування вразливостей образів у CI: Trivy, Grype, Docker Scout.

7. Мережа й ресурси: контейнер бази не публікує порт назовні, ліміти пам'яті й CPU (захист від виснаження ресурсів).

8. Health check і журнали в stdout - щоб оркестратор бачив стан, а журнали не накопичувалися в контейнері.

Пастка закріплення: «плаваючий» тег (php:8.5-fpm) може раптово принести несумісні зміни, а закріплений назавжди - перестати отримувати виправлення безпеки. Рішення - закріплена версія + автоматичні PR оновлень (Renovate, Dependabot).

Докладніше в документації: OWASP: безпека Docker

Сервер, щойно отримавши публічну IP-адресу, за лічені хвилини починає отримувати спроби підбору паролів SSH і сканування портів. Базовий захист:

1. SSH:

  • вхід лише за ключами, паролі вимкнено (PasswordAuthentication no);
  • заборонено вхід root (PermitRootLogin no) - окремий користувач з sudo;
  • сучасні ключі (Ed25519), захищені парольною фразою;
  • доступ до SSH - лише з певних адрес чи через VPN/bastion (WireGuard, Tailscale), або хоча б fail2ban проти перебору.

Mozilla публікує рекомендовану конфігурацію OpenSSH з безпечними алгоритмами.

2. Фаєрвол - відкрито лише потрібне:

ufw default deny incoming
ufw allow 22/tcp        # або лише з адреси VPN
ufw allow 80,443/tcp
ufw enable

База даних, Redis, панелі моніторингу - не слухають публічний інтерфейс. Окрема пастка Docker: опубліковані порти (ports: 5432:5432) обходять ufw, бо Docker сам керує правилами iptables. Порти служб - лише на 127.0.0.1 або без публікації.

3. Оновлення: автоматичні оновлення безпеки (unattended-upgrades в Ubuntu/Debian), регулярне оновлення ядра з перезавантаженням.

4. Принцип найменших прав:

  • застосунок працює від окремого непривілейованого користувача;
  • права на файли: код - лише для читання процесом PHP, запис - лише в storage/ і bootstrap/cache/;
  • без chmod 777 «щоб запрацювало».

5. Мінімум служб: видалити непотрібне ПЗ, не встановлювати phpMyAdmin і подібні панелі на продакшені.

6. Журнали й моніторинг: спроби входу, sudo, зміни в системних файлах, незвичне навантаження. Журнали - на окремий сервер, щоб зловмисник не міг їх стерти.

7. Інфраструктура як код: налаштування через Ansible, cloud-init чи образи - відтворювано й перевірено, а не «руками колись налаштований сервер», про який ніхто не пам'ятає подробиць.

Керовані платформи (Laravel Cloud, Forge, Ploi, контейнерні платформи) беруть частину цього на себе - але відповідальність за доступи, секрети й оновлення застосунку лишається за командою.

Докладніше в документації: Mozilla: рекомендації для OpenSSH

Значення за замовчуванням у PHP розраховані на зручність розробки. Для продакшену частину треба змінити.

Не розкривати інформацію:

expose_php = Off          ; без заголовка X-Powered-By: PHP/8.5.x
display_errors = Off      ; помилки не виводяться користувачу
display_startup_errors = Off
log_errors = On           ; але записуються в журнал
error_reporting = E_ALL

Точна версія PHP у заголовку підказує зловмиснику, які відомі вразливості перевіряти. Виведені помилки розкривають шляхи файлів і фрагменти коду.

Обмежити ресурси (захист від виснаження):

memory_limit = 256M
max_execution_time = 30
max_input_time = 60
post_max_size = 20M
upload_max_filesize = 10M
max_input_vars = 1000

Вимкнути небезпечне:

allow_url_include = Off   ; include за URL - класичний шлях до виконання чужого коду
allow_url_fopen = Off     ; якщо застосунку не потрібно (HTTP-клієнти працюють через curl)

disable_functions - вимкнути функції виконання команд (exec, shell_exec, system, passthru, proc_open, popen), якщо застосунок їх не використовує. Обережно: Laravel-черги, Horizon, деякі пакети й команди artisan можуть потребувати proc_open - перевіряти, а не вимикати наосліп. Для CLI і FPM можна мати різні налаштування.

open_basedir обмежує каталоги, доступні PHP. Корисно на спільному хостингу, але це не межа безпеки (історично обходилося) і помітно сповільнює роботу з файлами. У контейнерах і на виділених серверах ізоляцію краще робити засобами ОС.

Сесії (якщо використовується нативна сесія PHP):

session.cookie_httponly = 1
session.cookie_secure = 1
session.use_strict_mode = 1
session.cookie_samesite = Lax

Laravel має власну систему сесій з налаштуваннями в config/session.php, тож тут важливіше перевірити її.

OPcache для продакшену: opcache.validate_timestamps = 0 (швидкість), але тоді після деплою OPcache треба скидати - інакше працюватиме старий код, включно з невиправленою вразливістю.

Актуальна версія PHP - найважливіше налаштування: версії, що не отримують виправлень безпеки, мають відомі невиправлені вразливості.

Докладніше в документації: OWASP: конфігурація PHP

Протокол SMTP не перевіряє відправника: будь-хто може надіслати лист з From: security@your-app.com. Три DNS-записи дозволяють поштовим сервісам відрізнити справжній лист від підробки.

SPF - які сервери мають право відправляти пошту домену:

your-app.com.  TXT  "v=spf1 include:mailgun.org include:_spf.google.com -all"

Отримувач перевіряє IP-адресу сервера-відправника. Обмеження: SPF перевіряє технічну адресу конверта (Return-Path), а не видимий користувачу From, і ламається при пересиланні листів.

DKIM - криптографічний підпис листа:

mg._domainkey.your-app.com.  TXT  "k=rsa; p=MIGfMA0..."

Поштовий сервіс підписує заголовки й тіло приватним ключем, отримувач перевіряє підпис публічним ключем з DNS. Лист не можна змінити дорогою, а підпис прив'язаний до домену.

DMARC - політика, що зв'язує все разом:

_dmarc.your-app.com.  TXT  "v=DMARC1; p=reject; rua=mailto:dmarc@your-app.com; adkim=s; aspf=s"
  • вимагає вирівнювання (alignment): домен у видимому From має збігатися з доменом, що пройшов SPF чи DKIM. Саме це захищає від підробки адреси, яку бачить користувач;
  • політика: p=none (лише звіти), p=quarantine (у спам), p=reject (відхилити);
  • rua - щоденні агреговані звіти: хто й скільки листів відправляє від імені домену.

Чому це питання безпеки, а не лише доставки:

  • фішинг від вашого імені: без DMARC зловмисник надсилає «Скиньте пароль за посиланням» з вашого домену, і листи виглядають справжніми;
  • листи скидання пароля й коди входу, що потрапили в спам чи відхилені, - користувачі не можуть увійти й звертаються в підтримку, де їх легше обдурити соціальною інженерією;
  • з 2024 року Gmail і Yahoo вимагають від масових відправників (понад 5000 листів на день до Gmail) SPF, DKIM і DMARC, а від усіх відправників - щонайменше SPF чи DKIM; без них листи потрапляють у спам або відхиляються.

Як впроваджувати без зламаної пошти:

  1. SPF і DKIM для кожного сервісу, що відправляє від вашого домену (Mailgun, SES, CRM, служба підтримки);
  2. DMARC з p=none і звітами - кілька тижнів дивитися, хто ще відправляє;
  3. поступово quarantine, потім reject.

Окремий піддомен для транзакційної пошти (mg.your-app.com) ізолює її репутацію від маркетингових розсилок.

Домени без пошти теж треба захистити: v=spf1 -all і p=reject, інакше їх використають для підробки.

Докладніше в документації: RFC 7489: DMARC