Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions .github/workflows/e2e-portals.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
name: E2E Portals Test 🌀

on:
workflow_dispatch: ~
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
e2e_portals_test:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: ⚡ Setup Golang
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true

- name: Setup Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: 'stable'
default: true
profile: minimal
components: rustfmt, clippy

- name: ⚡ Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: tests/e2e/package-lock.json

- name: 📦 Install System Dependencies
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler libssl-dev jq

- name: 🔨 Build Tavern & Socks5
run: |
go mod download
go build -v -o tavern_bin ./tavern
go build -v -o socks5_bin ./bin/socks5

- name: 🚀 Run Tavern
env:
HTTP_LISTEN_ADDR: ":8000"
run: |
./tavern_bin > tavern.log 2>&1 &
echo "Waiting for Tavern to start..."
# Wait for port 8000
timeout 30 sh -c 'until nc -z $0 $1; do sleep 1; done' localhost 8000

- name: 🤖 Build & Run Agent
working-directory: implants/imixv2
env:
IMIX_CALLBACK_URI: "http://localhost:8000"
IMIX_CALLBACK_INTERVAL: 1
run: |
# Fetch the pubkey and verify it's not empty
PUBKEY=$(curl -s http://localhost:8000/status | jq -r .Pubkey)
if [ -z "$PUBKEY" ] || [ "$PUBKEY" == "null" ]; then
echo "Error: Could not fetch Pubkey from Tavern"
exit 1
fi
export IMIX_SERVER_PUBKEY=$PUBKEY
echo "Got pubkey: $IMIX_SERVER_PUBKEY"

echo "Building imixv2..."
cargo build --bin imixv2 --target-dir ./build
# Run agent and pipe logs to a file
./build/debug/imixv2 > agent.log 2>&1 &

# Give the agent a moment to perform the initial handshake
echo "Agent started. Waiting for initial callback..."
sleep 5

- name: 🎭 Install Playwright
working-directory: tests/e2e
run: |
npm ci
npx playwright install --with-deps chromium

- name: 🧪 Run Portal Provisioning Test
working-directory: tests/e2e
run: |
npx playwright test tests/portal.spec.ts

- name: 🔍 Retrieve Portal ID
id: get_portal_id
run: |
QUERY='query { portals { edges { node { id } } } }'
# Send GraphQL query to Tavern
RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" \
-d "{\"query\": \"$QUERY\"}" http://localhost:8000/query)

echo "GraphQL Response: $RESPONSE"

PORTAL_ID=$(echo $RESPONSE | jq -r '.data.portals.edges[0].node.id')

if [ -z "$PORTAL_ID" ] || [ "$PORTAL_ID" == "null" ]; then
echo "Error: Could not retrieve Portal ID"
exit 1
fi

echo "PORTAL_ID=$PORTAL_ID" >> $GITHUB_ENV
echo "Retrieved Portal ID: $PORTAL_ID"

- name: 🌐 Start Socks5 Proxy
run: |
./socks5_bin -portal $PORTAL_ID -listen_addr 127.0.0.1:1080 -upstream_addr 127.0.0.1:8000 &
echo "Socks5 Proxy started on port 1080"
sleep 2

- name: 📄 Prepare Mock Data & Server
run: |
# Generate 1MB random file
dd if=/dev/urandom of=test_data.bin bs=1M count=1
sha256sum test_data.bin > original_checksum.txt
echo "Original Checksum:"
cat original_checksum.txt

# Start Python HTTP Server on port 9000
python3 -m http.server 9000 &
echo "HTTP Server started on port 9000"
sleep 2

- name: 🔄 Transfer & Verification
run: |
# Download through proxy
curl -v -x socks5://127.0.0.1:1080 -o downloaded_data.bin http://127.0.0.1:9000/test_data.bin

sha256sum downloaded_data.bin > downloaded_checksum.txt
echo "Downloaded Checksum:"
cat downloaded_checksum.txt

# Compare hashes
ORIGINAL=$(awk '{print $1}' original_checksum.txt)
DOWNLOADED=$(awk '{print $1}' downloaded_checksum.txt)

if [ "$ORIGINAL" != "$DOWNLOADED" ]; then
echo "Error: Checksums do not match!"
exit 1
fi
echo "Success: Data integrity verified."

- name: 📂 Upload Service Logs
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-portal-logs
path: |
tavern.log
implants/imixv2/agent.log
46 changes: 46 additions & 0 deletions tests/e2e/tests/portal.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { test, expect } from '@playwright/test';

test('End-to-end portal provisioning test', async ({ page }) => {
// Connect to tavern's UI using playwright at http://127.0.0.1:8000/createQuest
console.log('Navigating to /createQuest');
await page.goto('/createQuest');

// Select the only visible beacon and click "continue"
console.log('Waiting for beacons to load');
await expect(page.getByText('Loading beacons...')).toBeHidden({ timeout: 15000 });

// Define the locator for the beacon checkboxes
const beacons = page.locator('.chakra-card input[type="checkbox"]');

// Assert that exactly one beacon exists
await expect(beacons).toHaveCount(1);

// Select the beacon
console.log('Selecting beacon');
await beacons.first().check({ force: true });

// Click Continue
console.log('Clicking Continue (Beacon)');
await page.getByRole('button', { name: 'Continue' }).click();

// 3. Select the "SOCKS5 Relay" tome and click "continue"
console.log('Selecting Tome');
await expect(page.getByText('Loading tomes...')).toBeHidden();
// Use exact match or check if it exists
await expect(page.getByText('SOCKS5 Relay')).toBeVisible();
await page.getByText('SOCKS5 Relay').click();

console.log('Clicking Continue (Tome)');
await page.getByRole('button', { name: 'Continue' }).click();

// 4. Select "Submit"
console.log('Submitting Quest');
await page.getByRole('button', { name: 'Submit' }).click();

// 5. Wait for execution.
// We expect "Portal created" to appear in the output.
console.log('Waiting for "Portal created" message');
await expect(page.getByText('Portal created')).toBeVisible({ timeout: 30000 });

console.log('Portal created successfully');
});