fix(28): CR-01/CR-02/WR-03/WR-05 harden run.py agent handling #93
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: Integration Tests | |
| # Runs on a GitHub-hosted windows-latest runner. | |
| # WSL2 is initialized and runs Docker daemon natively. | |
| # The custom published Docker image runs with --network host passthrough. | |
| # SSH delivery goes from Docker(WSL) → Windows. | |
| # Beacon callbacks go from Windows → Docker(WSL). | |
| on: | |
| push: | |
| pull_request: | |
| workflow_dispatch: | |
| env: | |
| CI_USER: ci_runner | |
| CI_PASS: Ci_Test_Pass1! | |
| CI_AGENT_DIR: 'C:\ci' | |
| CI_AGENT_PATH: 'C:\ci\agent.exe' | |
| CI_CONTAINER: 'ghcr.io/thegr3atjosh/adaptix-prebuilt:latest' | |
| jobs: | |
| integration-test: | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Pass CI variables into WSL | |
| shell: powershell | |
| run: echo "WSLENV=CI_USER/u:CI_PASS/u:CI_AGENT_PATH/u:CI_CONTAINER/u" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append | |
| # ── Windows: CI user, OpenSSH, tmp directory ──────────────────────────── | |
| # C:\ci (agent drop dir) and Defender/firewall config are handled by the | |
| # SSH preamble after connecting — these steps only do what must exist | |
| # before SSH is available. | |
| - name: Create CI user | |
| shell: powershell | |
| run: | | |
| $pass = ConvertTo-SecureString $env:CI_PASS -AsPlainText -Force | |
| if (-not (Get-LocalUser $env:CI_USER -ErrorAction SilentlyContinue)) { | |
| New-LocalUser $env:CI_USER -Password $pass -PasswordNeverExpires | |
| Add-LocalGroupMember -Group Administrators -Member $env:CI_USER | |
| } else { | |
| Set-LocalUser $env:CI_USER -Password $pass | |
| } | |
| - name: Start OpenSSH Server with password auth | |
| shell: powershell | |
| run: | | |
| $cap = Get-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 | |
| if ($cap.State -ne 'Installed') { | |
| Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 | |
| } | |
| Set-Service sshd -StartupType Automatic | |
| Start-Service sshd | |
| $cfg = "$env:ProgramData\ssh\sshd_config" | |
| (Get-Content $cfg) ` | |
| -replace '^#?PasswordAuthentication\s+\w+', 'PasswordAuthentication yes' | | |
| Set-Content $cfg | |
| Restart-Service sshd | |
| - name: Create tmp directory for SSH key transfer | |
| shell: powershell | |
| run: New-Item -ItemType Directory -Force -Path C:\tmp | Out-Null | |
| # ── WSL: Ubuntu + Docker ──────────────────────────────────────────────── | |
| - uses: Vampire/setup-wsl@v7 | |
| with: | |
| distribution: Ubuntu-24.04 | |
| # Install Docker inside WSL | |
| additional-packages: docker.io iproute2 curl | |
| - name: Start Docker daemon in WSL | |
| shell: wsl-bash {0} | |
| run: | | |
| # WSL compatibility for Docker | |
| sudo update-alternatives --set iptables /usr/sbin/iptables-legacy || true | |
| sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy || true | |
| # Start dockerd in the background | |
| sudo dockerd > /dev/null 2>&1 & | |
| echo "Waiting for Docker to start..." | |
| for i in $(seq 1 30); do | |
| if sudo docker info >/dev/null 2>&1; then | |
| echo "Docker started successfully." | |
| break | |
| fi | |
| sleep 1 | |
| done | |
| sudo docker info >/dev/null 2>&1 || { echo "Docker failed to start"; exit 1; } | |
| # ── WSL: SSH setup ─────────────────────────────────────────────────────── | |
| - name: Generate SSH keypair | |
| shell: wsl-bash {0} | |
| run: | | |
| ssh-keygen -t ed25519 -N "" -f ~/.ssh/ci_key | |
| cp ~/.ssh/ci_key.pub /mnt/c/tmp/ci_key.pub | |
| - name: Install SSH public key on Windows | |
| shell: powershell | |
| run: | | |
| $authFile = "$env:ProgramData\ssh\administrators_authorized_keys" | |
| New-Item -Force -ItemType File -Path $authFile | Out-Null | |
| Get-Content C:\tmp\ci_key.pub | Add-Content $authFile | |
| icacls $authFile /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F" | |
| # ── WSL: config + Execution ────────────────────────────────────────────── | |
| - name: Write CI config | |
| shell: wsl-bash {0} | |
| run: | | |
| WSL_IP=$(ip route get 1.1.1.1 2>/dev/null | grep -oP 'src \K\S+' | head -1) | |
| WSL_GW=$(ip route show default 2>/dev/null | awk 'NR==1{print $3}') | |
| WINDOWS_IP=$(cmd.exe /c ipconfig 2>/dev/null | tr -d '\r' | awk '/vEthernet.*WSL/{f=1} f && /IPv4 Address/{print $NF; exit}') | |
| if [ -z "$WINDOWS_IP" ] || ip addr show 2>/dev/null | grep -qF "$WINDOWS_IP"; then | |
| CALLBACK_HOST="127.0.0.1" | |
| SSH_HOST="127.0.0.1" | |
| echo "=== WSL2 mirrored mode: WSL_IP=$WSL_IP WINDOWS_IP=${WINDOWS_IP:-none}, using localhost ===" | |
| else | |
| CALLBACK_HOST="$WSL_IP" | |
| SSH_HOST="${WSL_GW:-$WINDOWS_IP}" | |
| echo "=== WSL2 NAT mode: WSL_IP=$WSL_IP WSL_GW=$WSL_GW WINDOWS_IP=$WINDOWS_IP, SSH→$SSH_HOST ===" | |
| fi | |
| cat > /tmp/ci_config.yaml << EOF | |
| server: | |
| url: https://127.0.0.1:4321 | |
| endpoint: /endpoint | |
| operator: | |
| name: ci | |
| password: pass | |
| setup: | |
| project: ci | |
| agent_output: /tmp/ci_agent.exe | |
| listener: | |
| name: ci_http | |
| type: BeaconHTTP | |
| config: | |
| host_bind: "0.0.0.0" | |
| port_bind: 8080 | |
| callback_addresses: | |
| - "$CALLBACK_HOST:8080" | |
| http_method: POST | |
| uri: | |
| - /beacon | |
| user_agent: | |
| - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" | |
| hb_header: "X-Beacon-Id" | |
| encrypt_key: "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" | |
| ssl: false | |
| page-payload: '{"status":"ok","data":"<<<PAYLOAD_DATA>>>","metrics":"sync"}' | |
| agent: | |
| agent: beacon | |
| listener: ci_http | |
| listener_type: BeaconHTTP | |
| config: | |
| arch: x64 | |
| format: Exe | |
| sleep: "0s" | |
| jitter: 0 | |
| ssh: | |
| host: "$SSH_HOST" | |
| username: "$CI_USER" | |
| key_path: /root/.ssh/ci_key | |
| source_path: /tmp/ci_agent.exe | |
| agent_path: '$CI_AGENT_PATH' | |
| terminate: true | |
| preamble: | |
| - 'New-Item -ItemType Directory -Force -Path C:\ci | Out-Null' | |
| - 'Set-MpPreference -DisableRealtimeMonitoring \$true' | |
| - 'Add-MpPreference -ExclusionPath C:\ci' | |
| - 'New-NetFirewallRule -DisplayName CI_C2_8080 -Direction Inbound -Protocol TCP -LocalPort 8080 -Action Allow -Profile Any | Out-Null' | |
| EOF | |
| - name: Pull Adaptix Container | |
| shell: wsl-bash {0} | |
| run: sudo docker pull "$CI_CONTAINER" | |
| - name: Run CI Container (Server + Tests) | |
| shell: wsl-bash {0} | |
| run: | | |
| WIN_WS=$(cmd.exe /c "echo %GITHUB_WORKSPACE%" 2>/dev/null | tr -d '\r') | |
| WSL_WS=$(wslpath "$WIN_WS") | |
| sudo docker run --rm --network host \ | |
| -v "$WSL_WS:/workspace" \ | |
| -v ~/.ssh/ci_key:/root/.ssh/ci_key:ro \ | |
| -v /tmp/ci_config.yaml:/tmp/ci_config.yaml:ro \ | |
| "$CI_CONTAINER" \ | |
| bash -c ' | |
| export PATH="/usr/local/bin:/root/.local/bin:$PATH" | |
| uv tool install /workspace --reinstall | |
| # ── Feature validation (no server required) ────────────────── | |
| echo "=== Feature validation ===" | |
| adaptix-testing --help 2>&1 | grep -q -- "-o" && \ | |
| echo "✓ --output flag available" || \ | |
| { echo "✗ --output flag missing from CLI"; exit 1; } | |
| echo "=== Feature validation passed ===" | |
| # ── Server startup ─────────────────────────────────────────── | |
| echo "Generating required TLS certificate..." | |
| openssl req -x509 -nodes -newkey rsa:2048 \ | |
| -keyout /tmp/adaptixc2/dist/server.rsa.key \ | |
| -out /tmp/adaptixc2/dist/server.rsa.crt \ | |
| -days 1 -subj "/CN=ci" 2>/dev/null | |
| echo "Starting AdaptixC2 Server..." | |
| cd /tmp/adaptixc2/dist | |
| ./adaptixserver -profile profile.yaml > /tmp/adaptixserver.log 2>&1 & | |
| SERVER_PID=$! | |
| # Wait up to 60s for the C2 to boot up fully | |
| for i in $(seq 1 60); do | |
| (exec 3<>/dev/tcp/127.0.0.1/4321) 2>/dev/null && break | |
| sleep 1 | |
| done | |
| (exec 3<>/dev/tcp/127.0.0.1/4321) 2>/dev/null || { | |
| echo "=== AdaptixC2 server failed to start within 60s ===" | |
| cat /tmp/adaptixserver.log | |
| exit 1 | |
| } | |
| # ── Integration tests (exercises --output and preamble) ────── | |
| echo "AdaptixC2 Ready! Running integration tests..." | |
| adaptix-testing -c /tmp/ci_config.yaml -t /workspace/.github/ci/tasks.yaml \ | |
| -o /tmp/ci-results.txt | |
| TEST_EXIT_CODE=$? | |
| echo "=== Test results ===" | |
| cat /tmp/ci-results.txt | |
| # Verify output file has a summary on success | |
| if [ $TEST_EXIT_CODE -eq 0 ]; then | |
| grep -q "All tasks passed" /tmp/ci-results.txt && \ | |
| echo "✓ Output file contains expected summary" || \ | |
| { echo "✗ Output file missing success summary"; exit 1; } | |
| fi | |
| kill $SERVER_PID 2>/dev/null | |
| exit $TEST_EXIT_CODE | |
| ' | |
| # ── Cleanup ────────────────────────────────────────────────────────────── | |
| - name: Remove SSH key | |
| if: always() | |
| shell: wsl-bash {0} | |
| run: rm -f ~/.ssh/ci_key ~/.ssh/ci_key.pub |