Blueforce-Linux/provisioning/enrollment/README.md
emirhanmamak-blueforce ef102b9657 feat(field-os): provisioning layer, real ISO build, offline contracts
Adds the Blueforce Field OS provisioning stack on top of the existing fleet
architecture, without rewriting the working systems (installer, diagnostics,
Ansible, WireGuard/SSH/xRDP/RustDesk, Docker policies, monitoring, Docusaurus).

ISO build (the previous builder was a skeleton that produced no medium):
- boot layout is read from the base ISO at build time via
  `xorriso -report_el_torito as_mkisofs` and rebuilt with `-as mkisofs`;
  `-boot_image any replay` cannot work on Ubuntu live ISOs because the GRUB2
  MBR lives in the source image's first 16 sectors and the EFI system partition
  is an appended partition, neither of which is a file in the ISO9660 tree
- preflight now asserts El Torito boot metadata positively: xorriso exits 0 on
  a non-ISO input, which previously let an arbitrary file pass --check
- artifact produced and verified: dist/Blueforce-Field-OS-1.0.0-amd64.iso,
  6 481 917 952 bytes, SHA256 cb0cc56f...d431296, BIOS+UEFI entries preserved,
  /autoinstall.yaml at the media root, casper/minimal.squashfs byte-identical
  to the base
- recorded baseline deviation: the verified medium on the build host is the
  Desktop flavour, so the manifest carries a baseline_deviation block instead
  of hiding it; Server remains one flag away

Offline and identity:
- offline APT contract with concrete package requirements, pin format and an
  audit table; builds fail closed while pins are UNPINNED
- autoinstall keeps interactive-sections: [storage]: the operator confirms the
  target disk, and no automatic wipe is ever performed
- firstboot terminal wizard for the 8-digit dealer id, three-state lifecycle
  (PROVISIONED_OFFLINE -> ENROLLED -> READY) with fail-closed gates, so a
  missing internet link or WireGuard handshake cannot fail provisioning but can
  never produce a false READY

Fleet and migration:
- five read-only Ansible playbooks for Field OS version, provisioning,
  enrollment, remote channels and offline readiness, all wave-scoped with a
  separate production override
- Windows pre-migration inventory and data export extended to the full brief
  scope, read-only, never collecting credentials

Tests: tests/run-all.sh -> 8/8 pass, exit 0. Boot acceptance (UEFI, Legacy
BIOS, Secure Boot), air-gapped install, real enrollment endpoint and Windows
rollback remain LAB work and are listed in IMPLEMENTATION-REPORT.md.
2026-09-16 11:14:42 +03:00

143 lines
9.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Enrollment sözleşmesi (Field OS yaşam döngüsü)
Bu dizin **sözleşmedir, uç nokta değildir**: istemci davranışı, istek/yanıt şemaları ve
güven modeli burada tanımlanır. Merkezi enrollment API'si bu repoda uygulanmaz;
uygulanması ayrı bir güvenlik incelemesi gerektirir (prompt §18, `docs/29` §13).
İlgili dosyalar: `docs/29-DEVICE-ENROLLMENT.md` (karar K-17), `docs/26` (K-15),
`schemas/enrollment-request.schema.json`, `schemas/enrollment-response.schema.json`.
## 1. Durum makinesi (ileri yönlü, atlama yok)
```text
(yok) --yerel kurulum--> PROVISIONED_OFFLINE --tek kullanımlık token--> ENROLLED --tüm kanallar doğrulandı--> READY
```
| Aşama | Ne kanıtlanır | Gerekli kanıt | İnternet |
|---|---|---|---|
| `PROVISIONED_OFFLINE` | Yerel kurulum tamam; cihaz çevrimdışı işe hazır | `bf-check-local`: yerel sistem durumu, xRDP, WireGuard anahtar çifti | Gerekmez |
| `ENROLLED` | Merkez cihazı tanıdı, WireGuard peer atandı, monitoring/inventory/RustDesk yapılandırması uygulandı | Doğrulanmış enrollment yanıtı + `central_verification` referansları + yeni WG handshake | Gerekir |
| `READY` | Yerel sağlık **ve** üç merkezi kanal doğrulandı | `bf-check-ready`: `bf-check-local` + `bf-check-enrollment` + `bf-remote-status --check` | Gerekir |
Geçiş kuralları (yazıcı ve okuyucu tarafında birlikte uygulanır):
- **Geriye dönüş yok.** `ENROLLED -> PROVISIONED_OFFLINE` ve `READY -> ENROLLED` yazılmaz;
`bf-enroll` READY cihazda çalışmayı reddeder. READY sonrası ciddi kimlik sorunu
`docs/27` akışıyla yeniden provision edilir.
- **Atlama yok.** `PROVISIONED_OFFLINE -> READY` doğrudan yazılamaz. READY yalnız
`bf-check-ready` geçtikten sonra final gate tarafından yazılır; okuma tarafında
`bf-enrollment-status` böyle bir durumu `INVALID` ve sıfırdan farklı çıkış kodu ile
bildirir (fail-closed).
- **Atomik yazım.** Her geçiş aynı dizinde `tmp + rename` ile yapılır, dosya modu `0600`,
`fsync` uygulanır. Yazıcılar `.state.lock` üzerinde `flock` ile serileştirilir ve geçiş
kilit altında yeniden doğrulanır (boot timer ile teknisyen yarışı).
- **Yetkili yazıcılar.** `PROVISIONED_OFFLINE` yerel kurulum, `ENROLLED` `bf-enroll`,
`READY` final kurulum kapısı. Başka hiçbir araç yaşam döngüsü durumunu değiştirmez:
`bf-status`, `bf-enrollment-status`, `bf-check-*` yalnız okur.
Durum dosyası sözleşmesi (`/var/lib/blueforce/state.json`, `chmod 600`, `BF_STATE_FILE`
ile geçersiz kılınabilir): `phase`, `enrollment_status`, `fleet_status`, `device_id`
(`BF-<no>`), `provisioning_id`. Bilinmeyen ek alanlar korunur ve yok sayılır.
## 2. İstek / yanıt sözleşmesi
**İstek** (`bf-enroll`, `schemas/enrollment-request.schema.json`): `device_id`,
`wireguard_public_key`, `idempotency_key`, `hardware`, `release`. İstek gövdesinde
secret yoktur: WireGuard private key, token değeri ve diğer kimlik bilgileri gövdeye
girmez. Tek kullanımlık token `Authorization` başlığında, argv'ye düşmeyen anonim bir
process-substitution fd'si üzerinden taşınır; başlık dosyaya, state'e veya loga yazılmaz.
**Yanıt** (`schemas/enrollment-response.schema.json`): `status` (`accepted`), `device_id`,
`idempotency_key` (istekle birebir aynı olmalı), isteğe bağlı `enrollment_id`,
`wireguard` (hub public key + adres + endpoint + allowed_ips) ve **zorunlu**
`central_verification` (`status`, `verification_id` ve her biri `status` +
`verification_id` taşıyan `monitoring`, `remote`, `management`). Yanıt bütünüyle
doğrulanır: fazla alan, uyuşmayan kimlik ya da `idempotency_key` tüm yanıtı reddettirir.
Yerel WireGuard yapılandırması yalnız bu doğrulamadan sonra kurulur; private key
merkezden **kabul edilmez**, cihazda kalır.
`central_verification` referansları istemci tarafından üretilmez; yalnız doğrulanmış
enrollment yanıtından kalıcılaştırılır. Eksik/biçimsiz kayıt ENROLLED kapısını, `pending`
kanal ise READY kapısını fail-closed düşürür (cihaz ENROLLED kalır).
## 3. Güven modeli: alternatifler
| Seçenek | Artı | Eksi | 700 cihaz ölçeği | Sonuç |
|---|---|---|---|---|
| **Tek kullanımlık, TTL'li token** | Küçük sızıntı alanı; kayıp başına iptal edilebilir; cihaz bağlama (`BF-<no>`) | Token üretim/i̇ptal hizmeti gerekir | Cihaz başına ayrı kayıt izi | **Seçildi** (K-17) |
| İmzalı bootstrap bundle (merkez imzalı dosya, cihaz sadece tüketir) | Çevrimiçi token hizmeti gerekmez; imza ile kimlik doğrulama | İmza anahtarı rotasyonu, bundle dağıtım kanalı ve çevrimdışı "eski bundle" riski; geri çekme zor | Bundle sürüm/iptal yönetimi filo çapında iş yükü | Elendi (V1) |
| Paylaşılan kalıcı bootstrap secret | Kurulumu basit | Tek sızıntı tüm filoyu etkiler; cihaz başına iptal imkânsız | Kabul edilemez blast-radius | Elendi |
| Manuel merkezi kayıt | Ek sistem yok | Denetim izi zayıf, insan hatası | 700 kayıt için sürdürülemez | Elendi |
Hiçbir seçenekte **700 cihaza ortak kalıcı secret konmaz**. Karar kaydı: `docs/24` K-17,
alternatif analizi `docs/29` §5.
### 3.1 Çevrimdışı → çevrimiçi geçiş (katılım) mekanizması
Teknisyen tokenı cihaz başına bir kez iletir:
```bash
# İnternet varken anında enrollment (önerilen):
printf '%s\n' '<tek-kullanımlık-token>' | sudo -E bf-enroll --token-stdin
# İnternet yokken: token'ı cihazda bırakmadan boot denemesine hazırla:
printf '%s\n' '<tek-kullanımlık-token>' | sudo -E bf-enroll-now --stage
```
`--stage` gerekçesi: saha cihazı çoğu kez önce çevrimdışı kurulur. İnternet geldiğinde
"kendiliğinden katılma" için tek kullanımlık token'ın cihazda **sınırlı ömürlü** bir
bekleme dosyasında durması gerekir. Uygulanan sınırlar:
- Dosya `<state dir>/enrollment.token`, mod `0600`, sahibi çalıştıran kullanıcı (root),
TTL `BF_ENROLLMENT_TOKEN_MAX_AGE` (varsayılan 1800 sn, süre sonunda silinir).
- Token argv'ye, state dosyasına, support bundle'a ve kurulum medyasına (USB/ISO) hiç
yazılmaz; yalnız bu tek dosyada ve yalnız boot denemesi okuyabilir.
- Token başarılı enrollment'ta veya kesin (HTTP 4xx) reddedilmede **hemen** silinir.
Yalnız geçici hata (DNS/TCP/TLS/zaman aşımı) token'ı sınırlı yeniden deneme için korur.
- Support bundle bu dosyayı toplamaz; `--stage` yalnız root tarafından kullanılabilir.
Bu mekanizma tek kullanımlık token'ın replay engelini değiştirmez: aynı token merkezde
yalnız bir kez tüketilir, `idempotency_key` retry'leri birleştirir.
## 4. Başarısızlık davranışı (reboot loop yok)
1. HTTP isteği başarısız: yaşam döngüsü önceki aşamada kalır (`PENDING` /
`PROVISIONED_OFFLINE`); hiçbir yapılandırma kurulmaz.
2. Yanıt doğrulaması başarısız: durum dosyası değişmez, geçici dosyalar silinir.
3. WireGuard etkinleştirmesi başarısız: yeni `wg0.conf` geri alınır (öncesi varsa eski
hali geri yüklenir, yoksa unit devre dışı bırakılır), durum dosyası yazılmaz.
4. Cihaz reboot edilmez: `blueforce-enroll.service` `Restart=on-failure` +
`RestartSec=5min` + `StartLimitBurst=6` / `StartLimitIntervalSec=1h` ile en fazla
saati 6 deneme yapar, sonrası `blueforce-enroll.timer` kadansına döner. İnternet yokken
agresif deneme yapılmaz; internet gelince cihaz kendiliğinden katılır.
5. Token yoksa veya durum `READY`/kurulmamış ise deneme hiçbir iş yapmadan `0` ile çıkar
(`PENDING` mesajı ile): yeniden deneme fırtınası oluşmaz.
## 5. Araçlar ve aşama kapıları
| Araç / unit | Aşama | Not |
|---|---|---|
| `bf-check-local` | `PROVISIONED_OFFLINE` | İnternet, handshake ve merkez kanıtı **gerekmez**; eksik `wg0.conf` yalnız uyarıdır |
| `bf-enroll` | `PROVISIONED_OFFLINE -> ENROLLED` | Token stdin'den ya da sahnelenmiş dosyadan; `--check` dry-run |
| `bf-check-enrollment` | `ENROLLED` | Merkez kaydı + atanmış peer + canlı tünel + yeni handshake; handshake yokluğu **başarısızlık** |
| `bf-check-ready` | `READY` | Yerel sağlık + tüm merkezi kanallar; kalıcı READY'a güvenmez |
| `bf-enrollment-status` | tümü (salt-okunur) | Sözleşme doğrulayıcı; `INVALID` durumda çıkış kodu ≠ 0 |
| `bf-status` | tümü (salt-okunur) | `Provisioning` / `Enrollment` / `Fleet State` özet bloğu |
| `blueforce-enroll.service` + `blueforce-enroll.timer` | otomatik deneme | Boot +10 dk, sonra günlük; sınırlı `Restart=` |
`blueforce-enrollment.timer` **ayrıca oluşturulmaz**: kurucu modül (`17-healthcheck`) yalnız
`blueforce-enroll.service` ve `blueforce-enroll.timer` dosyalarını cihaza kurar; ikinci bir
timer kurulmadığı için ölü yapılandırma olurdu. Periyodik kadans mevcut timer ve
servisin sınırlı `Restart=` davranışından gelir.
## 6. Doğrulama
```bash
bash tests/test-field-os-lifecycle.sh # sözleşme, aşama geçişleri, fail-closed davranışı
bash tests/check-provisioning-static.sh # şema/secret/cihaz-kimliği taraması
```
Şemalar JSON Schema 2020-12'dir ve `bf-enroll`'in gerçek davranışıyla birebir hizalanır
(zorunlu alanlar, `wireguard` alt alanları, `central_verification` biçimi). Endpoint
uygulaması, kimlik doğrulama, imzalama, iptal ve deneme politikası ayrı güvenlik
incelemesi olmadan eklenmeyecektir.