0% прочитано

Приватне S3-сховище з самого початку: не робити файли публічними лише для зручності

Якщо файл у production має бути приватним, локально я теж перевіряю приватний сценарій. Відкритий бакет може спростити розробку, але водночас приховати помилки в контролі доступу, тимчасових посиланнях і самій моделі роботи з файлами.

9 жовтня 2026 р. 9 хв читанняLaradock

Коли я залишила однакову S3-конфігурацію для local і production, для мене було важливо зберегти не тільки однаковий технічний контракт, а й однаковий зміст роботи з файлами.

Якщо файл у production має бути приватним, локально він теж не повинен ставати публічним лише тому, що так простіше перевірити його в браузері.

Інакше локальне середовище перестає перевіряти той сценарій, який насправді буде працювати після розгортання.

Приватність файла є частиною поведінки системи. Це не production-дрібниця, яку можна відкласти до моменту розгортання.

Публічний бакет здається зручним рішенням

Зручність може приховати неправильну модель доступу
Upload file    |    vPublic bucket    |    vCopy URL    |    vOpen in browser  Дуже просто. Але access flowвже не такий,

Під час локальної розробки виникає природне бажання просто зробити бакет публічним.

Тоді файл легко перевірити: скопіювати адресу і відкрити її в браузері.

Але якщо production-файл повинен відкриватися лише після перевірки прав користувача, такий локальний сценарій обходить найважливішу частину поведінки.

Приватний файл не повинен відкриватися лише тому, що відома його адреса

Знання шляху не дорівнює праву доступу
Known object path private/video/example.mp4        |        vDirect anonymous request        |        XNo permanent public access  Access must come throughan allowed application flow.

Для приватного файла сам шлях до об’єкта не повинен бути достатнім дозволом.

Користувач може знати назву файла або навіть повний ключ об’єкта, але це не означає, що сховище повинно віддати його без додаткового механізму доступу.

Саме таку поведінку я хочу бачити і локально.

Приватність не потребує окремого способу збереження файла

Збереження через звичайний s3 disk
Storage::disk('s3')->put(    $path,    $contents,);

Сам файл я продовжую зберігати через стандартний S3 disk Laravel.

Приватність не означає, що застосунку потрібен інший драйвер або окремий PrivateMinioService.

Вона означає, що сховище не повинно давати постійний анонімний доступ до цього об’єкта.

Я не позначаю приватний файл як public лише для локальної перевірки

Публічність додана лише для зручності
Storage::disk('s3')->put(    $path,    $contents,    'public',);
Приватна модель зберігається
Storage::disk('s3')->put(    $path,    $contents,);

Якщо правила системи кажуть, що файл приватний, я не передаю public лише тому, що хочу швидко відкрити його за прямою адресою.

Інакше тест проходить саме тому, що я прибрала обмеження, яке мала перевірити.

Не роблю весь бакет публічним заради одного простого сценарію

Зміна політики бакета має широкий вплив
Private bucket object A -> privateobject B -> privateobject C -> private  Temporary local shortcut: "Make bucket public"         |        v A, B, C becomeanonymous-accessible

Особливо небезпечна звичка - відкривати весь локальний бакет лише для того, щоб не думати про доступ до окремих файлів.

Тоді локальна система більше не покаже, що якийсь екран випадково покладається на постійний публічний URL.

Я краще одразу залишу бакет приватним і змушу застосунок працювати через правильний механізм доступу.

Приватний бакет ще не замінює перевірку прав у застосунку

Сховище і права користувача вирішують різні задачі
User requests file        |        vApplication        |        | authorize?        vAllowed        |        vControlled storage access        |        vPrivate object

Ці два рівні важливо не змішувати.

Приватний бакет захищає об’єкт від постійного анонімного доступу.

А застосунок вирішує, чи конкретному користувачу взагалі дозволено отримати цей файл.

Тому навіть приватне сховище не є заміною перевірки прав.

Постійний прямий URL не повинен ставати частиною моделі приватного файла

Публічна адреса непомітно стає контрактом
Database file_url =https://storage.example/private/video.mp4  Application later assumes: "If I have the URL,I can always open the file."

Коли локально файл завжди доступний за постійною URL, цей факт легко починає проникати в інші частини системи.

URL зберігається в базі, передається у відповіді API або напряму вставляється в інтерфейс.

Після переходу до приватного production-сховища виявляється, що весь цей код спирався на властивість, якої насправді не мало бути.

Для приватного файла мені важливіший ключ об’єкта, ніж постійна публічна адреса

Приклад ключа об’єкта
private/support-video/4f88b7a1-example.mp4

Для приватного сховища мені достатньо знати, де саме лежить об’єкт.

А спосіб отримання доступу може визначатися вже в момент запиту.

Це не прив’язує запис у базі до вічно доступної публічної адреси.

Приватний файл потребує контрольованого способу читання

Приклад обмеженого в часі доступу
$url = Storage::disk('s3')    ->temporaryUrl(        $path,        now()->addMinutes(10),    );

Один із можливих механізмів - тимчасове підписане посилання.

Його важлива властивість у тому, що доступ не стає постійним.

Спочатку застосунок може перевірити право користувача, а потім видати короткоживучий доступ до конкретного об’єкта.

Цей механізм я розберу окремо далі в серії.

Локальне середовище повинно вміти правильно відмовляти в доступі

Хороше локальне середовище перевіряє не тільки успішні сценарії.

Для приватного файла мені важливо побачити і правильну відмову: прямий анонімний запит не отримує об’єкт.

Саме така невдала спроба часто доводить, що модель приватності працює.

Публічний бакет може приховати помилку до самого production

Локальний успіх може бути хибним
Local file is public    |    vUI opens direct URL    |    vEverything works  Production file is private    |    v

Я намагаюся не послаблювати security-модель лише для local

Локальне середовище не повинно копіювати всі production-налаштування буквально.

Але правила, які впливають на поведінку коду, я намагаюся зберігати.

Приватність файла якраз належить до таких правил.

Якщо локально її вимкнути, ми тестуємо не спрощену інфраструктуру, а інший сценарій доступу.

Що я перевіряю для приватного файла

Перевіряю не тільки збереження
1. Laravel can write the object   -> yes  2. Object exists in the bucket   -> yes  3. Direct anonymous access   -> denied  4. Authorized application flow   -> allowed  

Що я перевіряю перед тим, як зробити S3-файл публічним

Мій короткий список:

  • Чи файл за правилами продукту справді має бути публічним?
  • Чи я відкриваю його лише заради зручності локальної перевірки?
  • Чи production-файл буде приватним?
  • Чи прямий URL випадково не став частиною API-контракту?
  • Чи зберігаю я ключ об’єкта замість припущення про постійний публічний URL?
  • Чи застосунок перевіряє право користувача перед доступом?
  • Чи прямий анонімний запит правильно отримує відмову?
  • Чи локальна модель доступу відповідає production-моделі?

Локальна зручність не повинна змінювати правила доступу

Зробити локальний бакет публічним дуже легко.

Але якщо production-сховище приватне, така зручність може приховати неправильні припущення в коді.

Інтерфейс починає покладатися на прямі URL, API повертає постійні адреси, а перевірка прав фактично обходиться.

Я краще побачу ці проблеми локально.

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

Локальне середовище може бути простішим за production, але воно не повинно змінювати security-семантику файла.

Якщо файл має бути приватним після розгортання, я не роблю його публічним під час розробки лише для того, щоб було зручніше.