forked from wolfSSL/wolfssh
-
Notifications
You must be signed in to change notification settings - Fork 0
253 lines (212 loc) · 8.45 KB
/
paramiko-sftp-test.yml
File metadata and controls
253 lines (212 loc) · 8.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
name: wolfSSH Paramiko SFTP Test
on:
push:
branches: [ 'master', 'main', 'release/**' ]
pull_request:
branches: [ '*' ]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build_wolfssl:
name: Build wolfssl
runs-on: ubuntu-latest
timeout-minutes: 4
steps:
- name: Checking cache for wolfssl
uses: actions/cache@v4
id: cache-wolfssl
with:
path: build-dir/
key: wolfssh-paramiko-sftp-wolfssl-ubuntu-latest
lookup-only: true
- name: Checkout, build, and install wolfssl
if: steps.cache-wolfssl.outputs.cache-hit != 'true'
uses: wolfSSL/actions-build-autotools-project@v1
with:
repository: wolfssl/wolfssl
ref: master
path: wolfssl
configure: --enable-all
check: false
install: true
paramiko_sftp_test:
needs: build_wolfssl
name: Paramiko SFTP Test
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checking cache for wolfssl
uses: actions/cache@v4
with:
path: build-dir/
key: wolfssh-paramiko-sftp-wolfssl-ubuntu-latest
fail-on-cache-miss: true
- uses: actions/checkout@v4
with:
path: wolfssh/
- name: autogen
working-directory: ./wolfssh/
run: ./autogen.sh
- name: configure
working-directory: ./wolfssh/
run: |
./configure --enable-all LDFLAGS="-L${{ github.workspace }}/build-dir/lib" CPPFLAGS="-I${{ github.workspace }}/build-dir/include -DWOLFSSH_NO_FPKI"
- name: make
working-directory: ./wolfssh/
run: make
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y python3-pip openssh-client
python3 -m pip install paramiko
- name: Create test directories
run: |
mkdir -p /tmp/sftp_upload
mkdir -p /tmp/sftp_download
- name: Create 20MB test file for upload
run: |
dd if=/dev/urandom of=/tmp/sftp_upload/test_upload.dat bs=1M count=20
echo "Created 20MB test file at /tmp/sftp_upload/test_upload.dat"
md5sum /tmp/sftp_upload/test_upload.dat
- name: Configure wolfSSHd
working-directory: ./wolfssh/
run: |
# Create a minimal sshd_config file
cat > sshd_config.txt << EOF
Port 22222
HostKey ./keys/server-key.pem
PasswordAuthentication yes
Subsystem sftp internal-sftp
EOF
# Set proper permissions for keys
chmod 600 ./keys/server-key.pem
# Print debug info
echo "Contents of sshd_config.txt:"
cat sshd_config.txt
- name: Start wolfSSHd
working-directory: ./wolfssh/
run: |
# Create a test user with known password
echo "Creating test user..."
sudo useradd -m testuser
echo "testuser:testpassword" | sudo chpasswd
# Start wolfSSHd with debug output
echo "Starting wolfSSHd..."
sudo ./apps/wolfsshd/wolfsshd -f sshd_config.txt -h ./keys/server-key.pem -p 22222 -d &
echo "Started wolfSSHd on port 22222"
sleep 5 # Give the server time to start
# Check if server is running
if ! nc -z 127.0.0.1 22222; then
echo "Error: wolfSSHd failed to start"
exit 1
fi
# Print debug info
echo "wolfSSHd process info:"
ps aux | grep wolfsshd
- name: Create Paramiko SFTP test script
run: |
cat > /tmp/paramiko_sftp_test.py << 'EOF'
import paramiko
import os
import time
import sys
import hashlib
import signal
# Timeout handler for detecting hangs
class TimeoutError(Exception):
pass
def timeout_handler(signum, frame):
raise TimeoutError("SFTP operation timed out - possible hang detected!")
def get_file_hash(filepath):
"""Calculate MD5 hash of a file."""
hash_md5 = hashlib.md5()
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()
def run_sftp_test():
# Create SSH client
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# Connect to server using password authentication with testuser
print("Connecting to wolfSSHd server...")
try:
ssh.connect('127.0.0.1', port=22222, username='testuser', password='testpassword')
except Exception as e:
print(f"Connection error: {e}")
raise
# Open SFTP session
print("Opening SFTP session...")
sftp = ssh.open_sftp()
# Upload test
print("Uploading 20MB test file...")
start_time = time.time()
sftp.put('/tmp/sftp_upload/test_upload.dat', '/tmp/test_upload.dat')
upload_time = time.time() - start_time
print(f"Upload completed in {upload_time:.2f} seconds")
# Download test
print("Downloading 20MB test file...")
start_time = time.time()
sftp.get('/tmp/test_upload.dat', '/tmp/sftp_download/test_download.dat')
download_time = time.time() - start_time
print(f"Download completed in {download_time:.2f} seconds")
# Stress test: Repeated GET operations with prefetch
# This tests the WS_WANT_WRITE handling during repeated transfers
# which was the original bug trigger (SFTP hang on non-blocking sockets)
print("\n=== Starting repeated GET stress test (prefetch enabled) ===")
num_iterations = 10
timeout_seconds = 30 # Per-operation timeout to detect hangs
orig_hash = get_file_hash('/tmp/sftp_upload/test_upload.dat')
for i in range(num_iterations):
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(timeout_seconds)
try:
download_path = f'/tmp/sftp_download/stress_test_{i}.dat'
start_time = time.time()
# Paramiko uses prefetch by default for get()
sftp.get('/tmp/test_upload.dat', download_path)
elapsed = time.time() - start_time
# Verify integrity
down_hash = get_file_hash(download_path)
if orig_hash != down_hash:
print(f" Iteration {i+1}: FAILED - hash mismatch!")
return False
print(f" Iteration {i+1}/{num_iterations}: OK ({elapsed:.2f}s)")
os.remove(download_path) # Cleanup
except TimeoutError as e:
print(f" Iteration {i+1}: FAILED - {e}")
print(" This may indicate the WS_WANT_WRITE hang bug!")
return False
finally:
signal.alarm(0) # Always cancel alarm
print(f"=== Stress test completed: {num_iterations} iterations OK ===\n")
# Close connections
sftp.close()
ssh.close()
print("SFTP session closed")
return True
if __name__ == "__main__":
try:
success = run_sftp_test()
sys.exit(0 if success else 1)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
EOF
- name: Run Paramiko SFTP test
run: |
python3 /tmp/paramiko_sftp_test.py
- name: Verify file integrity
run: |
echo "Verifying file integrity..."
if cmp -s /tmp/sftp_upload/test_upload.dat /tmp/sftp_download/test_download.dat; then
echo "SFTP Test PASSED: Files match"
else
echo "SFTP Test FAILED: Files do not match"
exit 1
fi
- name: Stop wolfSSHd
run: |
sudo pkill wolfsshd || true