docs: upkeep #54
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: TLS | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| jobs: | |
| wss: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: julia-actions/setup-julia@v2 | |
| with: | |
| version: '1' | |
| - uses: julia-actions/cache@v2 | |
| - uses: julia-actions/julia-buildpkg@v1 | |
| - name: Instantiate test env | |
| run: julia --project=test -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' | |
| # Self-signed cert valid for localhost. The SDK is configured with | |
| # tls_verify=false to accept the self-signed CA — this is exactly | |
| # the toggle that production users would NEVER set, exercised here | |
| # so we can confirm the wss:// path itself works end-to-end. | |
| - name: Generate self-signed cert | |
| run: | | |
| openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem \ | |
| -days 1 -subj "/CN=localhost" \ | |
| -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" | |
| # surrealdb container runs as a non-root user; the bind-mounted | |
| # cert files inherit the host's file mode, so we need them | |
| # world-readable for the container to load them. | |
| chmod 0644 cert.pem key.pem | |
| - name: Start SurrealDB with TLS | |
| run: | | |
| docker run -d -p 8443:8443 \ | |
| --name surrealdb-tls \ | |
| -e SURREAL_LOG=debug \ | |
| -e RUST_LOG=surrealdb=debug,surrealdb_server=debug,surrealdb::net=trace \ | |
| -v $PWD/cert.pem:/cert.pem \ | |
| -v $PWD/key.pem:/key.pem \ | |
| surrealdb/surrealdb:latest \ | |
| start --user root --pass root \ | |
| --bind 0.0.0.0:8443 \ | |
| --web-crt /cert.pem --web-key /key.pem \ | |
| --log debug \ | |
| memory | |
| - name: Wait for SurrealDB TLS port | |
| run: | | |
| for i in $(seq 1 30); do | |
| curl -sk -X POST https://localhost:8443/rpc \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"id":1,"method":"version","params":[]}' && break | |
| sleep 1 | |
| done | |
| # Raw WSS upgrade probe — captures the server's response headers on the | |
| # WebSocket upgrade so we can see what the v3+TLS server returns | |
| # (Sec-WebSocket-Accept, Sec-WebSocket-Protocol, body after 101, etc). | |
| # Helps debug "first RPC drops with Connection lost mid-request" — the | |
| # SDK sees a 101 then a dead socket; this surfaces what's between. | |
| # | |
| # openssl s_client -quiet implies -ign_eof, so it won't exit when our | |
| # stdin (the printf | sleep subshell) closes — the connection stays | |
| # open and `head -c 4096` blocks indefinitely waiting for bytes the | |
| # server may never send. `timeout 5` bounds the subprocess so the | |
| # downstream `xxd | head` gets EOF and the step actually completes. | |
| - name: Raw WSS upgrade probe (openssl s_client) | |
| if: always() | |
| run: | | |
| KEY=$(python3 -c 'import os, base64; print(base64.b64encode(os.urandom(16)).decode())') | |
| echo "=== probe A: with Sec-WebSocket-Protocol: json ===" | |
| (printf 'GET /rpc HTTP/1.1\r\nHost: localhost:8443\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: %s\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Protocol: json\r\n\r\n' "$KEY"; sleep 2) \ | |
| | timeout 5 openssl s_client -connect localhost:8443 -servername localhost -quiet -verify_quiet 2>/dev/null \ | |
| | head -c 4096 | xxd | head -60 || true | |
| echo | |
| echo "=== probe B: without Sec-WebSocket-Protocol header ===" | |
| (printf 'GET /rpc HTTP/1.1\r\nHost: localhost:8443\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: %s\r\nSec-WebSocket-Version: 13\r\n\r\n' "$KEY"; sleep 2) \ | |
| | timeout 5 openssl s_client -connect localhost:8443 -servername localhost -quiet -verify_quiet 2>/dev/null \ | |
| | head -c 4096 | xxd | head -60 || true | |
| # Verify the SDK actually completes a TLS handshake end-to-end on | |
| # both wire formats. Connecting + running a query is enough to prove | |
| # the wss:// path itself. The two-wire sweep guards against | |
| # binary-frame regressions over OpenSSL.SSLStream (CBOR) and keeps | |
| # the s8-stabilized JSON-over-TLS path under regression coverage now | |
| # that CBOR is the default. JULIA_DEBUG surfaces per-frame logging | |
| # from _ws_reader_task — needed to diagnose post-101 byte arrival. | |
| - name: Connect via wss:// (cbor) and run a query | |
| env: | |
| JULIA_DEBUG: SurrealDB | |
| run: | | |
| julia --project=test -e ' | |
| using SurrealDB | |
| db = SurrealDB.connect("wss://localhost:8443"; | |
| ns="test", db="test", | |
| auth=SurrealDB.RootAuth("root", "root"), | |
| tls_verify=false, wire=:cbor) | |
| r = SurrealDB.query(db, "SELECT * FROM 1") | |
| @info "wss://+cbor roundtrip ok" result=r | |
| SurrealDB.close!(db) | |
| ' | |
| - name: Connect via wss:// (json) and run a query | |
| env: | |
| JULIA_DEBUG: SurrealDB | |
| run: | | |
| julia --project=test -e ' | |
| using SurrealDB | |
| db = SurrealDB.connect("wss://localhost:8443"; | |
| ns="test", db="test", | |
| auth=SurrealDB.RootAuth("root", "root"), | |
| tls_verify=false, wire=:json) | |
| r = SurrealDB.query(db, "SELECT * FROM 1") | |
| @info "wss://+json roundtrip ok" result=r | |
| SurrealDB.close!(db) | |
| ' | |
| - name: Docker logs (always) | |
| if: always() | |
| run: docker logs surrealdb-tls 2>&1 | tail -100 |