Skip to content

Feat/m5 device registration M5: Cihaz kaydı ve kurulum — POST /devices, install.sh, systemd - #2

Merged
Denisergocmen924 merged 5 commits into
masterfrom
feat/m5-device-registration
Aug 24, 2026
Merged

Feat/m5 device registration M5: Cihaz kaydı ve kurulum — POST /devices, install.sh, systemd#2
Denisergocmen924 merged 5 commits into
masterfrom
feat/m5-device-registration

Conversation

@Denisergocmen924

Copy link
Copy Markdown
Owner

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

  • POST /devices — collector'ın kullanıcı JWT'siyle korunan ilk yazma ucu.
    Cihaz anahtarı burada üretilir; veritabanına yalnızca SHA-256 özeti yazılır,
    düz hali tek seferlik yanıtta döner.
  • User JWT doğrulama — Supabase access token'ı ES256 ile imzalanır; imza,
    projenin JWKS ucundan çekilen açık anahtarla doğrulanır. Paylaşılan sır
    (HS256) hiç kullanılmaz.
  • install.sh — 7 adımlı kurulum: ön kontrol, kaynak indirme (yalnızca
    HTTPS), yetkisiz tracebox kullanıcısı, izole venv, config, systemd,
    bağlantı testi. Yarım kalırsa geri alır.
  • systemd unit — servis root olarak çalışmaz, yazabildiği tek yer
    /var/lib/tracebox'tır.
  • uninstall.sh — anahtar dahil her şeyi temizler.

Güvenlik kararları

  • Cihaz kimliği anahtardan türetilir; payload'da device_id yok.
  • Satırın 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.
  • Anahtar yalnızca config.toml'da düz durur (chmod 600), terminale
    basılmaz, hiçbir mesaja karışmaz.
  • Config yazıldıktan sonra kurulumun geri alma mekanizması kapanır: sonraki
    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.sh kaynağı master'dan indirdiği için ancak bu PR merge
edildikten sonra çalışır.

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.
@Denisergocmen924
Denisergocmen924 merged commit ce582ab into master Aug 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant