|
| 1 | +name: Windows wolfsshd SFTP Test |
| 2 | + |
| 3 | +# This workflow tests wolfsshd and SFTP on Windows with: |
| 4 | +# 1. Basic test: wolfsshd + SFTP client (pwd, ls, put/get small file) |
| 5 | +# 2. Large file test: WOLFSSH_NO_SFTP_TIMEOUT, WOLFSSH_MAX_SFTP_RW=10485760, |
| 6 | +# WOLFSSH_MAX_CHN_NAMESZ=4200 - get and put a 3GB file |
| 7 | + |
| 8 | +on: |
| 9 | + push: |
| 10 | + branches: [ '*' ] |
| 11 | + pull_request: |
| 12 | + branches: [ '*' ] |
| 13 | + |
| 14 | +env: |
| 15 | + WOLFSSL_SOLUTION_FILE_PATH: wolfssl64.sln |
| 16 | + SOLUTION_FILE_PATH: wolfssh.sln |
| 17 | + USER_SETTINGS_H_NEW: wolfssh/ide/winvs/user_settings.h |
| 18 | + USER_SETTINGS_H: wolfssl/IDE/WIN/user_settings.h |
| 19 | + INCLUDE_DIR: wolfssh |
| 20 | + WOLFSSL_BUILD_CONFIGURATION: Release |
| 21 | + WOLFSSH_BUILD_CONFIGURATION: Release |
| 22 | + BUILD_PLATFORM: x64 |
| 23 | + TARGET_PLATFORM: 10 |
| 24 | + TEST_PORT: 22222 |
| 25 | + |
| 26 | +jobs: |
| 27 | + build: |
| 28 | + runs-on: windows-latest |
| 29 | + strategy: |
| 30 | + fail-fast: false |
| 31 | + matrix: |
| 32 | + include: |
| 33 | + - test_type: basic |
| 34 | + artifact_name: wolfssh-windows-build |
| 35 | + - test_type: large_rw |
| 36 | + artifact_name: wolfssh-windows-build-large-rw |
| 37 | + |
| 38 | + steps: |
| 39 | + - uses: actions/checkout@v4 |
| 40 | + with: |
| 41 | + repository: wolfssl/wolfssl |
| 42 | + path: wolfssl |
| 43 | + |
| 44 | + - uses: actions/checkout@v4 |
| 45 | + with: |
| 46 | + path: wolfssh |
| 47 | + |
| 48 | + - name: Add MSBuild to PATH |
| 49 | + uses: microsoft/setup-msbuild@v1 |
| 50 | + |
| 51 | + - name: Update user_settings.h for wolfSSL build |
| 52 | + working-directory: ${{env.GITHUB_WORKSPACE}} |
| 53 | + shell: bash |
| 54 | + run: | |
| 55 | + sed -i 's/#if 0/#if 1/g' ${{env.USER_SETTINGS_H_NEW}} |
| 56 | + cp ${{env.USER_SETTINGS_H_NEW}} ${{env.USER_SETTINGS_H}} |
| 57 | +
|
| 58 | + - name: Restore wolfSSL NuGet packages |
| 59 | + working-directory: ${{ github.workspace }}\wolfssl |
| 60 | + run: nuget restore ${{env.WOLFSSL_SOLUTION_FILE_PATH}} |
| 61 | + |
| 62 | + - name: Build wolfssl library |
| 63 | + working-directory: ${{ github.workspace }}\wolfssl |
| 64 | + run: msbuild /m /p:PlatformToolset=v142 /p:Platform=${{env.BUILD_PLATFORM}} /p:Configuration=${{env.WOLFSSL_BUILD_CONFIGURATION}} /t:wolfssl ${{env.WOLFSSL_SOLUTION_FILE_PATH}} |
| 65 | + |
| 66 | + - name: Upload wolfSSL build artifacts |
| 67 | + uses: actions/upload-artifact@v4 |
| 68 | + with: |
| 69 | + name: wolfssl-windows-build |
| 70 | + path: | |
| 71 | + wolfssl/IDE/WIN/${{env.WOLFSSL_BUILD_CONFIGURATION}}/${{env.BUILD_PLATFORM}}/** |
| 72 | + wolfssl/IDE/WIN/${{env.WOLFSSL_BUILD_CONFIGURATION}}/** |
| 73 | + wolfssl/${{env.WOLFSSL_BUILD_CONFIGURATION}}/${{env.BUILD_PLATFORM}}/** |
| 74 | + wolfssl/${{env.WOLFSSL_BUILD_CONFIGURATION}}/** |
| 75 | +
|
| 76 | + - name: Update user_settings.h for sshd and SFTP |
| 77 | + working-directory: ${{env.GITHUB_WORKSPACE}} |
| 78 | + shell: bash |
| 79 | + run: | |
| 80 | + # Enable SSHD, SFTP support (second #if 0 block) |
| 81 | + sed -i 's/#if 0/#if 1/g' ${{env.USER_SETTINGS_H_NEW}} |
| 82 | + cp ${{env.USER_SETTINGS_H_NEW}} ${{env.USER_SETTINGS_H}} |
| 83 | + # For large_rw test: add SFTP large file defines |
| 84 | + if [ "${{ matrix.test_type }}" = "large_rw" ]; then |
| 85 | + echo "" >> ${{env.USER_SETTINGS_H}} |
| 86 | + echo "/* SFTP large file test defines */" >> ${{env.USER_SETTINGS_H}} |
| 87 | + echo "#define WOLFSSH_NO_SFTP_TIMEOUT" >> ${{env.USER_SETTINGS_H}} |
| 88 | + echo "#define WOLFSSH_MAX_SFTP_RW 10485760" >> ${{env.USER_SETTINGS_H}} |
| 89 | + echo "#define WOLFSSH_MAX_CHN_NAMESZ 4200" >> ${{env.USER_SETTINGS_H}} |
| 90 | + echo "Added WOLFSSH_NO_SFTP_TIMEOUT, WOLFSSH_MAX_SFTP_RW=10485760, WOLFSSH_MAX_CHN_NAMESZ=4200" |
| 91 | + fi |
| 92 | +
|
| 93 | + - name: Restore NuGet packages |
| 94 | + working-directory: ${{ github.workspace }}\wolfssh\ide\winvs |
| 95 | + run: nuget restore ${{env.SOLUTION_FILE_PATH}} |
| 96 | + |
| 97 | + - name: Build wolfssh |
| 98 | + working-directory: ${{ github.workspace }}\wolfssh\ide\winvs |
| 99 | + run: msbuild /m /p:PlatformToolset=v142 /p:Platform=${{env.BUILD_PLATFORM}} /p:WindowsTargetPlatformVersion=${{env.TARGET_PLATFORM}} /p:Configuration=${{env.WOLFSSH_BUILD_CONFIGURATION}} ${{env.SOLUTION_FILE_PATH}} |
| 100 | + |
| 101 | + - name: Upload wolfSSH build artifacts |
| 102 | + uses: actions/upload-artifact@v4 |
| 103 | + with: |
| 104 | + name: ${{ matrix.artifact_name }} |
| 105 | + if-no-files-found: error |
| 106 | + path: | |
| 107 | + wolfssh/ide/winvs/**/Release/** |
| 108 | +
|
| 109 | + test: |
| 110 | + needs: build |
| 111 | + runs-on: windows-latest |
| 112 | + strategy: |
| 113 | + fail-fast: false |
| 114 | + matrix: |
| 115 | + include: |
| 116 | + - test_type: basic |
| 117 | + artifact_name: wolfssh-windows-build |
| 118 | + - test_type: large_rw |
| 119 | + artifact_name: wolfssh-windows-build-large-rw |
| 120 | + |
| 121 | + steps: |
| 122 | + - uses: actions/checkout@v4 |
| 123 | + with: |
| 124 | + path: wolfssh |
| 125 | + |
| 126 | + - name: Download wolfSSH build artifacts |
| 127 | + uses: actions/download-artifact@v4 |
| 128 | + with: |
| 129 | + name: ${{ matrix.artifact_name }} |
| 130 | + path: . |
| 131 | + |
| 132 | + - name: Download wolfSSL build artifacts |
| 133 | + uses: actions/download-artifact@v4 |
| 134 | + with: |
| 135 | + name: wolfssl-windows-build |
| 136 | + path: . |
| 137 | + |
| 138 | + - name: Create Windows user testuser and authorized_keys |
| 139 | + working-directory: ${{ github.workspace }}\wolfssh |
| 140 | + shell: pwsh |
| 141 | + run: | |
| 142 | + $homeDir = "C:\Users\testuser" |
| 143 | + $sshDir = "$homeDir\.ssh" |
| 144 | + $authKeysFile = "$sshDir\authorized_keys" |
| 145 | + $pw = 'T3stP@ss!xY9' |
| 146 | +
|
| 147 | + New-Item -ItemType Directory -Path $homeDir -Force | Out-Null |
| 148 | + New-Item -ItemType Directory -Path $sshDir -Force | Out-Null |
| 149 | +
|
| 150 | + $o = net user testuser $pw /add /homedir:$homeDir 2>&1 |
| 151 | + if ($LASTEXITCODE -ne 0) { |
| 152 | + if ($o -match "already exists") { |
| 153 | + net user testuser /homedir:$homeDir 2>$null |
| 154 | + } else { |
| 155 | + Write-Host "net user failed: $o" |
| 156 | + exit 1 |
| 157 | + } |
| 158 | + } |
| 159 | + Add-Content -Path $env:GITHUB_ENV -Value "TESTUSER_PASSWORD=$pw" |
| 160 | +
|
| 161 | + "" | Out-File -FilePath $authKeysFile -Encoding ASCII -NoNewline |
| 162 | + icacls $authKeysFile /grant "testuser:R" /q |
| 163 | +
|
| 164 | + $sid = (New-Object System.Security.Principal.NTAccount("testuser")).Translate([System.Security.Principal.SecurityIdentifier]).Value |
| 165 | + $profKey = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$sid" |
| 166 | + if (-not (Test-Path $profKey)) { New-Item -Path $profKey -Force | Out-Null } |
| 167 | + Set-ItemProperty -Path $profKey -Name "ProfileImagePath" -Value $homeDir -Force |
| 168 | +
|
| 169 | + - name: Create wolfSSHd config file |
| 170 | + working-directory: ${{ github.workspace }}\wolfssh |
| 171 | + shell: pwsh |
| 172 | + run: | |
| 173 | + $keyPath = Join-Path "${{ github.workspace }}" "wolfssh\keys\server-key.pem" |
| 174 | + $keyPathFull = (Resolve-Path $keyPath -ErrorAction Stop) |
| 175 | + $configContent = @" |
| 176 | + Port ${{env.TEST_PORT}} |
| 177 | + PasswordAuthentication yes |
| 178 | + PermitRootLogin yes |
| 179 | + HostKey $($keyPathFull.Path) |
| 180 | + AuthorizedKeysFile C:\Users\testuser\.ssh\authorized_keys |
| 181 | + "@ |
| 182 | + $configContent | Out-File -FilePath sshd_config_test -Encoding ASCII |
| 183 | + Get-Content sshd_config_test |
| 184 | +
|
| 185 | + - name: Find wolfSSH executables |
| 186 | + working-directory: ${{ github.workspace }}\wolfssh |
| 187 | + shell: pwsh |
| 188 | + run: | |
| 189 | + $searchRoot = "${{ github.workspace }}" |
| 190 | + $sshdExe = Get-ChildItem -Path $searchRoot -Recurse -Filter "wolfsshd.exe" -ErrorAction SilentlyContinue | |
| 191 | + Where-Object { $_.FullName -like "*Release*" } | Select-Object -First 1 |
| 192 | + if ($sshdExe) { |
| 193 | + Add-Content -Path $env:GITHUB_ENV -Value "SSHD_PATH=$($sshdExe.FullName)" |
| 194 | + } else { |
| 195 | + Write-Host "ERROR: wolfsshd.exe not found" |
| 196 | + exit 1 |
| 197 | + } |
| 198 | +
|
| 199 | + $sftpExe = Get-ChildItem -Path $searchRoot -Recurse -Filter "wolfsftp.exe" -ErrorAction SilentlyContinue | |
| 200 | + Where-Object { $_.FullName -like "*Release*" } | Select-Object -First 1 |
| 201 | + if (-not $sftpExe) { |
| 202 | + $sftpExe = Get-ChildItem -Path $searchRoot -Recurse -Filter "wolfsftp-client.exe" -ErrorAction SilentlyContinue | |
| 203 | + Where-Object { $_.FullName -like "*Release*" } | Select-Object -First 1 |
| 204 | + } |
| 205 | + if ($sftpExe) { |
| 206 | + Add-Content -Path $env:GITHUB_ENV -Value "SFTP_PATH=$($sftpExe.FullName)" |
| 207 | + } else { |
| 208 | + Write-Host "ERROR: SFTP client exe not found" |
| 209 | + exit 1 |
| 210 | + } |
| 211 | +
|
| 212 | + - name: Copy wolfSSL DLL to executable directory |
| 213 | + working-directory: ${{ github.workspace }} |
| 214 | + shell: pwsh |
| 215 | + run: | |
| 216 | + $sshdPath = $env:SSHD_PATH |
| 217 | + $sshdDir = Split-Path -Parent $sshdPath |
| 218 | + if (Test-Path (Join-Path $sshdDir "wolfssl.lib")) { exit 0 } |
| 219 | + $wolfsslDll = Get-ChildItem -Path "${{ github.workspace }}\wolfssl" -Recurse -Filter "wolfssl.dll" -ErrorAction SilentlyContinue | Select-Object -First 1 |
| 220 | + if ($wolfsslDll) { |
| 221 | + Copy-Item -Path $wolfsslDll.FullName -Destination (Join-Path $sshdDir "wolfssl.dll") -Force |
| 222 | + } |
| 223 | +
|
| 224 | + - name: Grant service access to config and keys |
| 225 | + working-directory: ${{ github.workspace }}\wolfssh |
| 226 | + shell: pwsh |
| 227 | + run: | |
| 228 | + icacls (Get-Location).Path /grant "NT AUTHORITY\SYSTEM:(OI)(CI)RX" /T /q |
| 229 | +
|
| 230 | + - name: Start wolfSSHd as Windows service |
| 231 | + working-directory: ${{ github.workspace }}\wolfssh |
| 232 | + shell: pwsh |
| 233 | + run: | |
| 234 | + $sshdPath = $env:SSHD_PATH |
| 235 | + $configPathFull = (Resolve-Path "sshd_config_test").Path |
| 236 | + $serviceName = "wolfsshd" |
| 237 | +
|
| 238 | + $existingService = Get-Service -Name $serviceName -ErrorAction SilentlyContinue |
| 239 | + if ($existingService) { |
| 240 | + if ($existingService.Status -eq 'Running') { Stop-Service -Name $serviceName -Force } |
| 241 | + sc.exe delete $serviceName | Out-Null |
| 242 | + Start-Sleep -Seconds 2 |
| 243 | + } |
| 244 | +
|
| 245 | + $binPath = "`"$sshdPath`" -f `"$configPathFull`" -p ${{env.TEST_PORT}}" |
| 246 | + sc.exe create $serviceName binPath= $binPath |
| 247 | + sc.exe start $serviceName |
| 248 | + Start-Sleep -Seconds 5 |
| 249 | +
|
| 250 | + $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue |
| 251 | + if ($service.Status -ne 'Running') { |
| 252 | + Write-Host "ERROR: Service failed to start" |
| 253 | + sc.exe query $serviceName |
| 254 | + exit 1 |
| 255 | + } |
| 256 | + Add-Content -Path $env:GITHUB_ENV -Value "SSHD_SERVICE_NAME=$serviceName" |
| 257 | +
|
| 258 | + - name: Test SFTP get non-existent file (no hang, correct error) |
| 259 | + working-directory: ${{ github.workspace }}\wolfssh |
| 260 | + shell: pwsh |
| 261 | + timeout-minutes: 1 |
| 262 | + run: | |
| 263 | + $sftpPath = $env:SFTP_PATH |
| 264 | + $getCommands = "get /this_file_does_not_exist_xyz /tmp/copy.dat`nquit" |
| 265 | + $getCommands | Out-File -FilePath sftp_get_nonexistent_commands.txt -Encoding ASCII |
| 266 | +
|
| 267 | + $proc = Start-Process -FilePath $sftpPath ` |
| 268 | + -ArgumentList "-u", "testuser", "-P", $env:TESTUSER_PASSWORD, "-h", "localhost", "-p", "${{env.TEST_PORT}}" ` |
| 269 | + -RedirectStandardInput "sftp_get_nonexistent_commands.txt" ` |
| 270 | + -RedirectStandardOutput "sftp_get_nonexistent_out.txt" ` |
| 271 | + -RedirectStandardError "sftp_get_nonexistent_err.txt" ` |
| 272 | + -Wait -NoNewWindow -PassThru |
| 273 | +
|
| 274 | + Write-Host "=== SFTP Output ===" |
| 275 | + if (Test-Path sftp_get_nonexistent_out.txt) { Get-Content sftp_get_nonexistent_out.txt } |
| 276 | + Write-Host "=== SFTP Error ===" |
| 277 | + if (Test-Path sftp_get_nonexistent_err.txt) { Get-Content sftp_get_nonexistent_err.txt } |
| 278 | +
|
| 279 | + if ($proc.ExitCode -eq 0) { |
| 280 | + Write-Host "ERROR: Expected non-zero exit for get of non-existent file (got 0)" |
| 281 | + exit 1 |
| 282 | + } |
| 283 | + Write-Host "PASS: SFTP get non-existent file failed correctly (exit $($proc.ExitCode)), did not hang" |
| 284 | +
|
| 285 | + - name: Test SFTP connection (basic) |
| 286 | + if: matrix.test_type == 'basic' |
| 287 | + working-directory: ${{ github.workspace }}\wolfssh |
| 288 | + shell: pwsh |
| 289 | + run: | |
| 290 | + $sftpPath = $env:SFTP_PATH |
| 291 | + $testCommands = "pwd`nls`nquit" |
| 292 | + $testCommands | Out-File -FilePath sftp_commands.txt -Encoding ASCII |
| 293 | +
|
| 294 | + $process = Start-Process -FilePath $sftpPath ` |
| 295 | + -ArgumentList "-u", "testuser", "-P", $env:TESTUSER_PASSWORD, "-h", "localhost", "-p", "${{env.TEST_PORT}}" ` |
| 296 | + -RedirectStandardInput "sftp_commands.txt" ` |
| 297 | + -RedirectStandardOutput "sftp_output.txt" ` |
| 298 | + -RedirectStandardError "sftp_error.txt" ` |
| 299 | + -Wait -NoNewWindow -PassThru |
| 300 | +
|
| 301 | + Get-Content sftp_output.txt |
| 302 | + Get-Content sftp_error.txt |
| 303 | + if ($process.ExitCode -ne 0) { |
| 304 | + Write-Host "ERROR: SFTP basic test failed with exit $($process.ExitCode)" |
| 305 | + exit 1 |
| 306 | + } |
| 307 | + Write-Host "Basic SFTP test passed" |
| 308 | +
|
| 309 | + - name: Create 3GB test file and run SFTP get/put (large_rw) |
| 310 | + if: matrix.test_type == 'large_rw' |
| 311 | + working-directory: ${{ github.workspace }}\wolfssh |
| 312 | + shell: pwsh |
| 313 | + timeout-minutes: 25 |
| 314 | + run: | |
| 315 | + $sftpPath = $env:SFTP_PATH |
| 316 | + $workDir = (Get-Location).Path |
| 317 | + $largeFile = Join-Path $workDir "large_test.dat" |
| 318 | + $getDest = Join-Path $workDir "large_test_copy.dat" |
| 319 | +
|
| 320 | + # Create 3GB file (3072 MB) |
| 321 | + Write-Host "Creating 3GB test file..." |
| 322 | + $fs = [System.IO.File]::Create($largeFile) |
| 323 | + $rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider |
| 324 | + $buf = New-Object byte[] 10485760 # 10MB chunks |
| 325 | + $remaining = 3221225472 # 3GB |
| 326 | + while ($remaining -gt 0) { |
| 327 | + $rng.GetBytes($buf) |
| 328 | + $toWrite = [Math]::Min($buf.Length, $remaining) |
| 329 | + $fs.Write($buf, 0, $toWrite) |
| 330 | + $remaining -= $toWrite |
| 331 | + } |
| 332 | + $fs.Close() |
| 333 | +
|
| 334 | + $hash = Get-FileHash -Path $largeFile -Algorithm SHA256 |
| 335 | + $hash.Hash | Out-File -FilePath large_test.dat.sha256 |
| 336 | + Write-Host "Created 3GB file, SHA256: $($hash.Hash)" |
| 337 | +
|
| 338 | + # SFTP PUT (upload) |
| 339 | + Write-Host "SFTP PUT 3GB file..." |
| 340 | + $putCommands = "put $largeFile /large_test.dat`nquit" |
| 341 | + $putCommands | Out-File -FilePath sftp_put_commands.txt -Encoding ASCII |
| 342 | + $proc = Start-Process -FilePath $sftpPath ` |
| 343 | + -ArgumentList "-u", "testuser", "-P", $env:TESTUSER_PASSWORD, "-h", "localhost", "-p", "${{env.TEST_PORT}}" ` |
| 344 | + -RedirectStandardInput "sftp_put_commands.txt" ` |
| 345 | + -RedirectStandardOutput "sftp_put_out.txt" ` |
| 346 | + -RedirectStandardError "sftp_put_err.txt" ` |
| 347 | + -Wait -NoNewWindow -PassThru |
| 348 | +
|
| 349 | + if ($proc.ExitCode -ne 0) { |
| 350 | + Get-Content sftp_put_out.txt |
| 351 | + Get-Content sftp_put_err.txt |
| 352 | + Write-Host "ERROR: SFTP PUT failed" |
| 353 | + exit 1 |
| 354 | + } |
| 355 | + Write-Host "PUT succeeded" |
| 356 | +
|
| 357 | + # SFTP GET (download) |
| 358 | + Write-Host "SFTP GET 3GB file..." |
| 359 | + $getCommands = "get /large_test.dat $getDest`nquit" |
| 360 | + $getCommands | Out-File -FilePath sftp_get_commands.txt -Encoding ASCII |
| 361 | + $proc2 = Start-Process -FilePath $sftpPath ` |
| 362 | + -ArgumentList "-u", "testuser", "-P", $env:TESTUSER_PASSWORD, "-h", "localhost", "-p", "${{env.TEST_PORT}}" ` |
| 363 | + -RedirectStandardInput "sftp_get_commands.txt" ` |
| 364 | + -RedirectStandardOutput "sftp_get_out.txt" ` |
| 365 | + -RedirectStandardError "sftp_get_err.txt" ` |
| 366 | + -Wait -NoNewWindow -PassThru |
| 367 | +
|
| 368 | + if ($proc2.ExitCode -ne 0) { |
| 369 | + Get-Content sftp_get_out.txt |
| 370 | + Get-Content sftp_get_err.txt |
| 371 | + Write-Host "ERROR: SFTP GET failed" |
| 372 | + exit 1 |
| 373 | + } |
| 374 | + Write-Host "GET succeeded" |
| 375 | +
|
| 376 | + # Verify integrity |
| 377 | + $expectedHash = (Get-Content large_test.dat.sha256).Trim() |
| 378 | + $actualHash = (Get-FileHash -Path $getDest -Algorithm SHA256).Hash |
| 379 | + if ($expectedHash -ne $actualHash) { |
| 380 | + Write-Host "ERROR: SHA256 mismatch - PUT/GET corruption" |
| 381 | + Write-Host "Expected: $expectedHash" |
| 382 | + Write-Host "Actual: $actualHash" |
| 383 | + exit 1 |
| 384 | + } |
| 385 | + Write-Host "PASS: 3GB SFTP get/put with WOLFSSH_MAX_SFTP_RW=10485760 succeeded" |
| 386 | +
|
| 387 | + - name: Cleanup |
| 388 | + if: always() |
| 389 | + working-directory: ${{ github.workspace }}\wolfssh |
| 390 | + shell: pwsh |
| 391 | + run: | |
| 392 | + $serviceName = $env:SSHD_SERVICE_NAME |
| 393 | + if (-not $serviceName) { $serviceName = "wolfsshd" } |
| 394 | + $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue |
| 395 | + if ($service) { |
| 396 | + if ($service.Status -eq 'Running') { Stop-Service -Name $serviceName -Force } |
| 397 | + sc.exe delete $serviceName | Out-Null |
| 398 | + } |
0 commit comments