← Блог

· 13 мин читања #rust#mqtt#iot#unikernel#open source#security

urMQTT v0.3.0: шифровање диска, боот који одбија измењену слику и оно што смо сами покварили

Шта се догодило после трећег текста о urMQTT-у: шифровање диска у мировању са измереном ценом, тест који мења један бајт у језгру и показује да га loader одбија, ревизијски пакет са 889 образложених unsafe места, грешке које смо сами пронашли (и једна тврдња коју смо погрешно написали), те издање v0.3.0 уз списак онога што још није доказано, укључујући црвени CI.

Трећи текст, urMQTT после VPS-а, завршио се списком „Шта следи”: јавни демо брокер (D1), друга рунда за спор један ток, обичан пут наспрам Mosquitta на VPS-у и, „после издања”, bare metal, IOMMU, Secure Boot и шифровање диска. Овај текст каже шта је од тога урађено, шта није и шта смо успут пронашли. Шифровање диска и део Secure Boota ипак су ушли пре издања, јер су били на путу ка ревизији. Друга рунда за један ток и обичан пут наспрам Mosquitta нису урађени.

Напомена унапред остаје иста: пројекат је пред-алфа, издање циља VPS-ове са KVM-ом, а бројке мере код, тестове и одређене огледе, не зрелост у продукцији.

Како је то изгледало у времену

Од прошлог текста (1.11. у 20:41) до данас (3.11. у 02:43) у главној грани је 37 нових commitова, укупно 215. Као и раније, пројекат се гради уз Claude Code (commitови носе Co-Authored-By: Claude), а овог пута су три гране радиле паралелно (D1, H8, припрема издања), па редослед commitова није стриктно редослед рада. Бројке су израчунате истом методом као у прошлом тексту (све .rs датотеке изван vendor/ и fuzz/, #[test] функције, наслови ## ADR- у docs/DECISIONS.md) и проверене на старом commitу, који даје исте бројке као тада.

Тренутак Commit Време Rust редова Тестова ADR-ова Commitова
прошли текст d21e3ec 1.11. 20:41 112 408 926 126 178
данас f1a7fbc 3.11. 02:43 115 148 962 133 215

Раст кода је скроман (+2 740 редова, +36 тестова), јер овог пута није било много новог кода: већина рада су тестови, скрипте, документација и провере. Скрипте и JavaScript не улазе у број редова.

Шифровање диска у мировању (S7)

Шта смо планирали. Необавезно шифровање података партиције, подразумевано искључено, са измереном ценом. Шта смо изградили (ADR-240):

  • Слој испод диска. CryptDev омотава блок уређај испод Disk, па распоред, табела величина, SQLite, телеметрија и резервне копије раде над отвореним текстом и не знају за шифровање. Алгоритам је AES-256-XTS (IEEE 1619), један сектор од 512 бајта по јединици података, а не блок од 4 KiB као у плану: похрана пише у секторима, а јединица од 4 KiB претворила би сваки упис од 512 бајта у читање-измену-писање које нестанак струје може преполовити.
  • Суперблок остаје отворен. Два слота суперблока носе заставицу и проверу кључа (HMAC-SHA-256 од ознаке и UUID-а диска), никад кључ. Све остало у партицији је шифровано. Сектор који је на медијуму потпуно нула чита се као нула и не дешифрује се, па discard, никад писан простор и раст диска раде као пре.
  • Конфигурација. storage.encryption = "aes-256-xts" и storage.disk_key (датотека са 64 бајта кључа). Формат blob-а постаје 4 само кад је шифровање укључено, па се све остале слике и даље покрећу на старијим језгрима. Старији читач одбија непознату заставицу у суперблоку.
  • Без хардверског AES-а: одбијање. Језгро одбија storage.encryption на процесору без AES инструкција (или кад их [cpu] disable искључи), уместо да тихо ради спору софтверску верзију.
  • Претварање је ван мреже. Језгро никад не шифрује нити дешифрује диск при покретању. urmqtt-disk encrypt, decrypt и rekey раде то са угашеним VM-ом: запис претварања пише се први и брише последњи (док постоји, језгро одбија диск), комади од 1 MiB прво се чувају у дневнику на домаћину, па се уписује претворена копија, а суперблок се пребацује последњи. Испробано нестанком струје након сваког појединачног уписа за сва три поступка, чисто и полузаписано, са одбаченим и задржаним кешом: увек исправан диск са свим подацима након --resume.

Измерено. Пропусност XTS-а по језгру на Apple silicon (један MiB сектора у петљи): 3 109 MB/s шифровање и 3 103 MB/s дешифровање са хардверским AES-ом, наспрам 252 и 258 MB/s у софтверу. Зато софтвер не дозвољавамо. db-bench у VM-у (aarch64, HVF, 5 000 уметања у 50 трансакција, пет покретања сваке варијанте, наизменично):

op/s, пет покретања медијана
шифровање искључено 49 239, 47 401, 50 802, 53 417, 46 531 49 239
шифровање укључено 57 790, 46 173, 45 939, 52 787, 49 632 49 632

Разлика медијана је +0,8 %, а распон унутар једне варијанте (46 000 до 58 000) већи је од тога, па цена није мерљива у овом тесту. То је и очекивано: db-bench у целом покретању пише око 6 MB, што је око 2 ms AES-а, а commit чека flush, не шифру.

У Дијагностичком уносу сада пише и AES-XTS уз AES-GCM (на HVF-у језгро мери око 800 MiB/s за XTS наспрам око 560 MiB/s за AES-GCM) и показује да ли је хардверски AES доступан.

Дијагностички унос покренут у веб емулатору на aldo.tech, у картици прегледача: у панелу CRYPTO последњи ред показује AES-XTS и „hw AES”. Бројке описују емулатор (6 MiB/s), не urMQTT.
Дијагностички унос покренут у веб емулатору на aldo.tech, у картици прегледача: у панелу CRYPTO последњи ред показује AES-XTS и „hw AES”. Бројке описују емулатор (6 MiB/s), не urMQTT.

Шта XTS не даје. Нема аутентикације: измењен шифрован сектор дешифрује се у шум у блоковима од 16 бајта (CRC-ови и чистач то налазе), а неко ко може да пише по волумену може заменити или вратити читаве секторе између два снимка. Штити украдени волумен или снимак, не украдени ISO, јер је кључ у конфигурацији слике. Остаје видљиво: GPT, суперблок, UUID и који су сектори икад писани.

Шта смо успут пронашли у сопственом раду. Писањем поглавља о криптографији утврдили смо две ствари. Корак множења tweak-а у XTS-у имао је грану зависну од бита који је изведен из кључа; сада је без гране. И тврдња „проверено према IEEE 1619 вектору 10” покривала је само први шифровани блок; сад постоји и наивна референтна имплементација (аритметика u128, блок по блок) која проверава читаве секторе у оба смера на 200 насумичних кључева и сектора, а документација каже шта је стварно проверено.

Боот који одбија измењену слику (H8)

Захтев са ревизијског списка био је једноставан и до сада непроверен: ниједан тест није показивао да се измењено језгро не покреће. Pin хеша у limine.conf постојао је, али без доказа.

  • Тест који мења бајт. scripts/tamper.py окрене један бит у свакој копији језгра (или конфигурационог модула) унутар изграђеног ISO-а или raw слике, па SMOKE_ONLY=integrity показује да loader одбија: 24 провере прошле, на aarch64 UEFI, x86_64 UEFI и x86_64 BIOS, уз контроле које се покрећу. Порука одбијања чита се са серијског порта преко прекидача само за тестове. На BIOS-у порука стиже само на екран, па тај тест тврди само да се машина не покреће.
  • Уписана конфигурација loadera (ADR-250). limine enroll-config ради на BOOTAA64.EFI, BOOTX64.EFI и BIOS степену, па се и измењен limine.conf (додат унос менија или аргумент) одбија. urmqtt-config pack --iso поново уписује.
  • Secure Boot путања (ADR-251). scripts/sign-loaders.sh (пробни кључеви, потписивање, провера, OVMF похрана променљивих), mkiso.sh --sb-key и група secureboot на x86_64: потписан ISO се покреће и поновљиво гради, а непотписан, потписан погрешним кључем и измењен након потписа добијају Access Denied (10 провера прошло). Limine и даље одбија измењено језгро под Secure Bootом.
  • Правила инсталационог уноса (ADR-253). План је говорио о једној речи на командној линији, install. Продукцијско језгро заправо прихвата три (install, live, diag), а пише само install. Тест (9 провера) покреће то на три машине са празним диском чији се хеш не сме променити.
  • Тајност слике (ADR-252). Гласно упозорење кад се продукцијска слика гради изван CI-ја, и скенирање слике, логова градње и серијских логова за бајтове кључева у многим кодирањима (11 провера).
  • TPM, само ADR-254. Шта би значило запечатити кључ сервера у vTPM или TPM 2.0. Препорука: не градити за издање усмерено на VPS. Нисмо проверавали који провајдери уопште нуде vTPM.

Шта то НЕ значи (записано као H8-W1 до H8-W6 у AUDIT.md): инсталиран диск се не покреће под Secure Bootом јер његов конфигурациони модул нема хеш (ADR-217); потписан ISO се не може накнадно конфигурисати (конфигурисати па потписати); BIOS пут нема потпис; нема заштите од враћања на старију верзију; на aarch64 firmware овде не спроводи Secure Boot, па је проверен само потпис учитавача; нема пројектног кључа, па су слике издања непотписане за Secure Boot; и ниједна физичка машина није пробана.

Ревизијски пакет (H10)

docs/AUDIT.md је сада пакет који спољни ревизор може користити без питања: систем на једној страни, база којој верујемо (89 612 редова сопственог Rusta у слици, 67 повезаних туђих пакета), улази и њихови парсери, матрица заштита са тестовима (и заштите без тестова), слабости са статусом, табела сваке мрежно доступне петље и алокације са границом и тестом, упутства за градњу и тестирање, предложени опсег спољне ревизије у десет области и red-team списак са 31 нападом и резултатима. SECURITY.md има STRIDE табелу по компоненти.

  • Исправка првог текста. У тексту од 29. октобра пише да сваки unsafe блок има коментар // SAFETY: који проверава lint. То није било тачно: lint (deny(unsafe_code)) само ограничава где unsafe сме да буде, а коментаре је од H0 бележио генерисани попис, који је већ тада нашао око 30 места без образложења, а при ревизији њих 108 (јер су у међувремену дошле нове врсте места, на пример атрибути #[unsafe(no_mangle)] и link_section). Зато је попис, а не lint, оно што их сада држи на нули.
  • 889 места unsafe, свако са писаним образложењем. 108 која га нису имала (углавном #[unsafe(no_mangle)] и link_section, неколико unsafe fn) добила су коментар (написао га је AI агент, само коментари, ја сам прегледао узорак); генерисани попис у CI-ју бележи свако ново место без образложења. Образложење је тврдња коју треба прегледати, не доказ, и то је тачка 5 предложеног опсега.
  • Пет отворених налаза остаје отворено, записано: нема fuzzinga PCI и virtio парсера, нема теста који пише у табелу страница након закључавања, време извршавања P-256, X25519 и Ed25519 није мерено, нема fuzzinga намерно израђених SQLite датотека (ограничено H6), и 53 од 67 повезаних пакета су cargo-vet изузеци, не ревизије.

Грешке које смо сами пронашли

  • Тајне TLS саобраћаја нису се брисале (H0-W2, отворено од првог дана ревизије): табела снимљених тајни била је Vec који су remove и swap_remove померали и остављали копије. Сад је табела по слотовима: слот се брише на месту кад се узме, одбаци или истисне, ништа се не помера.
  • Грана зависна од кључа у XTS-у (горе).
  • log-slow-flood на x86_64 падао је („log ring full”), и на изворном коду v0.2.1. Узрок није квар, него димензионисање теста: сценарио је терао свако језгро на 25 ms, а ADR-090 га је мерио на 50 ms. На x86_64 је COM1 port-I/O по бајту ухваћен у замку, једно језгро које пише у синкове треба око 9 ms по реду, а три језгра на 25 ms траже више од секунде посла у секунди, па се прстенови пуне (замишљено преливање). Тест сада на x86_64 ради на 50 ms (прошао, 30 од 30 при покретању са основним сценаријима); на aarch64 остаје 25 ms.
  • Спајање грана је сломило старе шаблоне. Поновни упис конфигурације у H8 захтевао је завршни маркер у limine.conf, којег шаблони пре H8 немају, па су urmqtt-config pack --iso и страница градитеља одбијали свако издање пре H8, укључујући v0.2.x које страница нуди. Ухватили су га тестови градитеља у прегледачу (4 од 100 пало), а не H8-ов сопствени тест. Поправљено (шаблон без маркера конфигурише се као пре) и додат тест који пада са изворном грешком кад се поправка уклони.
  • Наша документација је погрешно тврдила да је x86_64 проверен само у емулатору. X11 је покренуо продукцијску слику на правом Contabo KVM VPS-у (4 vCPU AMD EPYC) са узајамним TLS-ом под оптерећењем. Исправљено на свим местима: x86_64 је покренут на правом KVM VPS-у при H7 commitу и у емулатору, али код додат након тога (шифровање, интегритет боота) није вртео на KVM-у, и није пробан на физичком хардверу.

D1 и понуда „ваш сервис као unikernel”

Јавни демо брокер је изграђен, али не објављен: конфигурација (live начин, ACL demo/{id}/#, обичан MQTT само за мост), скрипта која прави објављени пробни CA и демо сертификате, WebSocket-на-TCP мост (само Python стандардна библиотека, ограничења веза, брзине и величине, провера порекла), страница „Пробајте urMQTT уживо” са MQTT клијентом у обичном JavaScriptу, runbook и модел претњи. Тестирано на loopbacku и у QEMU-у (18 сценарија, 26 тестова моста). Није тестирано: стварни VPS, DNS, Caddy, контејнерска слика. Треба одвојен VPS и изричито одобрење за јавно излагање.

Уз то је на сајту објављена и понуда, Unikernel (15 језика, без цена): прилагодба вашег сервиса тако да ради као unikernel (процена изводљивости, прилагодба, пилот, подршка), са urMQTT-ом као референцом. Текст изричито наводи ограничења данашње основе: IPv4, TCP само као сервер, KVM, Rust природан, C само као изоловани радник.

Издање v0.3.0

Ознака v0.3.0 постављен је 3. новембра на commit f1a7fbc, а workflow за издање је успео. Издање је означено као pre-release и садржи развојно aarch64 језгро, ISO и слику, шаблон ISO (језгра за aarch64 и x86_64 и празна конфигурација, што urmqtt-config pack --iso и страница градитеља претварају у вашу слику), CycloneDX SBOM, SHA256SUMS и његов minisign потпис те SLSA атестације порекла. Проверено након објаве, у празном директоријуму: потпис SHA256SUMS важи, свих пет датотека се поклапа са контролним збировима, обе атестације (језгро и ISO са CycloneDX предикатом) пролазе, SBOM има 221 компоненту, а шаблон се конфигурише (pack --iso поново уписује шест копија loadera). Кључ за потпис је и даље пробни кључ.

Редослед је био важан и вреди га записати: страница градитеља ажурирана је пре ознаке (зна формат 4 и све старије шаблоне), тек је онда огледало на aldo.tech преузело издање. Огледало га је потписано и проверено навело (ok, формат конфигурације 4, sha256 шаблона једнак оном у SHA256SUMS), а жива страница га нуди као подразумевано издање и у прегледачу направи ISO за око 20 секунди. Белешке издања кажу и оно што ретко пише: CI на GitHubу није зелен (видети доле), подршка само за последњу верзију (v0.2.1 и старије престају), и да је ревизију направио сам пројекат.

На сајту смо успут замало направили сопствену штету: скрипта за објаву сајта (publish.sh) ради rsync --delete, а огледало издања припада другом кориснику, па је свака објава завршавала грешком 23 (и тако би покварила дугме „Објави” у блог админу). Поправљено изузећем за /urmqtt/releases/. Сличан налаз: deploy.sh са --delete брише страницу градитеља са генератора, па се мора вратити пре објаве.

Шта још није доказано

  • CI на GitHubу је црвен. Не само данас: последње покретање потпуног CI-ја пре ознаке имало је седам палих послова, а покретања уназад до 31. октобра углавном су црвена. Падали су: lint (shellcheck упозорења у скриптама), TLS interop (сви wolfSSL случајеви), ланац снабдевања (попис изузетака vet-exemptions.md није ажуран на Linuxу), fuzz smoke (у том послу недостају развојни сертификати), санитајзери (LeakSanitizer јавља цурење у тестовима), тестови на Linux aarch64 (један тест алата пада са „Broken pipe”) и један Kani доказ за временски точак (timer wheel) не пролази. Нисмо још разврстали шта је од тога окружење, а шта стваран недостатак; док то не учинимо, не тврдимо да је издање прошло CI.
  • Кључ за потпис је пробни, а слике издања развојне са пробним сертификатима (осим шаблона).
  • Преводе страница и овог текста на осталих 13 језика написао је AI и нису их проверили изворни говорници.
  • Шифровање диска није измерено на x86_64 (ни на KVM-у ни на физичком хардверу), и није измерена пропусност телеметрије нити време покретања са шифровањем. На страници градитеља га нема (треба дијалог за кључ).
  • Независне безбедносне ревизије нема. Опсег је написан; ревизора немамо.
  • Secure Boot је делимичан (горе), без физичке машине.
  • Bare metal, IOMMU, јавни демо брокер: није готово или није објављено.

Шта следи

  1. Разврстати и поправити CI (окружење прво, па Kani доказ и тест који пада на aarch64 Linuxу), јер издање без зеленог CI-ја не сматрамо готовим.
  2. Одлука о D1 (засебан VPS, DNS, одобрење за јавно излагање).
  3. Fuzzing PCI и virtio парсера и тест закључаних табела страница (H0-W9, H0-W10).
  4. Дијалог за кључ на страници градитеља, па шифровање постаје доступно без командне линије.
  5. После издања: bare metal (X12 до X16) и IOMMU, редоследом који зависи од инвентара хардвера тестног PC-ја.

Код је на github.com/zeljan-alduk/urMQTT; docs/PROGRESS.md је и даље једини извор истине о томе шта стварно ради, docs/PERFORMANCE.md о томе шта је измерено, а docs/AUDIT.md о томе шта још није доказано. Пред-алфа, као и раније; пријаве грешака и безбедносне пријаве по упутама у SECURITY.md добродошле су.

Коментари

Правила коментарисања

Правила коментарисања

Коментари су ту за размену искустава и питања о темама блога. Да би расправа била корисна свима, важе ова правила.

  1. Пристојност Без увреда, претњи, узнемиравања, говора мржње и дискриминације по било ком основу. Критикујте аргумент, не особу.
  2. На тему Коментар треба да се односи на објаву или на расправу испод ње.
  3. Без реклама и спама Без оглашавања, ланчаних порука и веза без контекста. Везе и слике су добродошле кад доприносе расправи.
  4. Приватност других Не објављујте туђе личне податке: имена и презимена приватних лица, адресе, телефоне, е-пошту, фотографије без пристанка.
  5. Ауторска права Објављујте само садржај који смете да делите. Код цитирања наведите извор.
  6. Један идентитет Користите један надимак и не представљајте се као неко други.
  7. Потврда е-поштом Коментар се објављује тек кад потврдите везу из е-поште. Адреса се не приказује и не користи се ни за шта друго; непотврђени коментари се бришу након 48 сати.
  8. Модерација Коментаре који крше правила скривамо или бришемо без најаве, а расправу можемо закључати. Одлуке модератора су коначне.
  9. Одговорност За садржај коментара одговара његов аутор. Коментари не одражавају ставове ALDO TECH-а.
  10. Пријава Непримерен садржај пријавите на info@aldo.tech.