fix(aws): configure Windows SSH ports

This commit is contained in:
Vincent Koc 2026-05-03 23:27:47 -07:00 committed by GitHub
parent a45f308c13
commit 8c6512eaef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 71 additions and 10 deletions

View File

@ -7,11 +7,11 @@ import (
"time"
)
func bootstrapAWSWindowsDesktop(ctx context.Context, cfg Config, target SSHTarget, publicKey string, stderr io.Writer) error {
func bootstrapAWSWindowsDesktop(ctx context.Context, cfg Config, target *SSHTarget, publicKey string, stderr io.Writer) error {
if cfg.Provider != "aws" || cfg.TargetOS != targetWindows || cfg.WindowsMode != windowsModeNormal {
return waitForSSH(ctx, &target, stderr)
return waitForSSH(ctx, target, stderr)
}
bootstrapTarget := target
bootstrapTarget := *target
bootstrapTarget.User = "Administrator"
bootstrapTarget.ReadyCheck = powershellCommand(`$PSVersionTable.PSVersion | Out-Null`)
if err := waitForSSHReady(ctx, &bootstrapTarget, stderr, "windows openssh", 20*time.Minute); err != nil {
@ -28,5 +28,5 @@ exit $LASTEXITCODE`)
if err != nil {
fmt.Fprintf(stderr, "warning: Windows bootstrap SSH command ended before completion; waiting for reboot/ready state: %v\n", err)
}
return waitForSSH(ctx, &target, stderr)
return waitForSSH(ctx, target, stderr)
}

View File

@ -127,6 +127,7 @@ function New-CrabboxPassword {
$user = ` + psQuote(cfg.SSHUser) + `
$publicKey = ` + psQuote(publicKey) + `
$workRoot = ` + psQuote(workRoot) + `
$sshPorts = ` + windowsSSHPortsPowerShell(cfg) + `
$vncPasswordPath = "C:\ProgramData\crabbox\vnc.password"
$windowsUsernamePath = "C:\ProgramData\crabbox\windows.username"
$windowsPasswordPath = "C:\ProgramData\crabbox\windows.password"
@ -172,8 +173,26 @@ if (-not (Get-Service -Name sshd -ErrorAction SilentlyContinue)) {
New-Item -ItemType Directory -Force -Path "$env:ProgramData\ssh" | Out-Null
Set-Content -Encoding ASCII -Path "$env:ProgramData\ssh\administrators_authorized_keys" -Value $publicKey
icacls.exe "$env:ProgramData\ssh\administrators_authorized_keys" /inheritance:r /grant "*S-1-5-32-544:F" /grant "*S-1-5-18:F" | Out-Null
if (-not (Get-NetFirewallRule -Name "crabbox-sshd" -ErrorAction SilentlyContinue)) {
New-NetFirewallRule -Name "crabbox-sshd" -DisplayName "Crabbox OpenSSH" -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 | Out-Null
$sshdConfigPath = "$env:ProgramData\ssh\sshd_config"
$sshdConfig = ""
if (Test-Path -LiteralPath $sshdConfigPath) {
$sshdConfig = Get-Content -Raw -LiteralPath $sshdConfigPath
}
$globalLines = @()
$matchLines = @()
$inMatch = $false
foreach ($line in ($sshdConfig -split "\r?\n")) {
if ($line -match '^\s*Match\s+') { $inMatch = $true }
if (-not $inMatch -and $line -match '^\s*Port\s+\d+\s*$') { continue }
if ($inMatch) { $matchLines += $line } else { $globalLines += $line }
}
foreach ($port in $sshPorts) { $globalLines += "Port $port" }
Set-Content -Encoding ASCII -LiteralPath $sshdConfigPath -Value (($globalLines + $matchLines) -join [Environment]::NewLine)
foreach ($port in $sshPorts) {
$ruleName = "crabbox-sshd-$port"
if (-not (Get-NetFirewallRule -Name $ruleName -ErrorAction SilentlyContinue)) {
New-NetFirewallRule -Name $ruleName -DisplayName "Crabbox OpenSSH $port" -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort $port | Out-Null
}
}
Set-Service -Name sshd -StartupType Automatic
Start-Service sshd
@ -246,6 +265,15 @@ if (-not (Test-Path -LiteralPath $setupCompletePath)) {
`
}
func windowsSSHPortsPowerShell(cfg Config) string {
ports := sshPortCandidates(cfg.SSHPort, cfg.SSHFallbackPorts)
quoted := make([]string, 0, len(ports))
for _, port := range ports {
quoted = append(quoted, psQuote(port))
}
return "@(" + strings.Join(quoted, ", ") + ")"
}
func macOSUserData(cfg Config, _ string) string {
workRoot := cfg.WorkRoot
if workRoot == "" {

View File

@ -97,6 +97,10 @@ func TestAWSUserDataWindowsProfile(t *testing.T) {
"OpenSSH-Win64.zip",
"install-sshd.ps1",
"administrators_authorized_keys",
"$sshPorts = @('2222', '22')",
"sshd_config",
"Port $port",
"crabbox-sshd-$port",
"Git-2.52.0-64-bit.exe",
"tightvnc-2.8.85-gpl-setup-64bit.msi",
"VALUE_OF_PASSWORD=$vncPassword",

View File

@ -735,7 +735,7 @@ func (a App) acquireCoordinator(ctx context.Context, cfg Config, coord *Coordina
defer stopHeartbeat()
stopLeaseWatch := startCoordinatorLeaseWatch(waitCtx, coord, leaseID, cancelWait, a.Stderr)
defer stopLeaseWatch()
if err := bootstrapAWSWindowsDesktop(waitCtx, cfg, target, publicKey, a.Stderr); err != nil {
if err := bootstrapAWSWindowsDesktop(waitCtx, cfg, &target, publicKey, a.Stderr); err != nil {
if !keep {
if releaseErr := releaseCoordinatorLease(context.Background(), coord, leaseID); releaseErr != nil {
fmt.Fprintf(a.Stderr, "warning: release failed after bootstrap error for %s: %v\n", leaseID, releaseErr)
@ -1109,7 +1109,7 @@ func (a App) acquireAWS(ctx context.Context, cfg Config, keep bool) (Server, SSH
return Server{}, SSHTarget{}, "", err
}
target := sshTargetFromConfig(cfg, server.PublicNet.IPv4.IP)
if err := bootstrapAWSWindowsDesktop(ctx, cfg, target, publicKey, a.Stderr); err != nil {
if err := bootstrapAWSWindowsDesktop(ctx, cfg, &target, publicKey, a.Stderr); err != nil {
if !keep {
_ = client.DeleteServer(context.Background(), server.CloudID)
}

View File

@ -116,6 +116,7 @@ function New-CrabboxPassword {
$user = ${psQuote(config.sshUser)}
$publicKey = ${psQuote(config.sshPublicKey)}
$workRoot = ${psQuote(config.workRoot)}
$sshPorts = ${windowsSSHPortsPowerShell(config)}
$vncPasswordPath = "C:\\ProgramData\\crabbox\\vnc.password"
$windowsUsernamePath = "C:\\ProgramData\\crabbox\\windows.username"
$windowsPasswordPath = "C:\\ProgramData\\crabbox\\windows.password"
@ -159,8 +160,26 @@ if (-not (Get-Service -Name sshd -ErrorAction SilentlyContinue)) {
New-Item -ItemType Directory -Force -Path "$env:ProgramData\\ssh" | Out-Null
Set-Content -Encoding ASCII -Path "$env:ProgramData\\ssh\\administrators_authorized_keys" -Value $publicKey
icacls.exe "$env:ProgramData\\ssh\\administrators_authorized_keys" /inheritance:r /grant "*S-1-5-32-544:F" /grant "*S-1-5-18:F" | Out-Null
if (-not (Get-NetFirewallRule -Name "crabbox-sshd" -ErrorAction SilentlyContinue)) {
New-NetFirewallRule -Name "crabbox-sshd" -DisplayName "Crabbox OpenSSH" -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 | Out-Null
$sshdConfigPath = "$env:ProgramData\\ssh\\sshd_config"
$sshdConfig = ""
if (Test-Path -LiteralPath $sshdConfigPath) {
$sshdConfig = Get-Content -Raw -LiteralPath $sshdConfigPath
}
$globalLines = @()
$matchLines = @()
$inMatch = $false
foreach ($line in ($sshdConfig -split "\\r?\\n")) {
if ($line -match '^\\s*Match\\s+') { $inMatch = $true }
if (-not $inMatch -and $line -match '^\\s*Port\\s+\\d+\\s*$') { continue }
if ($inMatch) { $matchLines += $line } else { $globalLines += $line }
}
foreach ($port in $sshPorts) { $globalLines += "Port $port" }
Set-Content -Encoding ASCII -LiteralPath $sshdConfigPath -Value (($globalLines + $matchLines) -join [Environment]::NewLine)
foreach ($port in $sshPorts) {
$ruleName = "crabbox-sshd-$port"
if (-not (Get-NetFirewallRule -Name $ruleName -ErrorAction SilentlyContinue)) {
New-NetFirewallRule -Name $ruleName -DisplayName "Crabbox OpenSSH $port" -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort $port | Out-Null
}
}
Set-Service -Name sshd -StartupType Automatic
Start-Service sshd
@ -205,6 +224,12 @@ if (-not (Test-Path -LiteralPath $setupCompletePath)) {
`;
}
function windowsSSHPortsPowerShell(config: LeaseConfig): string {
return `@(${sshPorts(config)
.map((port) => psQuote(port))
.join(", ")})`;
}
export function macOSUserData(config: LeaseConfig): string {
return `#!/bin/bash
set -euxo pipefail

View File

@ -108,6 +108,10 @@ describe("cloud-init bootstrap", () => {
expect(got).toContain("OpenSSH-Win64.zip");
expect(got).toContain("install-sshd.ps1");
expect(got).toContain("administrators_authorized_keys");
expect(got).toContain("$sshPorts = @('2222', '22')");
expect(got).toContain("sshd_config");
expect(got).toContain("Port $port");
expect(got).toContain("crabbox-sshd-$port");
expect(got).toContain("tightvnc-2.8.85-gpl-setup-64bit.msi");
expect(got).toContain("VALUE_OF_PASSWORD=$vncPassword");
expect(got).toContain("VALUE_OF_ALLOWLOOPBACK=1");