Seri · The Quiet Machine · Bölüm 3

Windows'a Şifresiz SSH, Sonra Sıkı Sıkı Kilitlemek

Box köşede duruyor; monitörü yok, klavyesi yok, bir kesintiden sonra kendi kendine açılacak şekilde ayarlı. Elektrik gelince Windows kilit ekranında karşılıyor beni, kimsenin girmeyeceği bir PIN’i bekleyerek. İçeri girmenin tek yolu birinin oturum açmasıysa, elinde sunucu yok demektir. Elinde duvar kağıtlı, pahalı bir ısıtıcı var demektir.

Amaç şuydu: box, daha kimse oturum açmadan SSH üzerinden cevap versin (kilitli bir makinenin tam uzaktan kontrolü, döngünün hiçbir yerinde Windows şifresi olmadan); sonra da bu yeteneği, açık bırakmak bana hiçbir şeye mal olmayacak kadar sıkı kilitleyeyim.

OpenSSH artık bir Windows özelliği

Hoş sürpriz: artık üçüncü parti bir şey kurmuyorsun. OpenSSH Server, Windows 11’le birlikte on-demand bir capability olarak geliyor ve gerçek bir servis gibi çalışıyor.

Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Set-Service -Name sshd -StartupType Automatic
Start-Service sshd

StartupType Automatic verilince sshd, boot’ta, service layer’da, interactive login’den önce ayağa kalkıyor; yani box kilit ekranında, kimse oturum açmamış halde beklerken bile SSH cevap vermeye devam ediyor. Elektrik geldiğinden beri tek bir insan yüzü görmemiş, kilitli bir makinede shell alıyorum.

Öncekiyle aynı ayrım: Mac hâlâ benimle ve Claude’la konuşuyor, Windows box ise içine uzandığım, hep açık duran şey.

Bir akşamı yiyen iki tuzak

Mac’te bir key ürettim (ssh-keygen -t ed25519), public yarısını karşıya kopyaladım ve karşılığında bir password prompt’u aldım. Key sessiz sedasız görmezden gelinmişti: ne bir hata, ne işe yarar bir log satırı. İki tuzak; ikisi de Windows’a özgü, ikisi de tam bakacağın yerde belgelenmemiş.

Tuzak 1: admin hesapları ~/.ssh kullanmıyor

Hesap administrator’sa, Windows’taki OpenSSH C:\Users\you\.ssh\authorized_keys‘i okumuyor. Onun yerine tek ve paylaşılan bir dosyaya bakıyor:

C:\ProgramData\ssh\administrators_authorized_keys

ACL kilitlenmediği sürece de o dosyaya güvenmiyor: sahibi Administrators ve SYSTEM, inheritance kapalı:

icacls C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r
icacls C:\ProgramData\ssh\administrators_authorized_keys /grant Administrators:F /grant SYSTEM:F

Path’i ıskalarsan key auth başarısız olur. Path’i doğru verip ACL’i atlarsan da aynı sessizlikle başarısız olur.

Tuzak 2: BOM

Key dosyasını PowerShell’den yazdım, yine de authenticate olmadı. Her editörde kusursuz görünüyordu. Sorun gözle görülmüyordu: PowerShell’in default redirection’ı UTF-8’i byte-order mark’la yazıyor, sshd de baştaki o üç byte’a boğuluyor. Çözüm, düz ASCII’yi zorlamak:

Set-Content -Path C:\ProgramData\ssh\administrators_authorized_keys `
  -Value $pubkey -Encoding ascii

-Encoding ascii bunu çözüyor. Göremediğim üç byte yüzünden resmen zaman kaybettim.

Her şeyi scriptable yapan hile

Bir SSH bağlantısı üzerinden PowerShell sürmek tam bir quoting cehennemi: bir zsh string’inin içindesin, onun içinde bir ssh argument’i, onun içinde bir PowerShell parser’ı; her katman kendi quote’unu, kendi escape’ini istiyor. Yanlış yere düşmüş tek bir ", komutun anlamını baştan aşağı değiştiriyor.

Çıkış kapısı -EncodedCommand: script’i UTF-16LE olarak base64-encode edip blob’u PowerShell’e teslim ediyorsun. PowerShell onu decode edene kadar parse edilecek hiçbir şey kalmıyor, yani yanlış okunacak hiçbir quote hayatta kalmıyor.

CMD='Get-Service sshd | Select Status,StartType'
ENC=$(printf '%s' "$CMD" | iconv -t UTF-16LE | base64)
ssh box "powershell -EncodedCommand $ENC"

iconv -t UTF-16LE pazarlık konusu değil; -EncodedCommand, UTF-8 değil UTF-16 bekliyor. Bu çalışınca, build’de kalan her adım Mac’ten çalıştırılan bir one-liner’a dönüştü.

Kapıyı tekrar kilitlemek

SSH’ı açmak kolay yarısı. Dürüst yarısı ise şunu kabullenmek: attack surface’i genişletmiştim; şimdi onu, başladığı yerden bile daha da daraltmam gerekiyordu. BIOS turundaki fikrin aynısı: maksimum kapasite, minimum açık. C:\ProgramData\ssh\sshd_config içinde:

PasswordAuthentication no
MaxAuthTries 3
LoginGraceTime 20

PasswordAuthentication no burada en büyük işi görüyor: o gidince LAN brute-force ihtimali de gidiyor (tahmin edilecek şifre kalmıyor) ve tehdit modeli sadeleşiyor. Girmenin tek yolu private key; o key de tam olarak tek bir yerde duruyor: Mac’te. Mac’i koru (FileVault ve bir oturum açma şifresi), box da geçişlilik yoluyla hava geçirmez hale gelsin.

Sonra da onu internete hiç açmadım. Firewall kuralı sshd’yi local subnet’le sınırlıyor:

New-NetFirewallRule -Name sshd -DisplayName "OpenSSH Server" `
  -Enabled True -Direction Inbound -Protocol TCP -Action Allow `
  -LocalPort 22 -RemoteAddress LocalSubnet

RemoteAddress LocalSubnet şu demek: LAN’ımdan erişilebilir, tüm internete görünmez. Cloud relay yok, fatura yok; her şey bu duvarların içinde kalıyor.

Şifreli key, yine de hiç yazmak yok

Diskte plaintext duran bir key, laptop’un çalındığı gün başkasının key’ine dönüşür. O yüzden üzerine bir passphrase koydum; sonra da o passphrase’i günde kırk kez yazmak istemediğimden macOS Keychain’e emanet ettim:

ssh-add --apple-use-keychain ~/.ssh/id_ed25519

~/.ssh/config içindeki UseKeychain yes ve AddKeysToAgent yes sayesinde key, dururken şifreli kalıyor ama oturum açılınca kilidi kendiliğinden çözülüyor.

Tooling’in bana hayır dediği an

Bu işi yürüten agent’tan, ~/.ssh/config‘e o iki satırı eklemesini istedim. Reddetti. Kendi safety hook’u, ~/.ssh/ dahil credential path’lerine yazmayı engelliyor; o yüzden durdu, yazmayacağı satırı bana bir bir söyledi ve edit’i geri bıraktı. On saniyede elimle yazdım.

Bu friction’ın kendisi zaten amaçtı. Bu agent’ı, kilitli bir box’a giden şifresiz bir key’le iki makineye de bağlamışım; dolayısıyla asla sessiz sedasız yapmaması gereken tek şey, credential’ların nasıl kullanıldığına dokunmak. On saniyelik bir yazı, bunun için adil bir bedel.

Tüm bu hardening’in bedeli şu: box hava geçirmez, ama yalnızca kendi dairemin içinden. Bense aynısını bir kahve dükkânından da istiyordum, hem de o firewall’a tek bir delik bile açmadan. Beni sıradaki adıma, Tailscale’e götüren de bu oldu.