Zum Inhalt springen
Farbschema wählenSprache wählen

Einen Desktop-Host fuer SSH scharfschalten

Bevor ein Agent ein Werkzeug auf einer Workstation ausfuehren kann, muss die Maschine den Server hereinlassen — einmal, ueber ein Netz, mit einem Schluessel. Diese Seite ist das vollstaendige Verfahren fuer einen Windows-Host, der Reihe nach, mit einer Probe nach jedem Schritt. Arbeiten Sie sie ab, und Sie muessen nichts raten.

Die Schritte laufen auf der Maschine, in einer erhoehten PowerShell, ausser wo ein Schritt es anders sagt. Wo <user> steht, setzen Sie das Konto ein, auf das die Werkzeuge lizenziert sind.

SupaCloud-Server ──SSH (nur Tailnet)──▶ Workstation
haelt den privaten Schluessel haelt den oeffentlichen Schluessel,
im Secret-Bag der Resource die Werkzeuge, die GPU, die Lizenz

Der Server waehlt hinaus zu einer Maschine, die er benennt. Es gibt keinen Agenten zu installieren, und nichts auf der Workstation ruft nach Hause.

  • Die Maschine ist im Tailnet, und Sie kennen ihren Tailnet-Namen oder die 100.x-Adresse.

  • Sie tun das nicht auf einem Alltagsrechner. Ein Desktop-Host fuehrt Agenten-Kommandos als echter Benutzer aus, ohne Container-Grenze — kein cap_drop, kein schreibgeschuetztes Root, keine PID-Grenzen. Nehmen Sie eine dedizierte Maschine.

  • Sie haben ein Schluesselpaar fuer diesen Host. Erzeugen Sie es auf der Serverseite, nicht auf der Workstation:

    Terminal-Fenster
    ssh-keygen -t ed25519 -N '' -C 'supacloud→bench-01' -f ./bench-01

    bench-01.pub kommt auf die Maschine; bench-01 kommt in Schritt 7 in den Secret-Bag der Resource und sonst nirgendwohin.

Terminal-Fenster
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0

Probe: Get-WindowsCapability -Online -Name OpenSSH.Server* | Select-Object State meldet Installed.

Terminal-Fenster
Set-Service -Name sshd -StartupType Automatic
Start-Service -Name sshd

Probe: Get-Service sshd | Select-Object Status, StartType meldet Running und Automatic. Ein Host, der nur bis zum naechsten Neustart antwortet, ist ein Host, der um 3 Uhr nachts ausfaellt.

3. Den oeffentlichen Schluessel einlegen — und das hier vorher lesen

Abschnitt betitelt „3. Den oeffentlichen Schluessel einlegen — und das hier vorher lesen“

Windows-OpenSSH liest fuer Konten in der lokalen Administratorengruppe nicht ~/.ssh/authorized_keys. Der Match Group administrators-Block am Ende von C:\ProgramData\ssh\sshd_config leitet diese Konten auf C:\ProgramData\ssh\administrators_authorized_keys um. Stillschweigend. Das ist der mit Abstand haeufigste Grund, aus dem ein richtiger Schluessel abgewiesen wird.

Waehlen Sie eines:

A — das Konto ist kein Administrator (bevorzugt; die Werkzeuge brauchen das selten):

Terminal-Fenster
$file = "C:\Users\<user>\.ssh\authorized_keys"
New-Item -ItemType Directory -Force -Path (Split-Path $file) | Out-Null
Set-Content -Path $file -Value (Get-Content .\bench-01.pub) -Encoding ascii
icacls $file /inheritance:r /grant "<user>:R" /grant "SYSTEM:F"

B — das Konto ist Administrator: legen Sie denselben Schluessel stattdessen in C:\ProgramData\ssh\administrators_authorized_keys, mit der ACL, die diese Datei verlangt:

Terminal-Fenster
$file = "C:\ProgramData\ssh\administrators_authorized_keys"
Set-Content -Path $file -Value (Get-Content .\bench-01.pub) -Encoding ascii
icacls $file /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"

So oder so ist die ACL der Punkt: OpenSSH weist eine Schluesseldatei ab, die jemand anders schreiben kann — und zwar indem es den Schluessel ignoriert, nicht indem es etwas sagt.

Probe: icacls $file listet nur die zwei Eintraege, die Sie vergeben haben.

Die Installation legt eine breite eingehende Regel an. Ersetzen Sie sie.

Terminal-Fenster
Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -ErrorAction SilentlyContinue |
Set-NetFirewallRule -Enabled False
New-NetFirewallRule `
-Name 'sshd-tailnet-only' `
-DisplayName 'OpenSSH Server (nur Tailnet)' `
-Direction Inbound -Protocol TCP -LocalPort 22 -Action Allow `
-RemoteAddress 100.64.0.0/10 `
-InterfaceAlias (Get-NetAdapter |
Where-Object InterfaceDescription -like '*Tailscale*').Name

100.64.0.0/10 ist der Adressbereich, den ein Tailnet vergibt — die Regel bleibt also richtig, wenn der Adapter umbenannt wird; die Interface-Bindung ist das zweite Schloss.

Probe: von einer Maschine ausserhalb des Tailnets scheitert Test-NetConnection <public-ip> -Port 22. Von innen gelingt sie. Beide Haelften zaehlen — eine Regel, die alle hereinlaesst, besteht die zweite Probe auch.

Terminal-Fenster
ssh-keygen -lf C:\ProgramData\ssh\ssh_host_ed25519_key.pub

Es druckt etwas wie 256 SHA256:9k2f… C:\ProgramData\ssh\ssh_host_ed25519_key.pub (ED25519).

Melden Sie den SHA256:…-Teil zurueck. Er kommt in das Feld host_key_fingerprint der Resource und ist das, was den Server davon abhaelt, mit einer anderen Maschine zu sprechen, die auf dieser Adresse antwortet. Es gibt hier kein Trust-on-first-use: mit einem ungepinnten Host verbinden wir uns nicht.

Der Evidenzleser muss auf der Maschine genau zwei Dinge tun: eine Datei ausgeben und sagen, wann diese Datei zuletzt geschrieben wurde. Beides wird deklariert, nicht angenommen, weil cat / type / Get-Content nicht auf jedem Host dasselbe Wort sind.

Legen Sie C:\ProgramData\supacloud\bin\sc-read.ps1 an:

Terminal-Fenster
param([Parameter(Mandatory)][string]$Path)
if (-not (Test-Path -LiteralPath $Path)) { exit 1 }
$bytes = [System.IO.File]::ReadAllBytes($Path)
$out = [Console]::OpenStandardOutput()
$out.Write($bytes, 0, $bytes.Length)
$out.Flush()

Es schreibt absichtlich Bytes, nicht Text: eine Pipeline, die ihren Report durch PowerShell leitet, schreibt UTF-16LE mit Byte-Order-Mark, und der Leser dekodiert das nur richtig, wenn die Marke ueberlebt.

Legen Sie C:\ProgramData\supacloud\bin\sc-mtime-ms.ps1 an:

Terminal-Fenster
param([Parameter(Mandatory)][string]$Path)
if (-not (Test-Path -LiteralPath $Path)) { exit 1 }
$utc = (Get-Item -LiteralPath $Path).LastWriteTimeUtc
[DateTimeOffset]::new($utc, [TimeSpan]::Zero).ToUnixTimeMilliseconds()

Deklarieren Sie beide auf der Resource:

"read_command": "powershell -NoProfile -NonInteractive -File C:\\ProgramData\\supacloud\\bin\\sc-read.ps1",
"stat_command": "powershell -NoProfile -NonInteractive -File C:\\ProgramData\\supacloud\\bin\\sc-mtime-ms.ps1"

Der Aufruf ueber powershell -File sorgt dafuer, dass die Login-Shell keine Rolle mehr spielt — die Zeile laeuft gleich, ob die SSH-Sitzung in cmd.exe oder in PowerShell landet.

Probe, in einer SSH-Sitzung, gegen eine existierende Datei:

powershell -NoProfile -NonInteractive -File C:\ProgramData\supacloud\bin\sc-mtime-ms.ps1 C:/Windows/win.ini

Es druckt eine 13-stellige Zahl. Beachten Sie die Schraegstriche im Pfad: ein Backslash ist in einem deklarierten Pfad ein abgewiesenes Metazeichen und bleibt es — der Treiber weist Token ab, statt sie zu escapen, weil der entfernte Dialekt nicht erkennbar ist.

Auf einer Linux-Workstation sind dieselben zwei Kommandos cat -- und ein zweizeiliges mtime-ms-Skript; siehe Ein lokales Desktop-Werkzeug einbinden.

Legen Sie die ssh_host-Resource im Workspace an, mit Host, Port, Benutzername und dem host_key_fingerprint aus Schritt 5, und legen Sie den privaten Schluessel in ihren Secret-Bag:

{ "private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n\n-----END OPENSSH PRIVATE KEY-----\n" }

Loeschen Sie danach Ihre lokale Kopie.

Es gibt bewusst keinen Endpunkt, der Ihnen das ausstellt. Ein solcher Endpunkt waere ein zweiter Ort, an dem dieselben Zugangsdaten liegen, und Zugangsdaten mit zwei Heimaten haben zwei Gelegenheiten zu entweichen und keinen einzelnen Ort zum Zurueckziehen. Der Schluessel existiert im Secret-Bag und auf der Workstation und sonst nirgends — nicht im Agenten, nicht in der MCP-Schicht, und auch nicht im Werkzeug der Maschine selbst, das ihn ebenfalls nie sieht.

Von der Serverseite:

Terminal-Fenster
ssh -i ./bench-01 -o StrictHostKeyChecking=yes <user>@<tailnet-name> "echo ok"

Dann, als Agent oder ueber die API:

tool.list { "host": "bench-01", "probe": true }

Jedes deklarierte Werkzeug sollte mit available: true und einer reported_version zurueckkommen, die seinen Vertrag erfuellt. Kommt eine Version unlesbar zurueck, richten Sie das vorher: eine Festlegung, die sich nicht lesen laesst, blockiert den Lauf, statt still durchzugehen — genau das wollen Sie, und jetzt ist es leichter zu klaeren als mitten in einem Auftrag.

Ein ueber SSH gestartetes Kommando laeuft auf Windows in einer nicht-interaktiven Sitzung ohne Desktop. Das ist eine Eigenschaft von Windows, kein Konfigurationsfehler, und es geht nicht weg.

  • Die Werkzeugtreiber bleiben unberuehrt. Blender, Unity, FreeCAD, KiCad und OpenSCAD laufen alle kopflos; genau darum ist der Batch-Weg der primaere.
  • Die Bildschirmhaelfte steht nicht zur Verfuegung. desktop.observe und desktop.act brauchen eine angemeldete Maschine und einen Agenten, der in dieser interaktiven Sitzung laeuft. Das aufzustellen heisst Autologon plus den Agenten als Vordergrundprozess in dieser Sitzung — nie als Windows-Dienst, denn ein Dienst lebt in Session 0, und Session 0 hat keinen Desktop zum Aufnehmen oder Anklicken.

Das ist Betriebsarbeit an der Maschine, keine Konfiguration auf dieser Seite. Bis sie getan ist, scheitern desktop.observe und desktop.act auf einem Windows-Host an der fehlenden Sitzung, und jedes kopflose Werkzeug arbeitet weiter.