# ============================================================================= # 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. # # 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/vigilcyber/wraith.git . # --------------------------------------------------------------- # Extract version from tag # --------------------------------------------------------------- - 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 # --------------------------------------------------------------- - name: Install Node.js run: | # Install Node.js LTS if not present 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 # --------------------------------------------------------------- - name: Build frontend run: | cd frontend npm ci npm run build 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}" \ -o dist/wraith.exe \ . 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 # --------------------------------------------------------------- - 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 }}" \ -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 wraith.exe 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." # --------------------------------------------------------------- # Version manifest # --------------------------------------------------------------- - name: Create version.json run: | VERSION="${{ steps.version.outputs.version }}" SHA256=$(sha256sum dist/wraith.exe | awk '{print $1}') cat > dist/version.json << EOF { "version": "${VERSION}", "filename": "wraith.exe", "sha256": "${SHA256}", "platform": "windows", "architecture": "amd64", "released": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")", "signed": true } 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 (overwritten on each release) 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/"