До списку уроків
10 клас · Урок 11 з 16

Автоматизація Zapier/IFTTT і вхід за SSH-ключем

виконано0%
Крок 1

Дві лінії одного уроку

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

Чого навчишся за цей урок

Хід уроку

ЕтапХвЩо робимо
Шок-вступ10«Стіна атак»: спроби входу за ніч — шукаємо свій логін у списку
Пароль проти ключа20Перебір проти пари «замок-ключ» — де живе приватна частина
Майстер SSH-ключа30Генеруємо ed25519, кладемо в authorized_keys, права 700/600, вхід без пароля
Анатомія автоматизації15Конструктор «тригер → фільтр → трансформація → дія», квоти безкоштовних планів
OAuth-скоупи10Розбираємо реальний екран дозволів, оцінюємо ризик трьох скоупів
Сценарій і приймач25Збираємо Zap/аплет і hook.php, ловимо першу подію в events.jsonl
Автоперевірка5🔒 ssh_key, ssh_perms — доводимо до зеленого
Підсумок5Питання на наступний урок: «а якщо хтось знає URL вебхука?»
Разом120

Слова «SSH-ключ» і «тригер» поки нічого не означають. Розберемо їх по черзі. А «скоуп» ти вже знаєш із уроку 10. Сьогодні подивишся на нього з іншого боку — на екрані згоди чужого сервісу.

Ліній сьогодні дві, і поруч вони не випадково. Перша — вхід за ключем замість пароля. Друга — автоматизація: чужий сервіс отримує право щось робити від твого імені.

Обидві про одне й те саме. Чим ти доводиш, що це справді ти? І скільки прав віддаєш назовні?

Було: пароль

один секрет, який ти знаєш і вводиш

  • його можна підібрати перебором;
  • його видно через плече і в історії команд;
  • він однаковий на п'яти сайтах;
  • він летить на сервер при кожному вході.
Стане: пара ключів

два файли, з яких один нікуди не їде

  • приватна частина не залишає твій комп'ютер;
  • підібрати її перебором неможливо на практиці;
  • у журналі видно, яким саме ключем зайшли;
  • скомпрометований ключ прибирається одним рядком.

Що ти зробиш за 120 хвилин

  1. згенеруєш пару ed25519 у себе на комп'ютері;
  2. покладеш публічну частину в ~/.ssh/authorized_keys на сервері;
  3. виставиш права 700 на теку і 600 на файл;
  4. зайдеш на сервер без пароля й побачиш свій вхід у журналі;
  5. збереш сценарій «тригер → фільтр → дія» у Zapier або IFTTT;
  6. напишеш api/hook.php, який ловить подію в ~/data/events.jsonl;
  7. зробиш сторінку /events/ з часом останньої події.
Межа «локально / на сервер» лишається тією самою Пара ключів народжується в тебе на комп'ютері. По FTP у кабінет їдуть тільки api/hook.php, events/index.php і api/scenario.md. Файл ~/data/events.jsonl пише сам сервер, і він лежить поза ~/www. Приватний ключ на сервер не їде ніколи — це і є весь сенс уроку.
Урок 12 добудує те, що сьогодні лишиться дірявим Сьогоднішній hook.php приймає подію від будь-кого, хто знає URL. Це навмисно: наступного уроку ти додаси HMAC-підпис і мітку часу й побачиш різницю на власному журналі.
Крок 2

Твій сервер уже атакують. Просто зараз

Не тому, що ти комусь цікавий. Тому що адресу не треба знати — її знаходять суцільним перебором діапазонів

Уяви людину, яка йде вулицею і смикає всі двері підряд. Не тому, що шукала саме твої. Просто якісь виявляться незамкненими.

В інтернеті так само працюють тисячі ботів. Вони стукають у порт 22 кожної адреси підряд і пробують найпоширеніші пари «логін — пароль». Перші спроби приходять уже в першу годину після ввімкнення сервера і не припиняються ніколи.

Логіни в цих спробах завжди ті самі: root, admin, test, ubuntu, pi, user, oracle, postgres. Це стандартні акаунти, які на багатьох машинах справді існують.

сервер учителя, на проєкторі — журнал за одну ніч
$ sudo grep -c 'Failed password' /var/log/auth.log 4187 $ sudo grep 'Failed password' /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -5 1244 198.51.100.23 906 203.0.113.77 571 198.51.100.90 412 203.0.113.201 390 198.51.100.14
Два уточнення, щоб ти не спіткнувся на практиці По-перше, ці числа — з машини вчителя і показані як порядок величини: твій сервер дасть свої. По-друге, /var/log/auth.log тобі недоступний: він належить root:adm з правами 640, а sudo учням не видається. Журнал відкриває вчитель на проєкторі. Тобі доступний свій зріз — команда last, вона читає /var/log/wtmp. Усі адреси на цій сторінці — з документаційних діапазонів RFC 5737, вони навмисно нічиї.

Головне тут не кількість. Головне — що кожна з цих спроб перевіряє саме пароль.

Поки на сервері ввімкнено вхід за паролем, ця стіна має сенс. Колись хтось поставить qwerty123, і бот його знайде. Ключ вимикає всю гру: підбирати стає нічого.

Питання

Твій сервер працює три дні. Адресу ти не давав нікому, сайт не індексується, посилань на нього немає. У журналі — 4 187 рядків Failed password. Як боти взагалі дізналися, що машина існує?

Крок 3

Чому пароль програє ключу — чесно, з арифметикою

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

Вада пароляЩо це означає на практиці
його можна вгадатибот не перебирає всі варіанти — він починає зі списку вже відомих паролів; там уже є твій, якщо він не випадковий
він у тебе в голові, тому короткийлюдина не запам'ятає 40 випадкових символів, а ключ їй запам'ятовувати й не треба
він однаковий скрізьвитік із будь-якого форуму, де ти реєструвався, — і пароль до сервера відомий даром
ти його вводишчерез плече, кейлогер, чужа клавіатура, автозаповнення, скріншот, історія команд

Порівняємо чесно. Припустимо, що бот встигає 10 спроб на секунду по одному серверу. Для нього це дуже щедро.

Насправді sshd за замовчуванням дає лише 6 спроб на одне з'єднання — параметр MaxAuthTries 6. Далі боту доводиться підключатися наново, і це коштує часу.

Скільки часу займає підбір у логарифмічному масштабі Чотири смуги в логарифмічному масштабі часу: список 10 тисяч найпоширеніших паролів — 17 хвилин; словник із 14 мільйонів паролів — 17 діб; вісім випадкових малих літер — 662 роки; ключ ed25519 — близько 10 у 19 степені років. Остання смуга виходить за межі схеми. Час підбору, логарифмічна шкала секунда година рік 1000 років вік Всесвіту топ-10 000 паролів 17 хвилин словник, 14 млн паролів 17 діб 8 випадкових малих літер 662 роки ключ ed25519 10¹⁹ років — смуга не вміщується на схему А головне: ключ не підбирають «онлайн» узагалі. Сервер не дає боту нічого, що можна перевіряти на здогад.
Три верхні смуги — це 10 спроб на секунду: 10 000 ÷ 10 = 1 000 с ≈ 17 хв; 14 341 564 ÷ 10 ≈ 1,43 млн с ≈ 17 діб; 26⁸ = 208 827 064 576, поділити на 10 — це 2,09·10¹⁰ с ≈ 662 роки. Нижня смуга рахується інакше: у ключа ed25519 стійкість близько 2¹²⁸ ≈ 3,4·10³⁸ операцій, і навіть трильйон операцій за секунду дає 1,1·10¹⁹ років — приблизно у 780 мільйонів разів більше за вік Всесвіту.
Найчастіша помилка міркування «У мене 8 випадкових літер — це 662 роки, я в безпеці». Ні. Твій пароль порівнюють не з рештою 26⁸ варіантів, а зі списком. І якщо цей самий пароль ти вводив на форумі, який зламали торік, він уже в цьому списку — разом із твоєю поштою. Час підбору тоді нуль.
Питання

Однокласник рахує: «У мене пароль — 8 випадкових малих літер. Це двісті мільярдів варіантів, підбирати роками. Навіщо мені ключ?» Де діра в його міркуванні?

Крок 4

Як працює пара ключів — без магії

Один файл лишається в тебе назавжди, другий можна друкувати на футболці. Уся конструкція тримається саме на цій асиметрії

Уяви замок і ключ до нього. Замок можна повісити на видноті: він нікому нічого не відчиняє. Ключ ти носиш при собі й нікому не даєш.

Команда ssh-keygen робить одразу два файли, і вони математично пов'язані. З приватного можна порахувати публічний. З публічного приватний — ні, і саме на цьому все стоїть.

id_ed25519
приватний. Ніколи не залишає твій комп'ютер. Не копіюється, не пересилається, не заливається по FTP, не кладеться в репозиторій.
id_ed25519.pub
публічний. Один рядок тексту. Його можна віддавати, публікувати й розсилати — з нього нічого не дістати.

Що відбувається під час входу

Спочатку клієнт і сервер домовляються, як шифрувати саме з'єднання. Цей обмін зветься протоколом Діффі — Гелмана.

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

  1. клієнт каже: «я petro, хочу зайти ось цим публічним ключем»;
  2. сервер шукає цей рядок у ~/.ssh/authorized_keys. Немає — розмова закінчена;
  3. є — сервер каже: «добре, підпиши ось це» (у «це» входить ідентифікатор сеансу);
  4. клієнт підписує приватним ключем і надсилає підпис;
  5. сервер перевіряє підпис публічним ключем зі свого файлу. Збігається — пускає.
Що передається мережею при вході за ключем, а що не передається Ліворуч комп'ютер учня з приватним і публічним ключем. Праворуч сервер із файлом authorized_keys. Один раз при налаштуванні публічний ключ їде на сервер. При кожному вході сервер надсилає виклик, клієнт повертає підпис. Приватний ключ мережею не передається ніколи — цей шлях перекреслено. Твій ноутбук ~/.ssh/ id_ed25519 приватний · нікуди не їде id_ed25519.pub публічний · можна показувати що робить клієнт: підписує рядок, у який входить ідентифікатор цього сеансу VPS, спільний на 60+ акаунтів ~/.ssh/authorized_keys ssh-ed25519 AAAAC3Nz… копія публічного · права 600 що робить сервер: перевіряє підпис публічним ключем. Приватного не має приватний ключ — ніколи один раз: публічний ключ кожен вхід: «підпиши це» кожен вхід: підпис Чому це не «сервер зашифрував число, а ти розшифрував»: ed25519 взагалі не вміє шифрувати. Він уміє тільки підписувати й перевіряти підпис.
Мережею їде підпис, а не ключ. Підпис прив'язаний до ідентифікатора саме цього сеансу, тому перехоплений підпис не можна використати вдруге на іншому підключенні.
Пояснення, яке ти зустрінеш в інтернеті, і чому воно неточне Часто пишуть: «сервер шифрує випадкове число твоїм публічним ключем, а ти розшифровуєш приватним». Для старих RSA-ключів таку схему справді можна собі уявити. Але ed25519 — алгоритм підпису, він не має операції шифрування взагалі. У реальному SSH сервер надсилає дані для підпису, а не шифротекст (RFC 4252, розділ 7). Різниця не косметична: саме тому в підпис входить ідентифікатор сеансу — щоб чужий сервер не міг переслати твій підпис далі.
Питання

Ти виклав файл id_ed25519.pub у публічний репозиторій на GitHub — щоб не загубити. Наскільки це погано?

Крок 5

Генеруємо пару: одна команда і три рішення

Команда коротка, але кожен її параметр — це рішення, яке потім не переграєш

у себе на комп'ютері — Git Bash, PowerShell, macOS, Linux
$ ssh-keygen -t ed25519 -C "ТВІЙ-ЛОГІН@notebook 2026-09" Generating public/private ed25519 key pair. Enter file in which to save the key (/home/ТВІЙ-ЛОГІН/.ssh/id_ed25519): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/ТВІЙ-ЛОГІН/.ssh/id_ed25519 Your public key has been saved in /home/ТВІЙ-ЛОГІН/.ssh/id_ed25519.pub The key fingerprint is: SHA256:9k2vQ0r7hLpX3mBd1sYcTn8WqE5uZaJfR6oKgN4iVxU ТВІЙ-ЛОГІН@notebook 2026-09
-t ed25519
тип ключа. Сучасний стандарт: коротший за RSA, швидший і без вибору довжини — там її просто немає, вона фіксована. Якщо старий сервер його не приймає, запасний варіант — -t rsa -b 4096.
-C "…"
коментар у кінці публічного ключа. Він ні на що не впливає технічно, але саме за ним ти через рік зрозумієш, який рядок у authorized_keys звідки. Пиши «хто@яка машина + місяць».
passphrase
парольна фраза на сам файл ключа. Порожня — зручно, але тоді вкрадений файл одразу працює. З фразою вкрадений файл треба ще й розшифрувати.
Порожня парольна фраза — це не «неправильно», це компроміс Із фразою кожен вхід просить її ввести — і учні швидко починають ставити фразу «1234», що гірше за її відсутність. Розумний варіант: непорожня фраза плюс ssh-agent, який тримає розшифрований ключ у пам'яті до перезавантаження. Тоді фразу вводиш раз на день. Формат нового ключа шифрує файл через bcrypt-KDF, за замовчуванням 16 раундів — це навмисно повільно, щоб перебір фрази коштував дорого.

Як відрізнити публічний файл від приватного за один погляд

Анатомія рядка публічного ключа і початок приватного файлу Публічний ключ — один рядок із трьох частин: тип ssh-ed25519, база64 з тілом ключа, коментар. Приватний файл — багато рядків, що починаються з BEGIN OPENSSH PRIVATE KEY. id_ed25519.pub — один рядок, приблизно 100 символів ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP9tQ2vXkR4LmB7yWc1sNdZ0gHfE petro@notebook 1 2 3 1 · тип ключа: ssh-ed25519 — однаковий у всіх ключів цього типу 2 · тіло ключа в base64. Перші 25 символів теж однакові в усіх: AAAAC3NzaC1lZDI1NTE5AAAAI 3 · коментар із -C: єдина частина, яку пишеш ти сам id_ed25519 — багато рядків, починається так -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz c2gtZWQyNTUxOQAAACD9bUNr15EeC5ge8lnNbHXWdIB3xAAAAJj4kK3O+JCtzgAA Якщо в authorized_keys потрапив рядок «-----BEGIN» — ти вставив приватний ключ. Це аварія, а не помилка форматування.
Публічний ключ — один рядок і три поля через пробіл. Приватний — блок із заголовком BEGIN OPENSSH PRIVATE KEY. Ключі на схемі — вигадані, справжній base64 тут не потрібен.
перевір, що дивишся саме на публічний ключ
$ ssh-keygen -lf ~/.ssh/id_ed25519.pub 256 SHA256:9k2vQ0r7hLpX3mBd1sYcTn8WqE5uZaJfR6oKgN4iVxU ТВІЙ-ЛОГІН@notebook (ED25519)
Ключ, згенерований на сервері, — це вже не ключ Якщо ти зайшов по паролю й запустив ssh-keygen там, приватна частина народилася на спільній машині. Там живуть ще 60 акаунтів, а сам файл потрапляє в резервні копії сервера. Уся ідея «приватне не залишає мій комп'ютер» зникає в цю ж секунду. Генеруй локально — і тільки локально.
Питання

Учень зайшов на сервер по паролю, там виконав ssh-keygen, додав ключ у authorized_keys і тепер заходить без пароля. Формально працює. Що з цим не так?

Крок 6

Кладемо публічний ключ на сервер і виставляємо права

Файл ~/.ssh/authorized_keys — це просто список рядків. Один рядок — один дозволений ключ

Найпростіший спосіб — команда ssh-copy-id. Вона сама створить теку, сама допише рядок у кінець файлу і сама поставить права. Пароль ти вводиш тут востаннє.

Git Bash, macOS, Linux — найкоротший шлях
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub ТВІЙ-ЛОГІН@91.219.61.4 Number of key(s) added: 1 $ ssh -o PasswordAuthentication=no ТВІЙ-ЛОГІН@91.219.61.4 ТВІЙ-ЛОГІН@vps:~$

Другий рядок тут важливіший за перший. Ключ -o PasswordAuthentication=no забороняє клієнту питати пароль.

Без нього буває так. Ключ насправді не спрацював, ssh тихо перейшов на пароль, ти його ввів — і вирішив, що все налаштовано. Ця команда не дає себе обдурити.

У Windows PowerShell немає ssh-copy-id Клієнт ssh у Windows 10/11 є, а от ssh-copy-id до нього не входить. Роби вручну — одним рядком, який усе створить і одразу виставить права.
Windows PowerShell — установка публічного ключа вручну
$pub = Get-Content "$env:USERPROFILE\.ssh\id_ed25519.pub" -Raw
ssh ТВІЙ-ЛОГІН@91.219.61.4 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '$pub' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

Тут && усередині лапок виконує вже віддалений bash на сервері, а не PowerShell — тому воно працює. Пам'ятай різницю: у самому PowerShell 5.1 && між командами не існує, там пишуть команда; if ($?) { наступна }.

Права: що справді ламається, а що ні

Навколо прав на ~/.ssh ходить багато неточностей. Розберемо чесно, бо ти це перевіриш і побачиш розбіжність.

Права на файли SSH і що ламається при кожній помилці Таблиця: тека .ssh 700, authorized_keys 600, приватний ключ 600, публічний 644. Далі три типові помилки: 777 на теці — сервер відмовляє через StrictModes; 644 на приватному ключі — клієнт відмовляється його використовувати; ключ у неправильній теці — сервер його просто не бачить. Як має бути 700 ~/.ssh/ на сервері й у тебе 600 ~/.ssh/authorized_keys на сервері · список дозволених ключів 600 ~/.ssh/id_ed25519 тільки в тебе · приватний 644 ~/.ssh/id_ed25519.pub тільки в тебе · публічний Що станеться, якщо помилитися chmod 777 ~/.ssh Сервер відмовить. StrictModes бачить право запису для групи й інших і не довіряє файлу. У журналі: bad ownership or modes chmod 644 id_ed25519 Відмовить клієнт. Це вже не сервер і не тека, а твій ssh про приватний ключ: UNPROTECTED PRIVATE KEY FILE! chmod 755 ~/.ssh Технічно працює. StrictModes лається на право ЗАПИСУ (775, 777), а не на читання. Але 700 — вимога критерію ssh_perms.
Права 755 на ~/.ssh вхід не ламають — і ти це перевіриш. Ми все одно вимагаємо 700: на машині, де 60+ акаунтів, немає причини давати сусідам навіть право читати список твоїх ключів. Це гігієна й вимога критерію, а не технічна відмова.
Чому 777 справді неприпустимо Право запису для «інших» на ~/.ssh — це дозвіл будь-якому учневі на цьому сервері. Він може дописати свій публічний ключ у твій authorized_keys — і заходити під тобою. Сервер це розуміє й відмовляється працювати з такою текою взагалі. Саме тому chmod -R 777 ~ «щоб FTP не лаявся» ламає вхід за ключем.
на сервері: привести до порядку й перевірити
$ chmod 700 ~/.ssh $ chmod 600 ~/.ssh/authorized_keys $ stat -c '%a %n' ~/.ssh ~/.ssh/authorized_keys 700 /home/ТВІЙ-ЛОГІН/.ssh 600 /home/ТВІЙ-ЛОГІН/.ssh/authorized_keys
Питання

Учень виконав chmod -R 777 ~, «щоб FTP не лаявся на права». Вхід за ключем після цього перестав працювати: сервер знову питає пароль, хоча authorized_keys на місці й рядок у ньому правильний. Чому?

Крок 7

Симулятор входу: що сервер перевіряє на кожному кроці

Обери спосіб входу і ситуацію. Сторінка покаже рукостискання крок за кроком і те, що після нього залишиться в auth.log

Симулятор SSH-входу

Чим заходять
Налаштування сервера
Хто і в якій ситуації підключається
    що записав сервер у /var/log/auth.log
    що побачив той, хто підключався

    Значення maxretry = 5, findtime = 10m і bantime = 10m у лічильнику — це значення з поставки fail2ban (секція [DEFAULT] у jail.conf). На реальному сервері їх міняють у jail.local.

    Перемикач PasswordAuthentication — це не твоє налаштування Вимикає вхід за паролем адміністратор сервера, і sudo вам не видано. Тут він для того, щоб ти побачив наслідок. Коли пароль вимкнено, бот отримує відмову ще до першої спроби вгадування. А разом із ботом — і ти, якщо забув поставити ключ.
    Питання

    Учень скопіював у authorized_keys вміст файлу id_ed25519 — без .pub. Що станеться при вході і що з цим тепер робити?

    Крок 8

    Читаємо auth.log: анатомія рядка

    Один рядок журналу відповідає на п'ять питань одразу: коли, хто, звідки, чим і чи пустили

    Розбір рядка auth.log про успішний вхід за ключем Рядок ділиться на частини: дата й час, ім'я сервера, назва служби з номером процесу, рішення Accepted publickey, логін, адреса й порт клієнта, версія протоколу, тип ключа й відбиток. Рядок довгий, тому на схемі його показано у два рядки. Sep 2 08:15:33 vps sshd[26210]: Accepted publickey for petro from 192.0.2.44 port 50188 ssh2: ED25519 SHA256:9k2vQ0r7… 1 2 3 4 5 6 7 1 · коли: час сервера, а не твій часовий пояс 2 · на якій машині це сталося 3 · служба і номер процесу: усі рядки одного підключення мають той самий номер 4 · рішення і метод: Accepted / Failed, publickey / password 5 · чий акаунт намагалися відкрити 6 · звідки: адреса й порт того, хто підключався 7 · відбиток ключа: видно, ЯКИМ саме ключем зайшли. Пароль такого сліду не лишає ssh-keygen -lf ~/.ssh/id_ed25519.pub — цей відбиток має збігтися з тим, що в журналі
    Відбиток у журналі — це та сама сума SHA256:…, яку показує ssh-keygen -lf. Тому після компрометації ти зможеш перевірити, чи заходив хтось саме тим ключем.

    Найчастіші рядки й що вони означають

    РядокЩо сталося
    Accepted publickey for …вхід за ключем відбувся; далі — відбиток ключа
    Accepted password for …вхід за паролем відбувся. На сервері, де мали лишитися тільки ключі, це тривожний рядок
    Failed password for …акаунт існує, пароль не підійшов
    Invalid user admin from …такого акаунта на сервері немає взагалі — типова робота бота
    Connection closed by authenticating user … [preauth]відключилися, не пройшовши автентифікацію: вичерпали спроби або передумали
    Authentication refused: bad ownership or modes for directory …права на теку або файл дозволяють запис чужим; сервер ключ не читає
    NOTICE [sshd] Ban …це вже не sshd, а fail2ban: адресу заблоковано

    Тренажер: розбери фрагмент журналу

    Перед тобою 20 рядків із однієї ночі. Познач кожен рядок: сервер пустив, сервер не пустив, або рядок узагалі не про вхід. Клікай по рядку, щоб перемкнути мітку.

    Читач журналу

    мітки по колу:
    Розмічено 0 із 20
    Питання

    У журналі за ніч — 3 000 рядків Failed password for root з десятка адрес. І один рядок: Accepted password for petro from 203.0.113.77 о 03:40. Що з цього тривожніше й чому?

    Питання

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

    Крок 9

    Ключ витік. Що робити і чому «змінити пароль» не допоможе

    Пароль — це те, що ти знаєш. Ключ — це файл. Файл не міняють, файл відкликають

    Коли витікає пароль, ти вводиш новий, і старий перестає діяти в ту саму секунду. Бо пароль на сервері один, і перевіряється він за одним записом.

    Із ключем усе інакше. Сервер пускає кожен рядок з authorized_keys. Поки твій втрачений рядок там лежить, він працює. Зміна пароля на це не впливає ніяк.

    Що не працює
    • змінити пароль акаунта — доступ дає ключ, а не пароль;
    • видалити файл на чужій машині — копії могли лишитися;
    • «я поставлю на нього парольну фразу» — вона ставиться на твій файл, а не на вкрадений;
    • «воно ж на шкільному комп'ютері, там свої» — там кожен урок сидить інший клас.
    Порядок дій
    1. прибрати рядок цього ключа з authorized_keys — на всіх серверах;
    2. згенерувати нову пару локально, поставити новий публічний ключ;
    3. перевірити вхід новим ключем, тільки потім видаляти старий;
    4. подивитися журнал: чи заходив хтось цим відбитком;
    5. перевірити, де ще лежав цей ключ: GitHub, інші VPS, хмара.
    відкликання ключа: знайти рядок за коментарем і прибрати його
    $ grep -n 'school-pc' ~/.ssh/authorized_keys 2:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK7dP… ТВІЙ-ЛОГІН@school-pc $ cp ~/.ssh/authorized_keys ~/.ssh/authorized_keys.bak $ grep -v 'school-pc' ~/.ssh/authorized_keys.bak > ~/.ssh/authorized_keys $ chmod 600 ~/.ssh/authorized_keys $ wc -l ~/.ssh/authorized_keys 1
    Спочатку новий ключ, потім видалення старого Якщо прибрати єдиний робочий ключ, не перевіривши новий, ти залишишся без входу — і по паролю теж, якщо його вимкнено. Правило: відкрий другу сесію й не закривай її, поки нова пара не спрацювала в першій.

    І окремо про парольну фразу. Вона не рятує ключ, але дає час. Викрадений файл без фрази — це готовий доступ прямо зараз.

    З фразою зловмисникові доведеться її підбирати. Формат ключа навмисно робить кожну спробу повільною. Ключ ми однаково міняємо — просто без паніки.

    Питання

    Ти скопіював свій приватний ключ id_ed25519 на шкільний комп'ютер у кабінеті, щоб зайти на сервер прямо з уроку. Усе спрацювало. Що тепер треба зробити і чому саме це?

    Питання

    Той самий ключ був із парольною фразою. Це щось міняє в порядку дій?

    Крок 10

    Автоматизація: тригер → фільтр → дія

    Zapier і IFTTT не вміють нічого, чого не вміє звичайний скрипт. Вони вміють інше: чекати подію цілодобово замість тебе

    Будь-який сценарій автоматизації складається з трьох частин. Перша — тригер: подія, на яку сервіс чекає цілодобово. Наприклад, хтось надіслав твою форму.

    Друга — фільтр: умова, за якою більшість подій відсівається. Без нього тобі прилетить кожна дрібниця, і журнал стане непридатним.

    Третя — дія: що зробити з подіями, які пройшли фільтр. Наприклад, надіслати їх на твій сервер.

    Шлях події від зовнішнього сервісу до сторінки учня Тригер — надіслано форму. Фільтр — клас дорівнює 10А, інші події відкидаються. Дія — POST на hook.php. Далі сервер учня: hook.php записує рядок у events.jsonl поза веб-корінням, а сторінка events читає його і показує останні події. Зовнішній сервіс 1. Тригер нова заявка у формі 40 за день 2. Фільтр клас = 10А і гурток є лишилось 12 3. Дія POST JSON на твій URL 12 запитів 28 подій відкинуто фільтром — і це нормально Твій сервер ~/www/api/hook.php ~/data/events.jsonl Куди це видно ~/data/events.jsonl поза веб-корінням по HTTP не віддається тут лежать самі дані ~/www/events/index.php читає журнал і показує останні 10 подій і час тут видно, що конвеєр живий
    Числа 40 / 12 / 28 — приклад для одного дня, щоб було видно роботу фільтра. Твої будуть свої: саме їх ти запишеш у scenario.md.

    Квоти: чому сценарій замовкає 12 числа

    Безкоштовні тарифи не «повільніші» — вони закінчуються. І закінчуються тихо: сценарій просто перестає спрацьовувати.

    Лист про це або приходить на пошту, яку ти не читаєш, або не приходить узагалі. Дізнаєшся ти сам, коли помітиш порожній журнал.

    Безкоштовний тарифZapierIFTTT
    скільки сценаріївнеобмежено2 аплети
    скільки спрацювань100 задач на місяцьбез обмеження запусків
    кроків у сценарії2 (тригер + дія)1 дія
    як швидко реагуєопитування раз на 15 хвстандартна швидкість
    вебхук на свій сервернедоступний: Webhooks — платний застосунокнедоступний: сервіс Webhooks лише в Pro
    Головна практична пастка цього уроку Ані Zapier, ані IFTTT на безкоштовному тарифі не надішлють запит на твій hook.php: в обох вебхуки винесені в платні плани. Тому сценарій ти все одно будуєш і показуєш у сервісі по-справжньому: тригер, фільтр, історію запусків. А самі події на свій сервер надсилаєш через Google Apps Script. Ти вже вмієш його з уроку 3, і робить він те саме — безкоштовно.
    Apps Script — план Б: той самий «тригер → фільтр → дія», але безкоштовно
    function onFormSubmit(e) {
      var v = e.namedValues;
    
      // 2. Фільтр: пропускаємо далі лише свій клас
      if (String(v['Клас']) !== '10А') return;
    
      // 3. Дія: POST на власний приймач
      UrlFetchApp.fetch('http://91.219.61.4/s/ТВІЙ-ЛОГІН/api/hook.php', {
        method: 'post',
        contentType: 'application/json',
        payload: JSON.stringify({
          source: 'apps-script',
          type: 'form',
          title: String(v['Назва гуртка'])
        }),
        muteHttpExceptions: true
      });
    }

    Тарифи змінюються — таблицю вище перевірено у вересні 2026 за сторінками zapier.com/pricing і ifttt.com/plans. Перед уроком відкрий їх сам: якщо число інше, вір сайту, а не сторінці.

    Питання

    Твій сценарій відпрацював сотий раз 12 числа й до кінця місяця мовчить. Коли ти про це дізнаєшся?

    Крок 11

    Екран згоди: скільки прав ти щойно віддав

    Коли ти тиснеш «Дозволити», зовнішній сервіс отримує токен і зберігає його в себе. Далі все залежить від того, що цим токеном можна

    На екрані згоди написано, що саме сервіс проситиме робити. Цей перелік дозволених дій ти вже зустрічав на уроці 10 — це скоуп (scope). Там ти сам звужував його до read-only. Тут скоуп просить чужий сервіс, а звужуєш його все одно ти.

    Сервіси люблять просити з запасом. Один широкий скоуп простіший у розробці, ніж три вузьких. Твоє завдання — прочитати рядки й порівняти їх із тим, що сценарію справді потрібно.

    Що просятьЩо це насправді дозволяєОцінка
    drive.file
    «доступ до файлів, які створив цей застосунок»
    бачить лише свої файли й ті, які ти сам йому відкрив. Решта Диска для нього не існуєрозумно
    spreadsheets.readonly
    «перегляд твоїх таблиць»
    читає всі таблиці, але нічого не змінює й не видаляєприйнятно
    drive
    «перегляд, зміна і видалення всіх файлів на Диску»
    усе. Фотографії, домашні роботи, документи батьків у спільних текахнадлишково
    gmail.readonly
    «читання всієї пошти»
    уся пошта, включно з листами відновлення пароля з інших сайтівнебезпечно

    Правило просте: сервіс не повинен мати більше прав, ніж потрібно його сценарію. Це той самий принцип найменших привілеїв, що й на уроці 10.

    Різниця лише в тому, кого він тепер стосується. Там ішлося про твій власний скрипт, тут — про чужу компанію на іншому кінці світу.

    Три причини, чому «дам повний доступ, так простіше» — погана ідея

    1. Токен лежить не в тебе. Він у базі сервісу. Зламають сервіс — зламають і твій Диск, хоча ти нічого не робив;
    2. Ти не побачиш зловживання. Сервіс, який має право читати все, читає все тихо: ніяких листів «ваш файл відкрито»;
    3. Дозвіл не має терміну. Ти забудеш про сценарій через місяць, а токен житиме, поки ти вручну не відкличеш його в налаштуваннях акаунта.
    Що зробити просто зараз Відкрий у своєму акаунті сторінку «застосунки з доступом до акаунта» й подивись список. Там майже напевно є те, чим ти не користувався рік. Відкликати доступ — одна кнопка, і це варто робити раз на семестр.
    Питання

    Сервіс автоматизації на екрані згоди просить «перегляд, зміна і видалення всіх файлів на Google Диску». Твоєму сценарію треба читати одну таблицю з результатами опитування. Що робити?

    Крок 12

    Свій приймач: hook.php і журнал подій

    Зовнішній сервіс присилає JSON. Твоє завдання — прийняти, обрізати, записати рядок і не впасти на будь-якому смітті

    Формат журналу — JSONL: один рядок дорівнює одному повному JSON-об'єкту. Такий файл легко дописувати в кінець і легко читати частинами.

    Великий JSON-масив довелося б щоразу перечитувати цілком. Тут — ні: новий рядок просто лягає в кінець.

    ~/www/api/hook.php — версія уроку 11, ще без підпису
    <?php
    declare(strict_types=1);
    
    // 1. Приймаємо тільки POST і тільки розумного розміру
    $raw = file_get_contents('php://input');
    if ($_SERVER['REQUEST_METHOD'] !== 'POST' || strlen($raw) > 8192) {
        http_response_code(405);
        exit('method not allowed');
    }
    
    // 2. Битий JSON не повинен валити сервіс
    $data = json_decode($raw, true);
    if (!is_array($data)) {
        http_response_code(400);
        exit('bad json');
    }
    
    // 3. Беремо лише відомі поля й обрізаємо довжину
    $event = [
        'ts'     => gmdate('c'),
        'source' => substr((string)($data['source'] ?? 'unknown'), 0, 40),
        'type'   => substr((string)($data['type']   ?? 'event'),   0, 40),
        'title'  => substr((string)($data['title']  ?? ''),        0, 200),
    ];
    
    // 4. Пишемо ПОЗА веб-корінням: ~/www/api/ -> на два рівні вгору -> ~/data/
    $path = dirname(__DIR__, 2) . '/data/events.jsonl';
    $line = json_encode($event, JSON_UNESCAPED_UNICODE) . "\n";
    file_put_contents($path, $line, FILE_APPEND | LOCK_EX);
    
    http_response_code(200);
    header('Content-Type: application/json');
    echo json_encode(['ok' => true]);
    dirname(__DIR__, 2)
    піднімає шлях на два рівні від теки скрипта: ~/www/api~/www~. Так журнал гарантовано лежить поза www, і не залежить від того, з якої теки запущено PHP.
    ?? і substr
    подія приходить ззовні, отже вона недовірена: беремо лише відомі ключі, підставляємо значення за замовчуванням і обрізаємо довжину. Уся логіка уроку 4 діє й тут.
    LOCK_EX
    дві події, що прийшли одночасно, не змішають свої рядки в один.
    gmdate('c')
    час у UTC в форматі ISO 8601. Час сервера, а не автора події — так рядки завжди йдуть по порядку.
    Чого тут навмисно немає Немає перевірки, хто надіслав подію. Хто знає URL — той пише в твій журнал. Сьогодні це видно неозброєним оком, а на уроці 12 ти закриєш дірку HMAC-підписом. Тому events.jsonl вже зараз лежить поза ~/www: щоб хоча б читати його не міг ніхто сторонній.
    ~/data/events.jsonl — фрагмент: один рядок на одну подію
    {"ts":"2026-09-02T08:41:07+00:00","source":"apps-script","type":"form","title":"Заявка на гурток робототехніки"}
    {"ts":"2026-09-02T09:15:52+00:00","source":"apps-script","type":"form","title":"Заявка на гурток 3D-друку"}
    {"ts":"2026-09-02T11:02:30+00:00","source":"ifttt","type":"weather","title":"Завтра дощ: нагадати про парасольку"}
    {"ts":"2026-09-02T12:47:11+00:00","source":"unknown","type":"event","title":""}

    Останній рядок — це чийсь порожній POST на твою адресу. Він валідний, тому й записався.

    Такі рядки варто рахувати окремо. Їхня кількість показує, наскільки твій приймач відкритий цілому світу.

    тестовий запит — Git Bash, macOS, Linux
    $ curl -s -X POST http://91.219.61.4/s/ТВІЙ-ЛОГІН/api/hook.php -H 'Content-Type: application/json' -d '{"source":"test","type":"manual","title":"перша подія"}' {"ok":true} $ tail -n 1 ~/data/events.jsonl {"ts":"2026-09-02T13:05:44+00:00","source":"test","type":"manual","title":"перша подія"}
    Windows: пиши curl.exe, а не curl У Windows PowerShell 5.1 curl — це псевдонім Invoke-WebRequest, і ключі -X, -H, -d там означають зовсім інше. Повне ім'я curl.exe викликає справжній curl, який є у Windows 10/11.
    ~/www/api/scenario.md — кістяк, який ти заповниш своїми числами
    # Сценарій автоматизації
    
    **Кейс.** Навіщо це взагалі потрібно, одним реченням.
    
    | Частина | Що саме |
    |---|---|
    | Тригер | нова відповідь у формі «Запис у гурток» |
    | Фільтр | поле «Клас» = 10А, поле «Гурток» не порожнє |
    | Дія | POST JSON на http://91.219.61.4/s/<логін>/api/hook.php |
    | Сервіс | Zapier / IFTTT / Apps Script — що саме використано |
    
    ## Квоти мого тарифу
    - ліміт: ... задач або аплетів
    - як часто опитується тригер: ...
    - що буде, коли ліміт вичерпається: ...
    
    ## Як цей сценарій може зламатися тихо
    1. ...
    2. ...
    
    ## Як я про це дізнаюсь
    Сторінка /events/ показує час останньої події. Якщо він старший за добу — ...
    Питання

    Ти надіслав на свій hook.php тіло {{{ замість JSON. Що має статися правильно написаним приймачем?

    Крок 13

    Практика: ключ, сценарій, приймач

    Кроки 1—9 — у себе на комп'ютері й на сервері по SSH. Кроки 10—11 — у сервісі автоматизації. По FTP їдуть три файли: api/hook.php з кроку 12, events/index.php з кроку 15 і api/scenario.md з кроку 16. Галочки зберігаються

    Одне правило, яке не має винятків Файл id_ed25519 (без .pub) не потрапляє нікуди. Ні на сервер, ні в ~/upload/, ні в репозиторій. Ні у ваш класний чат, ні на флешку, яку ви передаєте по колу. Якщо він там опинився — ключ вважається скомпрометованим, і ти робиш крок 9 цієї сторінки.
    Крок 14

    Автоперевірка

    Чекер заходить на сервер твоїм ключем, дивиться права, шукає приватний ключ і надсилає тестову подію. Спроби не обмежені

    Демонстраційний режим: результат згенеровано для показу. На сервері ця кнопка викликає POST /api/check, який запускає /opt/lessons/11/check.sh від імені учня.

    Що вчитель дивиться на твоєму екрані

    Крок 15

    Шкала виконання

    Це те, що бачить учитель, коли виставляє оцінку.

    СкладникВагаРезультат
    Виконано
    0%
    Рекомендований бал за 12-бальною

    Шість речень, які варто запам'ятати

    Де найчастіше помиляються

    Домашнє завдання

    Опиши в ~/www/api/scenario.md два способи, якими твій сценарій може зламатися тихо. Тобто перестати працювати так, що помилки ніхто не побачить. Для кожного напиши, як саме ти про це дізнаєшся.

    Підказки, куди дивитися. Скінчилася квота тарифу. Токен доступу відкликали або в нього минув термін. Сервіс перейменував поле у формі. Твій сервер відповів 500, а сервіс не повторив спробу.

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

    Джерела

    Звідки взяті числа й формулювання

    Усі розрахунки часу підбору з кроку 3 можна перевірити калькулятором: припущення про швидкість написані просто на схемі

    Повний список із поясненнями, що звідки взято і що є ілюстративним прикладом, — у файлі urok-11-джерела.md поруч із цією сторінкою.