Feat/m5 device registration M5: Cihaz kaydı ve kurulum — POST /devices, install.sh, systemd - #2
Merged
Merged
Conversation
Dashboard'un çağırdığı ilk yazma ucu. Agent uçlarından farklı olarak cihaz anahtarıyla değil kullanıcı JWT'siyle korunur; cihazın anahtarı burada doğar. auth.py — ikinci kimlik modu (require_user): - Supabase access token'ı ES256 ile imzalanır. İmza, projenin JWKS ucundan çekilen AÇIK anahtarla doğrulanır; paylaşılan sır (HS256) kullanılmaz, yani SUPABASE_JWT_SECRET diye bir ortam değişkeni hiç tanımlanmaz. - Açık anahtarlar süreç belleğinde tutulur ve yalnızca TANINMAYAN bir `kid` görüldüğünde yenilenir. İki yenileme arasına 60 saniyelik alt sınır konuldu: uydurma `kid`'lerle gelen istek seli, collector'ı Supabase'e istek üretmeye zorlayamasın. - JWKS çekilemediğinde 401 değil 503 döner — token geçerli olabilir, sorun doğrulayamamaktır; istemcinin oturumunu geçersiz sayması yanlış olurdu. - `exp`, `iss`, `aud`, `sub` zorunlu tutulur ve `role` alanının `authenticated` olduğu ayrıca kontrol edilir: imzası doğru bir servis anahtarı bir son kullanıcıyı temsil etmez. - Reddedilen token loglanmaz, yalnızca hatanın türü yazılır. endpoints_device.py — POST /devices: - Anahtar üretilir, veritabanına yalnızca SHA-256 özeti yazılır; düz hali tek seferlik yanıtta döner ve hiçbir yerde saklanmaz. - Satırın `account_id`'si gövdeden değil doğrulanmış token'dan alınır; kullanıcı cihazı başka bir hesabın altına açamaz. - Gövde `extra="forbid"`: `account_id` ya da `key_hash` yazmayı deneyen istek sessizce yutulmaz, 422 ile reddedilir. - Aynı hesapta aynı ad çakışırsa 409 (kullanıcının düzeltebileceği kalıcı bir durum), diğer veritabanı arızalarında 503. Postgres'in kendi mesajı istemciye geçirilmez. hashing.py: `generate_device_key` — `secrets.token_urlsafe(32)` ile üretilen yüksek entropili anahtar, `tbx_live_` ön ekiyle. supabase_client.py: `insert_device` eklendi. `insert_rows` bu iş için kullanılamaz — o metot çakışan satırı sessizce atlar, cihaz kaydında ise çakışma çağırana bildirilmesi gereken bir hatadır. `SupabaseError` artık Postgres hata kodunu taşıyor (409/503 ayrımı buna dayanıyor). requirements: pyjwt + cryptography (ES256 doğrulaması saf Python'da yapılamaz).
Kurulumun son adımı tek soruyu cevaplar: bu config ile collector'a ulaşılıyor ve anahtar kabul ediliyor mu? Hiçbir şey yazmaz, hiçbir durum değiştirmez. core/verify.py: - Yalnızca HTTP 200 "bağlandı" sayılır. Gevşek bir kontrol (2xx ise tamam, 401 değilse tamam) sessizce yanlış olurdu: 404 alan kullanıcı "✓ Kuruldu ve bağlandı" görür, sorun ilk verinin kaybolmasına kadar gecikirdi. - Durum kodları ayrı ayrı ele alınıyor çünkü kullanıcının atacağı adım her birinde farklı: 401 anahtarı, ulaşılamama adresi/ağı, diğerleri collector'ı işaret eder. - Yanıt gövdesi beklenmedik geldiğinde test yine BAŞARILI sayılır; doğrulanan şey anahtarın kabul edilmesi, gövdenin şekli değil. - Anahtar sorgu dizesine değil `Authorization` başlığına konur ve hiçbir mesaja karışmaz: URL'ler erişim loglarına düz metin yazılır, mesajlar da kullanıcı tarafından kopyalanıp paylaşılır. - `httpx.InvalidURL` ayrıca yakalanıyor: `httpx.HTTPError`'un ALTINDA olmadığı için, elle yazılmış bozuk bir collector_url kurulumun son adımında Python traceback'i bastırırdı. __main__.py: - `python -m agent --verify` modu eklendi (install.sh + sonradan teşhis için). - Her iki modda da config önce okunur; hatalıysa agent hiç açılmaz. - Başarısızlık stderr'e yazılır ve ayrı bir çıkış kodu (3) döner, install.sh sonucu ayırt edebilsin.
Kullanıcı dashboard'dan anahtarı alır, install.sh'i indirir ve çalıştırır. İndirilen tek dosya install.sh'tir; agent kaynağını betiğin kendisi çeker (dashboard bunu sonradan bir butona bağlayacak, kullanıcı terminale zorlanmayacak). install.sh — 7 adım: 1. Ön kontrol (Linux, journald, python3 >= 3.11, systemctl, curl) 2. Kaynak indirme — yalnızca HTTPS: `--proto '=https'` sayesinde yönlendirme düz HTTP'ye düşerse indirme reddedilir. Kurulan şey root'un çalıştıracağı koddur; araya giren biri içeriği değiştirebilseydi makineye istediği kodu yazdırırdı. Arşivin beklenen dosyaları taşıdığı ayrıca doğrulanır. 3. Yetkisiz `tracebox` kullanıcısı + dizinler (yalnızca systemd-journal grubuna eklenir; log okumak dışında bir yetkisi yoktur) 4. İzole venv + bağımlılıklar (PEP 668 sorununu aşar) 5. Config oluşturma 6. systemd kurulumu ve servisin başlatılması 7. `python -m agent --verify` ile bağlantı testi Kurulum yarım kalırsa geri alma (rollback) çalışır ve oluşturulan her şey ters sırayla temizlenir — YALNIZCA config yazılana kadar. Anahtar tek başına config.toml'da düz durur; sunucuda özeti, dashboard'da hiçbir şey vardır. Sonraki bir adımın hatası geri almayı tetikleseydi anahtar KALICI olarak kaybolur, kullanıcı cihazı silip baştan oluşturmak zorunda kalırdı. Bu yüzden config yazıldıktan sonra geri alma kapanır ve betik ne kurulduğunu söyleyip çıkar. Sorular stdin'den değil /dev/tty'den okunur: betik `curl … | sudo bash` ile çalıştırıldığında stdin BETİĞİN KENDİSİDİR; `read` stdin'den okusaydı sorunun cevabı olarak betiğin bir sonraki satırını yutardı. Anahtar `read -s` ile alınır, ekrana basılmaz ve hiçbir çıktıda tekrarlanmaz. config.toml `chmod 600` ile kilitlenir. tracebox-agent.service: - `User=tracebox` — servis root olarak çalışmaz. - ExecStart venv'in Python'ını kullanır; sistem python3'ü psutil ve httpx'i görmez. - ProtectSystem=strict + ReadWritePaths=/var/lib/tracebox: agent'ın yazabildiği tek yer state.json ve spool'dur. - NoNewPrivileges, ProtectHome, PrivateTmp, ProtectKernel*, RestrictAddressFamilies. - Çökme döngüsü freni [Unit] bölümünde: systemd v230'dan beri StartLimitIntervalSec/StartLimitBurst [Service] altında YOK SAYILIR. uninstall.sh servisi durdurur, üç dizini de siler (anahtar dahil) ve sistem kullanıcısını kaldırır. Yeniden çalıştırılabilir. `delete` komutu M6'da bu betiği çağıracak. Betikler tek seferlik atılabilir bir Docker konteynerinde uçtan uca çalıştırılarak doğrulandı: gerçek kullanıcı/dizin/venv oluşturma ve ardından tam geri alma.
config.toml, cihaz anahtarının DÜZ halini tutan tek dosyadır; sunucuda yalnızca SHA-256 özeti vardır. Dosyayı okuyabilen, cihazı taklit edebilir. İki değişiklik: 1. `check_permissions` — sahibi dışında herhangi biri için herhangi bir bit açıksa (grup dahil, yazma dahil) uyarı basılır. Yalnızca "diğerleri" bitlerine bakan bir kontrol 640'ı temiz sayardı; yalnızca okuma bitine bakan bir kontrol 620'yi temiz sayardı — oysa anahtarı saldırganın kendi anahtarıyla değiştirmek de bir saldırı yoludur. Agent DURDURULMAZ. Çalışan bir izleme aracını izin biti yüzünden öldürmek makineyi tamamen gözsüz bırakır; uyarı journald'a düşer ve düzeltme komutu (`chmod 600 <yol>`) mesajın içinde verilir. 2. Önbellek imzasına `st_mode` eklendi. `chmod` dosyanın ne mtime'ını ne de boyutunu değiştirir; imza yalnızca bu ikisine baksaydı açılışta 600 olan bir dosya sonradan 644 yapıldığında agent bunu ömür boyu fark etmezdi. Uyarı hâlâ yalnızca dosya (yeniden) okunurken basılıyor, her tick'te değil.
…betikleri M5'in tamamı testlerle kapatıldı. Her test, koruduğu kod bilerek bozularak doğrulandı: 38 sabotaj denemesinin 38'i yakalandı. "Yeşil" tek başına kanıt sayılmadı. test_auth_user.py (30) — user JWT doğrulaması. Gerçek ES256 anahtarlarıyla, ağa çıkmadan: JWKS belgesini yerelde bir HTTP sunucusu yayınlıyor. - Başka bir anahtarla imzalanmış token reddedilir (en kritik iddia). - Elle kurulmuş saldırı token'ları: `alg:none` ve açık anahtarı HMAC sırrı gibi kullanan HS256. Bu ikisi iki ayrı katmanda reddediliyor, bu yüzden izin listesinin kendisi ayrıca doğrudan sınanıyor — yoksa listeye HS256 eklenmesi davranış testlerinden kaçardı. - Süresi geçmiş / `exp`siz / `sub`suz / yanlış `aud` / yanlış `iss` / yanlış `role` token'ları; bozuk başlık biçimleri. - Önbellek gerçekten bir kez çekiyor; 20 uydurma `kid` seli Supabase'e tek istekten fazlasını üretmiyor. - Reddetme mesajı her hatada aynı ve token loga düşmüyor. test_endpoints_device.py (25) — POST /devices. - Düz anahtar hiçbir koşulda satıra yazılmıyor; saklanan özet dönen anahtarın özeti. - Gövdeye `account_id`/`key_hash`/`device_id` yazma denemesi 422. - Ad kırpma, uzunluk sınırı (sınırın kendisi geçerli), boş/boşluk reddi. - 409 ile 503 ayrımı; Postgres'in kendi mesajı yanıta sızmıyor. - Kimlik doğrulaması olmadan 401 ve satır oluşmuyor. test_verify.py (27) — bağlantı testi. - Yalnızca 200 "bağlandı" sayılıyor (204/301/403/404/500/503 dahil hepsi başarısız). - Anahtar URL'e girmiyor, hiçbir mesajda görünmüyor. - Ağ hataları okunabilir sonuca dönüşüyor, istisna dışarı sızmıyor. test_config_permissions.py (18) — izin denetimi. - 640/644/660/604/… hepsi işaretleniyor; 600 sessiz. - Gevşek izin agent'ı durdurmuyor, uyarı her tick tekrarlanmıyor. - Açılıştan SONRA gevşetilen izin fark ediliyor. test_install_scripts.py (33) — betikler birim testinde çalıştırılamaz (kullanıcı oluşturur, sistem dizinlerine yazar, systemd'ye dokunur), bu yüzden statik sözleşme kontrolü: `bash -n`, `set -euo pipefail`, `chmod 600`, `read -s`, `/dev/tty`, yalnızca HTTPS, `User=tracebox`, venv yorumlayıcısı, NoNewPrivileges, çökme freninin [Unit] altında olması, uninstall'ın üç dizini de silmesi. İddialar yorum satırlarıyla tatmin edilemiyor. Ayrıca TRACEBOX_CONFIG / TRACEBOX_STATE_DIR override'larının kurulum betiğine ya da unit dosyasına sızmadığı kontrol ediliyor: sızsalardı üretim agent'ı /etc/tracebox yerine başka bir yolu okur ve hata vermeden yanlış ayarla çalışırdı. test_hashing.py (+5) — `generate_device_key`: ön ek, benzersizlik (1000 anahtar), entropi uzunluğu ve anahtarın `random` modülünden gelmediği. Sonuncusu için başlangıç değeri aynı noktaya iki kez sabitlenip iki anahtar isteniyor; kaynak `random` olsaydı ikisi birebir aynı çıkardı. Bu tur iki gerçek kusur ortaya çıkardı: yakalanmayan `httpx.InvalidURL` (ayrı commit'te düzeltildi) ve hiçbir bozulmada kırmızıya dönemeyen bir testim — dosya türü bitleri izin maskesiyle hiç kesişmediği için silindi. Toplam: 226 test.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bir cihazın sisteme dahil olduğu yolu uçtan uca kapatır: dashboard cihazı
oluşturur ve anahtarı alır, kullanıcı install.sh'i çalıştırır, agent servis
olarak kurulur ve bağlantısını doğrular.
Neler var
Cihaz anahtarı burada üretilir; veritabanına yalnızca SHA-256 özeti yazılır,
düz hali tek seferlik yanıtta döner.
projenin JWKS ucundan çekilen açık anahtarla doğrulanır. Paylaşılan sır
(HS256) hiç kullanılmaz.
HTTPS), yetkisiz
traceboxkullanıcısı, izole venv, config, systemd,bağlantı testi. Yarım kalırsa geri alır.
/var/lib/tracebox'tır.Güvenlik kararları
device_idyok.account_id'si gövdeden değil doğrulanmış token'dan alınır —kullanıcı başka bir hesabın altına cihaz açamaz.
config.toml'da düz durur (chmod 600), terminalebasılmaz, hiçbir mesaja karışmaz.
bir hata anahtarı kalıcı olarak silerdi.
Test
226 test (138'i bu PR'da). Her test, koruduğu kod bilerek bozularak
doğrulandı — 38 sabotaj denemesinin 38'i yakalandı.
Betikler ayrıca tek seferlik bir Docker konteynerinde uçtan uca çalıştırıldı:
gerçek kullanıcı/dizin/venv oluşturma ve ardından tam geri alma.
Bu PR'da olmayan
Collector henüz deploy edilmedi; gerçek cihaz ekleme akışı deploy sonrası
denenecek.
install.shkaynağımaster'dan indirdiği için ancak bu PR mergeedildikten sonra çalışır.