diff --git a/.gitea/workflows/build-release.yml b/.gitea/workflows/build-release.yml index 1e743fb..57c0c0f 100644 --- a/.gitea/workflows/build-release.yml +++ b/.gitea/workflows/build-release.yml @@ -1,15 +1,15 @@ # ============================================================================= # Wraith — Build & Sign Release # ============================================================================= -# Builds the Wails v3 desktop app for Windows amd64, signs it with an -# Azure Key Vault code-signing certificate via jsign, then uploads the -# signed binary and version manifest as release artifacts. +# 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_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 @@ -35,7 +35,7 @@ jobs: - name: Checkout code run: | git clone --depth 1 --branch ${{ github.ref_name }} \ - https://${{ secrets.GIT_TOKEN }}@git.command.vigilcyber.com/vigilcyber/wraith.git . + https://${{ secrets.GIT_TOKEN }}@git.command.vigilcyber.com/vstockwell/wraith.git . # --------------------------------------------------------------- # Extract version from tag @@ -43,27 +43,107 @@ jobs: - name: Get version from tag id: version run: | - # Strip leading "v" from the tag (v1.2.3 -> 1.2.3) TAG=$(echo "${{ github.ref_name }}" | sed 's/^v//') echo "version=${TAG}" >> $GITHUB_OUTPUT echo "Building version: ${TAG}" # --------------------------------------------------------------- - # Install toolchain: Go, Node, npm + # Install toolchain # --------------------------------------------------------------- - - name: Install Node.js + - name: Install build dependencies run: | - # Install Node.js LTS if not present + 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 - node --version - npm --version - # --------------------------------------------------------------- - # Build frontend assets - # --------------------------------------------------------------- + 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 @@ -72,22 +152,11 @@ jobs: echo "Frontend build complete:" ls -la dist/ - # --------------------------------------------------------------- - # Build Windows amd64 binary - # --------------------------------------------------------------- - # Wails v3 cross-compilation from Linux to Windows requires CGO - # for the webview2 bindings. Rather than pull in a full MinGW - # cross-compiler, we build the Go binary directly — the frontend - # assets are embedded via //go:embed in main.go, so the result is - # a single self-contained .exe. - # --------------------------------------------------------------- - name: Build wraith.exe (Windows amd64) run: | VERSION="${{ steps.version.outputs.version }}" echo "=== Cross-compiling wraith.exe for Windows amd64 ===" - mkdir -p dist - GOOS=windows GOARCH=amd64 CGO_ENABLED=0 \ go build \ -ldflags="-s -w -X main.version=${VERSION}" \ @@ -96,64 +165,18 @@ jobs: ls -la dist/wraith.exe - # --------------------------------------------------------------- - # FreeRDP3 DLLs — runtime dependency for the real RDP backend - # --------------------------------------------------------------- - - name: Download FreeRDP3 DLLs - run: | - echo "=== Downloading FreeRDP3 pre-built DLLs for Windows amd64 ===" - FREERDP_VERSION="3.12.0" - - # FreeRDP releases pre-built Windows binaries on GitHub. - # We need: libfreerdp3.dll, libwinpr3.dll, libfreerdp-client3.dll - # - # Option 1: Download from FreeRDP GitHub releases - # curl -sSL -o /tmp/freerdp.zip \ - # "https://github.com/FreeRDP/FreeRDP/releases/download/${FREERDP_VERSION}/FreeRDP-${FREERDP_VERSION}-win64.zip" - # unzip /tmp/freerdp.zip -d /tmp/freerdp - # cp /tmp/freerdp/bin/libfreerdp3.dll dist/ - # cp /tmp/freerdp/bin/libwinpr3.dll dist/ - # cp /tmp/freerdp/bin/libfreerdp-client3.dll dist/ - # - # Option 2: Build from source with cmake + MSVC cross-compiler - - # For the initial CI run, the app will fall back to MockBackend - # until the DLLs are provided alongside wraith.exe. - echo "FreeRDP3 DLLs not yet configured — app will use mock RDP backend" - echo "To enable real RDP: place libfreerdp3.dll, libwinpr3.dll, libfreerdp-client3.dll alongside wraith.exe" - - # When DLLs are available, sign them alongside wraith.exe: - # for dll in dist/lib*.dll; do - # 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 \ - # "$dll" - # done - - # --------------------------------------------------------------- - # Code signing — jsign + Azure Key Vault - # --------------------------------------------------------------- + # =============================================================== + # Code signing — jsign + Azure Key Vault (EV cert) + # =============================================================== - name: Install jsign run: | - # jsign: Java-based Authenticode signing tool that runs on - # Linux and supports Azure Key Vault as a keystore. 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" - # Ensure a JRE is available (jsign needs Java) - command -v java >/dev/null 2>&1 || { - apt-get update -qq && apt-get install -y -qq default-jre-headless - } - - name: Get Azure Key Vault access token id: azure-token run: | - # OAuth2 client credentials flow for Azure Key Vault access TOKEN=$(curl -s -X POST \ "https://login.microsoftonline.com/${{ secrets.AZURE_TENANT_ID }}/oauth2/v2.0/token" \ -d "client_id=${{ secrets.AZURE_CLIENT_ID }}" \ @@ -164,45 +187,62 @@ jobs: echo "::add-mask::${TOKEN}" echo "token=${TOKEN}" >> $GITHUB_OUTPUT - - name: Sign wraith.exe + - name: Sign all Windows binaries run: | - echo "=== Signing wraith.exe with Azure Key Vault certificate ===" - 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 \ - dist/wraith.exe - echo "Signing complete." + 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 }}" - SHA256=$(sha256sum dist/wraith.exe | awk '{print $1}') + 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": "${SHA256}", + "sha256": "${EXE_SHA}", "platform": "windows", "architecture": "amd64", "released": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")", - "signed": true + "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 }}" @@ -214,10 +254,13 @@ jobs: aws s3 cp dist/ "s3://agents/wraith/${VERSION}/windows/amd64/" \ --recursive --endpoint-url "$ENDPOINT" --no-sign-request - # Latest path (overwritten on each release) + # 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/