Матеріал на сайті
Кейс №1. Cloudflare Crawler Surface Policy: контроль доступу ботів до URL
Серія «Великий і жахливий Cloudflare». Cloudflare Operations — policy, security and fleet management.
Як закрити для crawler-ів транзакційні URL магазину, зберегти індексацію публічних сторінок і перевірити результат у всій мережі сайтів? Цей кейс описує рішення, яке виросло з конкретного production-інциденту та клієнтської інфраструктури.
За даними авторського опису, мережа охоплювала 22 сайти із сукупною щоденною аудиторією близько 400 тисяч унікальних користувачів. Це контекст кейсу, наданий автором; агреговані показники й наведені нижче результати пілота не пройшли незалежного аудиту. Клієнтські домени, credentials і приватні snapshots до публікації не входять.
Керівні інструменти спочатку працювали на shared hosting клієнта й були написані на PHP: середовище вже існувало поряд із сайтами, а оператору не доводилося розгортати окремий сервер. Для нової самостійної установки можна використати локальне середовище, зокрема WSL. Архітектурний принцип зберігається: конфігурація та policy мають перевірюваний стан, секрети й приватні накладення залишаються локальними, а зміна Cloudflare проходить окремий керований шлях запису.
Чистовий код і приклади: IRONCREED/cloudflare, каталог Case 001. Усі інструкції нижче використовують структуру цього репозиторію.
Корисний бот на транзакційній поверхні
На WooCommerce-сайті crawler-трафік регулярно звертався до маршрутів і параметрів, які обслуговують кошик, оформлення замовлення та обліковий запис:
/cart/
/checkout/
/my-account/
?add-to-cart=
?remove_item=
?wc-ajax=Такі запити можуть запускати PHP, WooCommerce, сесію й роботу з кошиком. Серед помітних учасників був Meta-WebIndexer. Глобальне блокування цього User-Agent прибирало б також його звернення до сторінок товарів та статей, які можуть мати пошукову й посилальну цінність.
Спостереження за навантаженням відкриває два варіанти: глобальну заборону identity та обмеження конкретної поверхні; кейс обирає другий варіант.
- source: Crawler на транзакційних URL
- boundary: Глобальна блокада identity
- state: Обмеження поверхні
- state: Public content поза правилом
- incident references global
- incident derives surface
- surface produces content
Одиницею рішення стає поєднання crawler identity × request surface × profile × конфігурація зони.
Identity, клас URL, profile і локальні умови сходяться в effective policy зони.
- state: Crawler identity
- state: Request surface
- state: Profile
- state: Умови зони
- artifact: Effective policy
- identity requires effective
- surface requires effective
- profile requires effective
- zone requires effective
Три класи URL
| Поверхня | Приклади | Рішення в цьому кейсі |
|---|---|---|
| Public content | Головна, товар, категорія, стаття | Ця policy залишає запит поза своїм block-правилом |
| Transactional hard-deny | Кошик, checkout, account, операційні query-параметри | Визначений клас crawler-ів блокується на edge |
| Combinatorial | Фільтри, фасетна навігація, внутрішній пошук, комбінації параметрів | Окремий SEO-аналіз hostname перед зміною policy |
Перші шляхи в прикладах мають точну форму /cart, /checkout, /my-account і вкладені маршрути з / після назви. Схожий префікс на кшталт /cartography до цього переліку не належить. Перекладені WooCommerce slug-и, мовні префікси, кодування URL й поведінка query-параметрів потребують перевірки на конкретному сайті.
Демонстраційне правило шукає add-to-cart=, remove_item= і wc-ajax= як підрядки query. Це простий приклад, який може збігтися також зі значенням іншого параметра. Для production необхідно перевірити точність такого зіставлення. Фільтри й пошук залишаються поза загальним прикладом: корисна SEO-посадкова сторінка одного магазину може виглядати як зайва комбінація на іншому.
Identity та область дії
Використовувати весь cf.client.bot як перелік crawler-ів цього кейсу було б надто широко. Verified automation охоплює також моніторинг, перевірки безпеки та інші сервіси. Демонстрація використовує вужчі значення cf.verified_bot_category:
Search Engine Crawler
AI Search
AI Crawler
Page PreviewДо них додаються User-Agent fallbacks Meta-WebIndexer, Googlebot і Bingbot. Ці рядки можна підробити: fallback класифікує запит, але не підтверджує його походження. Станом на перевірку 6 вересня 2026 року Cloudflare зберігає ці legacy-категорії для WAF-сумісності; нова таксономія вже розрізняє поведінку ботів інакше. Verified bots
Запит блокується лише при одночасному збігу потрібної crawler identity та transactional surface.
- state: Збіг crawler identity
- state: Збіг transactional surface
- decision: Обидві умови
- boundary: Block на edge
- state: Поза цим block-правилом
- identity requires and
- surface requires and
- and rejects block: Так
- and returns other: Ні
| Запит | Результат саме Crawler Surface Policy |
|---|---|
Meta-WebIndexer → / | Не блокується цим правилом |
Meta-WebIndexer → /product/... | Не блокується цим правилом |
Meta-WebIndexer → /cart/ | Block |
Meta-WebIndexer → /checkout/ | Block |
Підсумкову відповідь можуть змінити інші правила Cloudflare та origin. Відсутність збігу з одним block-правилом не створює глобального allow.
Чому edge
Заборону можна реалізувати у WordPress, Apache, nginx або застосунку. Вибір Cloudflare у цьому кейсі переносить відсікання запиту перед origin: crawler, який потрапив до визначеної транзакційної поверхні, не повинен запускати WooCommerce. Custom Rules виконуються у власній фазі ruleset engine. Cloudflare Custom Rules
Початкова production-модель поєднувала global baseline, site profile і hostname exceptions. Переносимий інструмент узагальнює її через явну композицію policy.
Profile policies, additional policies, disabled policies та inline rules визначають logical rules; compiler перетворює їх на physical rules.
- state: Profile policies
- state: Additional policies
- boundary: Disabled policies
- state: Inline rules
- state: Logical rules
- state: Physical rules
- artifact: Effective managed state
- profile produces logical
- additional produces logical
- disabled rejects logical: Вилучити зі складу
- inline produces logical
- logical derives physical
- physical produces state
Від production workspace до окремого репозиторію
Початковий workspace на shared hosting містив policy, profiles, exceptions, scripts, backups і reports. Там виникли compiler, diff, dry-run, audit, deployment, snapshots та edge smoke. Чистовий репозиторій містить переносимий механізм, fixtures і демонстраційні конфігурації. Приватні клієнтські накладення залишаються за його межами.
Шлях у IRONCREED/cloudflare | Призначення |
|---|---|
CONSTITUTION.md, governance/PROFILE.md | Прийнятий нормативний source і профіль кандидата до випуску |
LICENSE.md | Ліцензійна карта для коду, текстів і бренду |
code-constitution/ | Сабмодуль нормативного корпусу, закріплений commit |
repository-licensing-policy/ | Сабмодуль ліцензійної policy, закріплений commit |
cases/001-crawler-surface-policy/bin/ | compile, plan, apply, audit, export, rollout |
cases/001-crawler-surface-policy/src/ | PHP API client, compiler і Ruleset Manager |
cases/001-crawler-surface-policy/examples/ | Inventory, profiles, policy та zone fixtures |
cases/001-crawler-surface-policy/tests/ | Автономні перевірки без Cloudflare-токенів |
cases/001-crawler-surface-policy/runtime/ | Локальні результати, виключені з Git |
Перенесення зберігає Case ID, ліцензії й точні версії сабмодулів. Окремий запис походження фіксує вихідне дерево та зміни. Підготовка репозиторію й автоматичні перевірки залишають остаточне рішення про випуск людині.
Межі Case 001
Інструмент керує zone-level WAF Custom Rules у фазі http_request_firewall_custom. DNS, Workers, Zero Trust, cache rules, billing, account membership та origin-конфігурація залишаються за межами цього кейсу.
Явний inventory веде до compilation, читання live state, плану зі збереженою unmanaged-частиною, окремого рішення оператора й перевірки результату.
- source: Explicit inventory
- process: Compile
- process: Read live phase
- process: Зберегти unmanaged
- artifact: Plan / diff
- human-decision: Рішення оператора
- process: Apply та snapshots
- process: Readback та audit
- inventory derives compile
- compile derives live
- live derives preserve
- preserve derives plan
- plan derives review
- review derives apply
- apply derives verify
Файли bot-example.json, country-policy.json, crawler-surface.json, named-ip-list.example.json і scanner-user-agents.json показують можливості механізму. Наприклад, commerce fixture додатково включає bot-example; якщо відтворювати саме поверхневе обмеження пілота, цю додаткову policy слід окремо переглянути й за потреби вилучити. Набір examples не є production baseline.
Inventory, profiles і винятки
Inventory явно перелічує зони, фазу, prefix володіння та бюджет:
{
"schema_version": 1,
"phase": "http_request_firewall_custom",
"managed_prefix": "[CFOPS]",
"max_physical_rules": 5,
"zones": ["domain-01.example", "domain-02.example", "domain-03.example"]
}5 — параметр прикладу, пов’язаний з умовами пілота. Його потрібно звірити з фактичними лімітами зони, а бюджет має враховувати також unmanaged rules. Доступність Custom Rules
| Profile | Policy |
|---|---|
default | scanner-user-agents |
commerce | scanner-user-agents, crawler-surface |
restricted-geo | scanner-user-agents, country-policy |
Конфігурація зони вибирає profile і змінює композицію:
{
"zone": "domain-02.example",
"profile": "commerce",
"additional_policies": ["bot-example"],
"disabled_policies": []
}Дві policy профілю та окрема policy зони утворюють три логічні складники.
- source: Commerce profile
- state: scanner-user-agents
- state: crawler-surface
- source: Zone additional_policies
- state: bot-example
- profile requires scanner
- profile requires surface
- additional produces bot
Для одного hostname можна додати inline rule, не створюючи нового загального профілю. Compiler вимагає також id і name:
{
"zone": "domain-03.example",
"profile": "restricted-geo",
"inline_rules": [
{
"id": "forum-example",
"name": "Forum hostname example",
"action": "managed_challenge",
"physical_group": "challenge",
"expression": "(http.host eq \"forum.domain-03.example\" and ip.src.country ne \"UA\")"
}
]
}Географічне обмеження тут демонструє механізм винятку. Його придатність визначає аудиторія конкретного сайту.
Logical rules і physical rules
PolicyCompiler збирає policy профілю, додає additional_policies, вилучає disabled_policies й приєднує inline_rules. Прості правила з однаковими action і physical_group об’єднуються через or в одне physical rule. Логічні файли залишаються окремими.
Кілька логічних block-policy утворюють одне physical block-rule; challenge залишається окремою групою.
- state: Логічна block policy A
- state: Логічна block policy B
- state: Логічна block policy C
- artifact: Один physical block
- state: Логічна challenge policy
- artifact: Окремий physical challenge
- scanner derives block
- surface derives block
- bot derives block
- challenge derives challenge-out
(crawler_surface_condition) or (bot_condition)Це пояснювальна формула, а не готовий Cloudflare expression. Групування потребує сумісної дії та перегляду порядку виконання. Поточний compiler призначений для показаних простих правил; він не гарантує збереження довільних action_parameters у managed policy.
Границя managed/unmanaged
Інструмент вважає своїми лише rules, у яких description починається з [CFOPS]. Новий managed block займає місце першого попереднього managed rule; якщо таких правил не було, блок додається в кінець. Unmanaged rules зберігають взаємний порядок.
Prefix визначає ownership; managed rules замінюються, решта переходить у план із збереженням взаємного порядку.
- source: Live rules
- decision: Description починається з prefix?
- process: Замінити managed block
- process: Зберегти unmanaged порядок
- artifact: Фінальний plan
- live references prefix
- prefix executes managed: Так
- prefix returns unmanaged: Ні
- managed produces plan
- unmanaged produces plan
Якщо managed та unmanaged rules були перемішані, збирання managed-частини в один блок може змінити їхнє взаємне розташування. План слід перевіряти на цю зміну. Інший writer із тим самим prefix руйнує межу ownership. Послідовний rollout одного процесу також не блокує паралельне редагування через dashboard або інший інструмент.
Локальне середовище WSL
У Windows WSL можна встановити з PowerShell:
wsl --install -d UbuntuКоманда та вимоги описані в документації Microsoft. Усередині Ubuntu потрібні Git, PHP 8.1+ і cURL:
sudo apt update
sudo apt install -y git php-cli php-curl
git clone https://github.com/IRONCREED/cloudflare.git
cd cloudflare
git submodule update --init --recursive
cd cases/001-crawler-surface-policy
php tests/run.php
cp -R examples local
export CFOPS_CONFIG_DIR="$PWD/local"
export CFOPS_RUNTIME_DIR="$PWD/runtime"local/ виключений із Git і призначений для реальних зон та приватних накладень. runtime/ містить snapshots і звіти. Доступ до сабмодулів визначають їхні власні репозиторії; автономні тести engine не потребують їх завантаження. Команду копіювання examples виконують під час першої установки, до редагування локальної конфігурації.
Створення та зберігання API tokens
У Cloudflare відкрийте профіль → API Tokens → Create Token → Create Custom Token. Для читання цього кейсу потрібні права Zone / Zone / Read для пошуку zone ID та Zone / WAF / Read для rulesets. Окремому write-token надайте Zone / Zone / Read і Zone / WAF / Edit; його використовують також для читання перед і після запису. Обмежте обидва токени потрібними зонами, а за можливості — строком дії та IP оператора. Назви й доступність permissions перевіряйте в поточному інтерфейсі. Створення токена, реєстр permissions
Для разового сеансу Bash введіть read-token без відображення та без запису його значення в історію команд:
read -rsp 'Cloudflare read token: ' CLOUDFLARE_READ_TOKEN
printf '\n'
export CLOUDFLARE_READ_TOKENWrite-token вводиться перед погодженою операцією тим самим способом у CLOUDFLARE_WRITE_TOKEN, а після неї вилучається з середовища:
read -rsp 'Cloudflare write token: ' CLOUDFLARE_WRITE_TOKEN
printf '\n'
export CLOUDFLARE_WRITE_TOKEN
# Run the reviewed apply command here.
unset CLOUDFLARE_WRITE_TOKENДля повторного використання підійде менеджер секретів або файл поза checkout, наприклад ~/.config/ironcreed/cloudflare/, із правами 700 на каталог і 600 на файл. Значення вводять редактором чи менеджером секретів; .gitignore не видаляє секрет із Git-історії, якщо він уже туди потрапив. Після витоку токен відкликають і замінюють. Environment variables передаються дочірнім процесам, тому write-token завантажують лише на час операції.
Read-token обслуговує спостереження, write-token разом із явним рішенням відкриває apply.
- source: Read token
- process: Plan / audit / export
- source: Write token
- human-decision: Review та --apply
- process: API write
- read requires observe
- write requires apply
- human validates apply
Fixtures, compile і plan
php tests/run.php перевіряє локальну compilation-модель без API tokens. Такі тести не виконують вирази на Cloudflare edge. Compile також працює локально:
php bin/compile.php --zone=domain-02.examplePlan уже використовує read-token і конфігурацію реальної зони. Замініть example.com у наступних командах на зону з власного inventory:
php bin/plan.php --zone=example.comRuleset Manager отримує zone ID, читає phase entry point, відділяє managed rules, компілює desired state, формує фінальний список, перевіряє бюджет і визначає зміну. Він нічого не записує. Порівняння виразів зберігає пробіли всередині рядків; зміна форматування може дати консервативний сигнал для перегляду.
Apply і докази
php bin/apply.php --zone=example.com --applyБез --apply CLI відмовляє ще до створення API client. За наявності прапорця використовується окремий write-token. Перед записом інструмент заново читає стан і перевіряє ліміт. За відсутності зміни результат — NO_CHANGE.
Перевірка плану й бюджету передує snapshot; після ruleset write йде readback, а невідповідність зупиняє операцію.
- process: Повторне inspect
- decision: Бюджет допустимий?
- artifact: Before snapshot
- process: Ruleset write
- process: Readback / after snapshot
- decision: Очікуваний стан?
- state: Перевірено
- boundary: Зупинити з помилкою
- inspect produces budget
- budget validates snapshot: Так
- budget rejects stop: Ні
- snapshot requires write
- write produces readback
- readback validates match
- match validates success: Так
- match rejects stop: Ні
Для зміни створюється унікальний каталог runtime/deployments/<timestamp-random>--<zone>/ з before.json, write-response.json та after.json. Якщо entry point існує, він оновлюється одним ruleset operation; інакше створюється zone ruleset відповідної фази. Rulesets API
Після запису стан читається повторно. Незнятий drift завершує операцію помилкою Post-write verification failed. Успішний HTTP response сам по собі не доводить правильності deployment. Автоматичного rollback у Case 001 немає; відновлення за snapshot є окремою перевіреною дією оператора.
Fleet rollout, audit та export
| Команда | Дія |
|---|---|
php bin/rollout.php | Послідовний dry-run усіх зон inventory |
php bin/rollout.php --apply | Послідовний запис; виняток зупиняє подальші зони |
php bin/audit.php | Порівняння live та desired state по inventory |
php bin/export.php | Локальна копія поточного стану phase entry points |
Зони проходять перевірку послідовно; зміна обмежується призначеною policy, а помилка завершує rollout.
- source: Зони inventory
- state: Поточна зона
- decision: Plan: потрібна зміна?
- human-decision: Є --apply?
- process: Запис і перевірка
- process: Наступна зона
- boundary: Помилка: stop
- inventory produces zone
- zone references plan
- plan requires review: Так
- plan returns next: NO_CHANGE
- review validates write: Так
- review returns next: Dry-run
- write produces next: Успіх
- write rejects stop: Помилка
Live state й compiled desired state сходяться в порівнянні, яке повертає збіг або розбіжність.
- source: Live state
- source: Compiled desired state
- process: Порівняння
- state: Збіг
- boundary: DRIFT: перегляд
- live references compare
- desired requires compare
- compare validates ok
- compare rejects drift
Audit повідомляє про drift і зберігає межу читання. Snapshots, звіти та експорти залишаються локальними. Здатність обійти весь inventory потрібна також для доказу того, що більшість зон не потребує змін.
Результати початкового пілота
Пілот виконувався на попередніх приватних інструментах EMBO, до виділення Case 001. Наступні числа наведено з авторського опису пілота; вони не є результатом повторного production-запуску чистового коду.
| Етап | Кількість Custom Rules |
|---|---|
| До: baseline Block, Asia Challenge, Meta-WebIndexer hotfix | 3 |
| Вилучити / зберегти / додати | 2 / 1 / 1 |
| Після перенесення функції hotfix у compiled policy | 2 із 5 доступних у пілоті |
| Smoke-запит | Зафіксований HTTP status |
|---|---|
Звичайний / | 200 |
Meta / | 200 |
Звичайний /cart/ | 200 |
Meta /cart/ | 403 |
Googlebot /cart/ | 403 |
Meta /checkout/ | 403 |
Meta /my-account/ | 403 |
Meta add-to-cart | 403 |
Meta remove_item | 403 |
Meta wc-ajax | 403 |
Підсумок авторського smoke: 10 PASS, 0 FAIL. Позначення Meta й Googlebot стосуються профілів тестових запитів; тест із підставленим User-Agent сам по собі не перевіряє реальну verified identity. Код 200 головної показує збереження доступу в цьому тесті, а не гарантію для всіх майбутніх запитів.
| Перевірка мережі після пілота | Авторський результат |
|---|---|
| Audit | 22 OK, 0 DRIFT, 0 ERROR |
| Diff | 22 OK, 0 CHANGES, 0 OVER LIMIT, 0 ERROR |
| Fleet dry-run | 22 NO_CHANGE |
За описом пілота commerce-зміна була призначена одній зоні; решта 21 зберегла попереднє очікуване налаштування. Агреговані результати показують задуманий критерій приймання. Для незалежного повторення потрібні власні конфігурації, перевірений plan і edge smoke.
Від пілота до переносимого pipeline
| Функція попередніх інструментів | Межа Case 001 |
|---|---|
policy-compiler.php | compile.php |
diff.php | plan.php |
deploy.php | apply.php --apply |
audit.php | audit.php |
complete-export.php | export.php |
rollout.php | rollout.php, окремий --apply |
validate-expression.php, crawler-surface-smoke.php | Окремі deployment-перевірки; до поточного Case 001 не перенесені |
Локальна конфігурація й fixtures ведуть до compile, plan, dry-run, рішення оператора, apply, readback та audit; site-specific edge smoke є окремою перевіркою deployment.
- source: Локальні paths і credentials
- process: Fixtures
- process: Compile
- process: Plan / diff
- process: Fleet dry-run
- human-decision: Review оператора
- process: Apply / snapshot / readback
- process: Окремий edge smoke
- process: Audit / export
- setup derives fixtures
- fixtures derives compile
- compile derives plan
- plan derives dry
- dry derives review
- review derives apply
- apply derives smoke
- smoke derives audit
Робоча дисципліна має послідовність compile → diff/plan → dry-run → apply → smoke → audit. Окремі команди перевіряють різні твердження: compilation — узгодженість моделі, plan — відмінність від live state, smoke — поведінку запитів, audit — відповідність очікуваному стану після зміни. Readback не замінює edge smoke.
Telemetry й аналітичний LLM-шар
Crawler telemetry може повідомити про новий клас трафіку на кількох hostname або відхилення окремої зони від звичного профілю. Такий висновок стає пропозицією для редакції policy. Право на security write залишається окремим повноваженням оператора.
Telemetry відкриває пропозицію; тести, plan і людське review передують explicit apply та перевірці.
- source: Telemetry / LLM observation
- artifact: Policy proposal
- process: Compile і тести
- artifact: Plan / diff
- human-decision: Людське review
- process: Explicit apply
- process: Перевірка
- observe derives proposal
- proposal derives compile
- compile derives plan
- plan derives human
- human derives apply
- apply derives verify
Аналітичний шар не отримує write-token. Це дозволяє автоматизувати спостереження, залишаючи зміну доступності сайтів явною й перевірюваною дією.
Межі та результат
Кейс вирішує обмежене завдання: виразити допустимість crawler-запиту через identity та surface, призначити policy потрібній зоні й перевірити стан решти inventory. Фасетна навігація, фільтри й пошук потребують окремого рішення; commerce profile призначається лише після перевірки конкретного сайта.
Результат — спільна система керування policy з різними effective states для окремих зон. Репозиторій відділяє повторюваний механізм від клієнтських даних: демонстраційні домени мають форму domain-N.example, приватні overlays і tokens залишаються локальними, а записи проходять окремий контрольований шлях.
Автор кейсу й вихідного production-опису: Sam Starling. Переклад, редакційна адаптація, запитання, перевірка публічної документації та підготовка чистового коду виконані за участі ШІ. Джерела платформи звірені 6 вересня 2026 року; повторний deployment у клієнтський Cloudflare в межах підготовки статті не виконувався.