# ============================================================================= # Wraith — Build & Sign Release # ============================================================================= # Builds the Wails v3 desktop app for Windows amd64, cross-compiles FreeRDP3 # from source via MinGW, signs everything with Azure Key Vault EV cert, # then uploads to SeaweedFS. # # Trigger: push a tag matching v* (e.g. v1.0.0) or run manually. # # Required secrets: # AZURE_TENANT_ID — Azure AD tenant # AZURE_CLIENT_ID — Service principal client ID # AZURE_CLIENT_SECRET — Service principal secret # AZURE_KEY_VAULT_URL — e.g. https://my-vault.vault.azure.net # AZURE_CERT_NAME — Certificate/key name in the vault # GIT_TOKEN — PAT for cloning private repo # ============================================================================= name: Build & Sign Wraith on: push: tags: - 'v*' workflow_dispatch: jobs: build-and-sign: name: Build Windows + Sign runs-on: linux steps: # --------------------------------------------------------------- # Checkout # --------------------------------------------------------------- - name: Checkout code run: git clone --depth 1 --branch ${{ github.ref_name }} https://${{ secrets.GIT_TOKEN }}@git.command.vigilcyber.com/vstockwell/wraith.git . # --------------------------------------------------------------- # Extract version from tag # --------------------------------------------------------------- - name: Get version from tag id: version run: | TAG=$(echo "${{ github.ref_name }}" | sed 's/^v//') echo "version=${TAG}" >> $GITHUB_OUTPUT echo "Building version: ${TAG}" # --------------------------------------------------------------- # Install toolchain # --------------------------------------------------------------- - name: Install build dependencies run: | apt-get update -qq apt-get install -y -qq \ mingw-w64 mingw-w64-tools binutils-mingw-w64 \ cmake ninja-build nasm meson \ default-jre-headless \ python3 awscli # Node.js if ! command -v node >/dev/null 2>&1; then curl -fsSL https://deb.nodesource.com/setup_22.x | bash - apt-get install -y -qq nodejs fi echo "=== Toolchain versions ===" go version node --version x86_64-w64-mingw32-gcc --version | head -1 cmake --version | head -1 # =============================================================== # FreeRDP3 — Cross-compile from source via MinGW # =============================================================== - name: Build FreeRDP3 for Windows (MinGW cross-compile) run: | FREERDP_VERSION="3.24.0" echo "=== Building FreeRDP ${FREERDP_VERSION} for Windows amd64 via MinGW ===" # Download FreeRDP source curl -sSL -o /tmp/freerdp.tar.gz \ "https://github.com/FreeRDP/FreeRDP/archive/refs/tags/${FREERDP_VERSION}.tar.gz" tar -xzf /tmp/freerdp.tar.gz -C /tmp cd /tmp/FreeRDP-${FREERDP_VERSION} # Create MinGW toolchain file cat > /tmp/mingw-toolchain.cmake << 'TCEOF' set(CMAKE_SYSTEM_NAME Windows) set(CMAKE_SYSTEM_PROCESSOR AMD64) set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) TCEOF # Configure — minimal client-only build (no server, no extras) cmake -B build -G Ninja \ -DCMAKE_TOOLCHAIN_FILE=/tmp/mingw-toolchain.cmake \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/tmp/freerdp-install \ -DBUILD_SHARED_LIBS=ON \ -DWITH_CLIENT=ON \ -DWITH_SERVER=OFF \ -DWITH_SHADOW=OFF \ -DWITH_PROXY=OFF \ -DWITH_SAMPLE=OFF \ -DWITH_PLATFORM_SERVER=OFF \ -DWITH_WINPR_TOOLS=OFF \ -DWITH_FFMPEG=OFF \ -DWITH_SWSCALE=OFF \ -DWITH_CAIRO=OFF \ -DWITH_CUPS=OFF \ -DWITH_PULSE=OFF \ -DWITH_ALSA=OFF \ -DWITH_OSS=OFF \ -DWITH_WAYLAND=OFF \ -DWITH_X11=OFF \ -DCHANNEL_URBDRC=OFF \ -DWITH_OPENH264=OFF # Build cmake --build build --parallel $(nproc) cmake --install build echo "=== FreeRDP3 DLLs built ===" ls -la /tmp/freerdp-install/bin/*.dll 2>/dev/null || ls -la /tmp/freerdp-install/lib/*.dll 2>/dev/null || echo "Checking build output..." find /tmp/freerdp-install -name "*.dll" -type f - name: Stage FreeRDP3 DLLs run: | mkdir -p dist # Copy all FreeRDP DLLs (MinGW produces lib-prefixed names) find /tmp/freerdp-install -name "*.dll" -type f -exec cp {} dist/ \; echo "=== Staged DLLs ===" ls -la dist/*.dll 2>/dev/null || echo "No DLLs found — FreeRDP build may have failed" # =============================================================== # Build Wraith # =============================================================== - name: Build frontend run: | cd frontend npm ci npm run build echo "Frontend build complete:" ls -la dist/ - name: Build wraith.exe (Windows amd64) run: | VERSION="${{ steps.version.outputs.version }}" echo "=== Cross-compiling wraith.exe for Windows amd64 ===" GOOS=windows GOARCH=amd64 CGO_ENABLED=0 \ go build \ -ldflags="-s -w -X main.version=${VERSION}" \ -o dist/wraith.exe \ . ls -la dist/wraith.exe # =============================================================== # Code signing — jsign + Azure Key Vault (EV cert) # =============================================================== - name: Install jsign run: | JSIGN_VERSION="7.0" curl -sSL -o /usr/local/bin/jsign.jar \ "https://github.com/ebourg/jsign/releases/download/${JSIGN_VERSION}/jsign-${JSIGN_VERSION}.jar" - name: Get Azure Key Vault access token id: azure-token run: | TOKEN=$(curl -s -X POST \ "https://login.microsoftonline.com/${{ secrets.AZURE_TENANT_ID }}/oauth2/v2.0/token" \ -d "client_id=${{ secrets.AZURE_CLIENT_ID }}" \ -d "client_secret=${{ secrets.AZURE_CLIENT_SECRET }}" \ -d "scope=https://vault.azure.net/.default" \ -d "grant_type=client_credentials" \ | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])") echo "::add-mask::${TOKEN}" echo "token=${TOKEN}" >> $GITHUB_OUTPUT - name: Sign all Windows binaries run: | echo "=== Signing all .exe and .dll files with EV certificate ===" for binary in dist/*.exe dist/*.dll; do [ -f "$binary" ] || continue echo "Signing: $binary" java -jar /usr/local/bin/jsign.jar \ --storetype AZUREKEYVAULT \ --keystore "${{ secrets.AZURE_KEY_VAULT_URL }}" \ --storepass "${{ steps.azure-token.outputs.token }}" \ --alias "${{ secrets.AZURE_CERT_NAME }}" \ --tsaurl http://timestamp.digicert.com \ --tsmode RFC3161 \ "$binary" echo "Signed: $binary" done # =============================================================== # Version manifest # =============================================================== - name: Create version.json run: | VERSION="${{ steps.version.outputs.version }}" EXE_SHA=$(sha256sum dist/wraith.exe | awk '{print $1}') # Build DLL manifest DLL_ENTRIES="" for dll in dist/*.dll; do [ -f "$dll" ] || continue DLL_NAME=$(basename "$dll") DLL_SHA=$(sha256sum "$dll" | awk '{print $1}') DLL_ENTRIES="${DLL_ENTRIES} \"${DLL_NAME}\": \"${DLL_SHA}\", " done cat > dist/version.json << EOF { "version": "${VERSION}", "filename": "wraith.exe", "sha256": "${EXE_SHA}", "platform": "windows", "architecture": "amd64", "released": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")", "signed": true, "dlls": { ${DLL_ENTRIES} "_note": "All DLLs are EV code-signed" } } EOF echo "=== version.json ===" cat dist/version.json # =============================================================== # Upload release artifacts # =============================================================== - name: Upload release artifacts run: | VERSION="${{ steps.version.outputs.version }}" ENDPOINT="https://files.command.vigilcyber.com" echo "=== Uploading Wraith ${VERSION} ===" # Versioned path aws s3 cp dist/ "s3://agents/wraith/${VERSION}/windows/amd64/" \ --recursive --endpoint-url "$ENDPOINT" --no-sign-request # Latest path aws s3 sync dist/ "s3://agents/wraith/latest/windows/amd64/" \ --delete --endpoint-url "$ENDPOINT" --no-sign-request echo "=== Upload complete ===" echo "Versioned: ${ENDPOINT}/agents/wraith/${VERSION}/windows/amd64/" echo "Latest: ${ENDPOINT}/agents/wraith/latest/windows/amd64/" echo "" echo "=== Contents ===" ls -la dist/