From 5dcf66cbeb3e8ff7f7fd7434cc4fd3906d4008e2 Mon Sep 17 00:00:00 2001 From: TUYIZERE Samuel Date: Thu, 23 Jul 2026 13:55:12 +0200 Subject: [PATCH 1/2] ci: add Windows code signing and resource generation to release workflows - Add Windows resource file generation (winres.json and winres.xml) for executable metadata - Generate architecture-specific .syso files (amd64, arm64) using go-winres during CI builds - Implement Windows binary code signing with certificate and timestamp authority - Update version information in resource files to match release version - Add *.syso to .gitignore to exclude generated build artifacts - Apply Windows signing to both beta and production release workflows - Enable proper Windows executable branding and authentication for distributed binaries --- .github/workflows/beta-release.yml | 54 +++++++++++++++++++++++++++ .github/workflows/release.yml | 53 ++++++++++++++++++++++++++ .gitignore | 3 ++ cmd/routatic-proxy/winres/winres.json | 47 +++++++++++++++++++++++ cmd/routatic-proxy/winres/winres.xml | 33 ++++++++++++++++ 5 files changed, 190 insertions(+) create mode 100644 cmd/routatic-proxy/winres/winres.json create mode 100644 cmd/routatic-proxy/winres/winres.xml diff --git a/.github/workflows/beta-release.yml b/.github/workflows/beta-release.yml index 3927395..f6d1814 100644 --- a/.github/workflows/beta-release.yml +++ b/.github/workflows/beta-release.yml @@ -69,6 +69,32 @@ jobs: echo "Production version: ${PROD_VERSION}" echo "Beta tag: ${BETA_VERSION}" + - name: Generate Windows resource files + env: + VERSION: ${{ steps.version.outputs.version }} + run: | + go install github.com/tc-hib/go-winres@latest + + # Parse version components (e.g., "0.5.3-beta.1" → "0.5.3.0") + CLEAN_VER=$(echo "$VERSION" | sed 's/-.*//') + MAJOR=$(echo "$CLEAN_VER" | cut -d. -f1) + MINOR=$(echo "$CLEAN_VER" | cut -d. -f2) + PATCH=$(echo "$CLEAN_VER" | cut -d. -f3) + FILE_VER="${MAJOR:-0}.${MINOR:-0}.${PATCH:-0}.0" + + # Update version in winres.json + cd cmd/routatic-proxy + sed -i.bak "s/\"file_version\": \"0.0.0.0\"/\"file_version\": \"${FILE_VER}\"/" winres/winres.json + sed -i.bak "s/\"product_version\": \"0.0.0.0\"/\"product_version\": \"${FILE_VER}\"/" winres/winres.json + sed -i.bak "s/\"FileVersion\": \"0.0.0.0\"/\"FileVersion\": \"${VERSION}\"/" winres/winres.json + sed -i.bak "s/\"ProductVersion\": \"0.0.0.0\"/\"ProductVersion\": \"${VERSION}\"/" winres/winres.json + rm -f winres/winres.json.bak + + # Generate .syso files for both Windows architectures + go-winres make --arch amd64 --out rsrc_windows_amd64.syso + go-winres make --arch arm64 --out rsrc_windows_arm64.syso + cd ../.. + - name: Build cross-platform binaries env: VERSION: ${{ steps.version.outputs.version }} @@ -99,6 +125,34 @@ jobs: echo "Built binaries:" ls -lh dist/ + - name: Sign Windows binaries + if: env.WINDOWS_SIGN_CERT != '' + env: + WINDOWS_SIGN_CERT: ${{ secrets.WINDOWS_SIGN_CERT }} + WINDOWS_SIGN_KEY: ${{ secrets.WINDOWS_SIGN_KEY }} + run: | + brew install osslsigncode + + # Decode certificate and key from base64 secrets + echo "$WINDOWS_SIGN_CERT" | base64 -d > /tmp/cert.pem + echo "$WINDOWS_SIGN_KEY" | base64 -d > /tmp/key.pem + + for EXE in dist/routatic-proxy_windows-*.exe; do + echo "Signing ${EXE}..." + osslsigncode sign \ + -certs /tmp/cert.pem \ + -key /tmp/key.pem \ + -t http://timestamp.digicert.com \ + -n "Routatic Proxy" \ + -url "https://github.com/routatic/proxy" \ + -in "$EXE" \ + -out "${EXE}.signed" + mv "${EXE}.signed" "$EXE" + done + + # Clean up + rm -f /tmp/cert.pem /tmp/key.pem + - name: Generate checksums run: | cd dist diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 50732e4..d2d41f9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,6 +68,31 @@ jobs: echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" echo "Releasing as $TAG" + - name: Generate Windows resource files + env: + VERSION: ${{ steps.version.outputs.version }} + run: | + go install github.com/tc-hib/go-winres@latest + + # Parse version components (e.g., "0.5.3" → "0.5.3.0") + MAJOR=$(echo "$VERSION" | cut -d. -f1) + MINOR=$(echo "$VERSION" | cut -d. -f2) + PATCH=$(echo "$VERSION" | cut -d. -f3) + FILE_VER="${MAJOR:-0}.${MINOR:-0}.${PATCH:-0}.0" + + # Update version in winres.json + cd cmd/routatic-proxy + sed -i.bak "s/\"file_version\": \"0.0.0.0\"/\"file_version\": \"${FILE_VER}\"/" winres/winres.json + sed -i.bak "s/\"product_version\": \"0.0.0.0\"/\"product_version\": \"${FILE_VER}\"/" winres/winres.json + sed -i.bak "s/\"FileVersion\": \"0.0.0.0\"/\"FileVersion\": \"${VERSION}\"/" winres/winres.json + sed -i.bak "s/\"ProductVersion\": \"0.0.0.0\"/\"ProductVersion\": \"${VERSION}\"/" winres/winres.json + rm -f winres/winres.json.bak + + # Generate .syso files for both Windows architectures + go-winres make --arch amd64 --out rsrc_windows_amd64.syso + go-winres make --arch arm64 --out rsrc_windows_arm64.syso + cd ../.. + - name: Build cross-platform binaries env: VERSION: ${{ steps.version.outputs.version }} @@ -98,6 +123,34 @@ jobs: echo "Built binaries:" ls -lh dist/ + - name: Sign Windows binaries + if: env.WINDOWS_SIGN_CERT != '' + env: + WINDOWS_SIGN_CERT: ${{ secrets.WINDOWS_SIGN_CERT }} + WINDOWS_SIGN_KEY: ${{ secrets.WINDOWS_SIGN_KEY }} + run: | + brew install osslsigncode + + # Decode certificate and key from base64 secrets + echo "$WINDOWS_SIGN_CERT" | base64 -d > /tmp/cert.pem + echo "$WINDOWS_SIGN_KEY" | base64 -d > /tmp/key.pem + + for EXE in dist/routatic-proxy_windows-*.exe; do + echo "Signing ${EXE}..." + osslsigncode sign \ + -certs /tmp/cert.pem \ + -key /tmp/key.pem \ + -t http://timestamp.digicert.com \ + -n "Routatic Proxy" \ + -url "https://github.com/routatic/proxy" \ + -in "$EXE" \ + -out "${EXE}.signed" + mv "${EXE}.signed" "$EXE" + done + + # Clean up + rm -f /tmp/cert.pem /tmp/key.pem + - name: Generate checksums run: | cd dist diff --git a/.gitignore b/.gitignore index 46662f1..1fb1d34 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ router.test .playwright-mcp .gstack/ node_modules/ + +# Generated Windows resource files (built by go-winres in CI) +*.syso diff --git a/cmd/routatic-proxy/winres/winres.json b/cmd/routatic-proxy/winres/winres.json new file mode 100644 index 0000000..94af95a --- /dev/null +++ b/cmd/routatic-proxy/winres/winres.json @@ -0,0 +1,47 @@ +{ + "RT_MANIFEST": { + "#1": { + "0409": { + "identity": { + "name": "Routatic.Proxy", + "version": "0.0.0.0" + }, + "description": "Routatic Proxy - AI Model Router", + "minimum-os": "win7", + "execution-level": "asInvoker", + "ui-access": false, + "auto-elevate": false, + "dpi-awareness": "system", + "disable-theming": false, + "disable-window-filtering": false, + "high-resolution-scrolling-aware": false, + "ultra-high-resolution-scrolling-aware": false, + "long-path-aware": true, + "gdi-scaling": false + } + } + }, + "RT_VERSION": { + "#1": { + "0000": { + "fixed": { + "file_version": "0.0.0.0", + "product_version": "0.0.0.0" + }, + "info": { + "0409": { + "Comments": "AI model routing proxy for Claude Code", + "CompanyName": "Routatic", + "FileDescription": "Routatic Proxy - AI Model Router", + "FileVersion": "0.0.0.0", + "InternalName": "routatic-proxy", + "LegalCopyright": "Copyright (c) 2024-2026 Routatic", + "OriginalFilename": "routatic-proxy.exe", + "ProductName": "Routatic Proxy", + "ProductVersion": "0.0.0.0" + } + } + } + } + } +} diff --git a/cmd/routatic-proxy/winres/winres.xml b/cmd/routatic-proxy/winres/winres.xml new file mode 100644 index 0000000..46718e2 --- /dev/null +++ b/cmd/routatic-proxy/winres/winres.xml @@ -0,0 +1,33 @@ + + + + Routatic Proxy - AI Model Router + + + + + + + + + + + + + + + + + + + + + + true + + + From 87242ae81ae0e23bb6d2e6c40a2dc15824f5c18a Mon Sep 17 00:00:00 2001 From: TUYIZERE Samuel Date: Thu, 23 Jul 2026 14:09:07 +0200 Subject: [PATCH 2/2] ci: migrate Windows code signing from osslsigncode to SignPath - Replace local osslsigncode signing with SignPath GitHub Action for secure code signing - Update beta-release.yml and release.yml workflows with new signing pipeline - Package Windows binaries (amd64 and arm64) into zip archive for SignPath submission - Configure SignPath API integration with organization and project credentials - Add post-signing step to extract and replace binaries from signed artifacts - Change credential detection from WINDOWS_SIGN_CERT/KEY to SIGNPATH_API_TOKEN - Improve security by using dedicated code signing service instead of local certificate handling --- .github/workflows/beta-release.yml | 55 ++++++++++++++++-------------- .github/workflows/release.yml | 55 ++++++++++++++++-------------- 2 files changed, 58 insertions(+), 52 deletions(-) diff --git a/.github/workflows/beta-release.yml b/.github/workflows/beta-release.yml index f6d1814..dcdd4d3 100644 --- a/.github/workflows/beta-release.yml +++ b/.github/workflows/beta-release.yml @@ -125,33 +125,36 @@ jobs: echo "Built binaries:" ls -lh dist/ - - name: Sign Windows binaries - if: env.WINDOWS_SIGN_CERT != '' - env: - WINDOWS_SIGN_CERT: ${{ secrets.WINDOWS_SIGN_CERT }} - WINDOWS_SIGN_KEY: ${{ secrets.WINDOWS_SIGN_KEY }} + - name: Package Windows binaries for signing + if: ${{ secrets.SIGNPATH_API_TOKEN != '' }} run: | - brew install osslsigncode - - # Decode certificate and key from base64 secrets - echo "$WINDOWS_SIGN_CERT" | base64 -d > /tmp/cert.pem - echo "$WINDOWS_SIGN_KEY" | base64 -d > /tmp/key.pem - - for EXE in dist/routatic-proxy_windows-*.exe; do - echo "Signing ${EXE}..." - osslsigncode sign \ - -certs /tmp/cert.pem \ - -key /tmp/key.pem \ - -t http://timestamp.digicert.com \ - -n "Routatic Proxy" \ - -url "https://github.com/routatic/proxy" \ - -in "$EXE" \ - -out "${EXE}.signed" - mv "${EXE}.signed" "$EXE" - done - - # Clean up - rm -f /tmp/cert.pem /tmp/key.pem + mkdir -p signing + cp dist/routatic-proxy_windows-amd64.exe signing/ + cp dist/routatic-proxy_windows-arm64.exe signing/ + cd signing && zip ../windows-binaries.zip *.exe + + - name: Sign Windows binaries (SignPath) + if: ${{ secrets.SIGNPATH_API_TOKEN != '' }} + uses: signpath/github-action-submit-signing-request@ced31329c0317e779dad2eec2a7c3bb46ea1343e # v1 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: '5c4d4e48-c428-4e10-ab07-8db7f46cbec9' + project-slug: 'routatic-proxy' + signing-policy-slug: 'release-signing' + artifact-configuration-slug: 'windows-exe' + github-artifact-id: 'windows-binaries' + input-artifact-path: 'windows-binaries.zip' + output-artifact-path: 'windows-binaries-signed.zip' + wait-for-completion: true + wait-for-completion-timeout-in-seconds: 300 + + - name: Replace with signed Windows binaries + if: ${{ secrets.SIGNPATH_API_TOKEN != '' }} + run: | + unzip -o windows-binaries-signed.zip -d signed/ + cp signed/routatic-proxy_windows-amd64.exe dist/routatic-proxy_windows-amd64.exe + cp signed/routatic-proxy_windows-arm64.exe dist/routatic-proxy_windows-arm64.exe + rm -rf signing signed windows-binaries.zip windows-binaries-signed.zip - name: Generate checksums run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d2d41f9..575b4c4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -123,33 +123,36 @@ jobs: echo "Built binaries:" ls -lh dist/ - - name: Sign Windows binaries - if: env.WINDOWS_SIGN_CERT != '' - env: - WINDOWS_SIGN_CERT: ${{ secrets.WINDOWS_SIGN_CERT }} - WINDOWS_SIGN_KEY: ${{ secrets.WINDOWS_SIGN_KEY }} + - name: Package Windows binaries for signing + if: ${{ secrets.SIGNPATH_API_TOKEN != '' }} run: | - brew install osslsigncode - - # Decode certificate and key from base64 secrets - echo "$WINDOWS_SIGN_CERT" | base64 -d > /tmp/cert.pem - echo "$WINDOWS_SIGN_KEY" | base64 -d > /tmp/key.pem - - for EXE in dist/routatic-proxy_windows-*.exe; do - echo "Signing ${EXE}..." - osslsigncode sign \ - -certs /tmp/cert.pem \ - -key /tmp/key.pem \ - -t http://timestamp.digicert.com \ - -n "Routatic Proxy" \ - -url "https://github.com/routatic/proxy" \ - -in "$EXE" \ - -out "${EXE}.signed" - mv "${EXE}.signed" "$EXE" - done - - # Clean up - rm -f /tmp/cert.pem /tmp/key.pem + mkdir -p signing + cp dist/routatic-proxy_windows-amd64.exe signing/ + cp dist/routatic-proxy_windows-arm64.exe signing/ + cd signing && zip ../windows-binaries.zip *.exe + + - name: Sign Windows binaries (SignPath) + if: ${{ secrets.SIGNPATH_API_TOKEN != '' }} + uses: signpath/github-action-submit-signing-request@ced31329c0317e779dad2eec2a7c3bb46ea1343e # v1 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: '5c4d4e48-c428-4e10-ab07-8db7f46cbec9' + project-slug: 'routatic-proxy' + signing-policy-slug: 'release-signing' + artifact-configuration-slug: 'windows-exe' + github-artifact-id: 'windows-binaries' + input-artifact-path: 'windows-binaries.zip' + output-artifact-path: 'windows-binaries-signed.zip' + wait-for-completion: true + wait-for-completion-timeout-in-seconds: 300 + + - name: Replace with signed Windows binaries + if: ${{ secrets.SIGNPATH_API_TOKEN != '' }} + run: | + unzip -o windows-binaries-signed.zip -d signed/ + cp signed/routatic-proxy_windows-amd64.exe dist/routatic-proxy_windows-amd64.exe + cp signed/routatic-proxy_windows-arm64.exe dist/routatic-proxy_windows-arm64.exe + rm -rf signing signed windows-binaries.zip windows-binaries-signed.zip - name: Generate checksums run: |