Przeglądaj źródła

Windows code signing (#1718)

Nate Brown 5 miesięcy temu
rodzic
commit
398d67e2da
2 zmienionych plików z 123 dodań i 0 usunięć
  1. 113 0
      .github/actions/code-sign/action.yml
  2. 10 0
      .github/workflows/release.yml

+ 113 - 0
.github/actions/code-sign/action.yml

@@ -0,0 +1,113 @@
+name: Code-sign Windows binaries
+description: >
+  Sign every .exe under a given path in place via the DefinedNet code-signer
+  Lambda. If `role` or `bucket` is empty, logs a notice and skips signing so
+  forks and dev branches without AWS access still produce usable builds.
+
+inputs:
+  path:
+    description: "Directory whose .exe files should be signed in place"
+    required: true
+  role:
+    description: "IAM role ARN to assume via OIDC; empty disables signing"
+    required: false
+    default: ""
+  bucket:
+    description: "S3 staging bucket the code-signer Lambda reads from; empty disables signing"
+    required: false
+    default: ""
+  region:
+    description: "AWS region for the role and Lambda"
+    required: false
+    default: "us-east-2"
+  function-name:
+    description: "Code-signer Lambda function name"
+    required: false
+    default: "code-signer"
+  key-prefix:
+    description: "S3 key prefix the caller is authorized to write under"
+    required: false
+    default: "code-signing/slackhq/nebula"
+
+runs:
+  using: composite
+  steps:
+    - name: Skip notice
+      if: inputs.role == '' || inputs.bucket == ''
+      shell: sh
+      run: echo "::notice::code-signer role or bucket not set; skipping code signing."
+
+    - name: Configure AWS credentials
+      if: inputs.role != '' && inputs.bucket != ''
+      uses: aws-actions/configure-aws-credentials@v6
+      with:
+        role-to-assume: ${{ inputs.role }}
+        aws-region: ${{ inputs.region }}
+        # Default is 12 retries to ride out IAM trust-policy propagation; once
+        # the role is stable we want a real misconfiguration to fail fast.
+        retry-max-attempts: 5
+
+    - name: Sign .exe files
+      if: inputs.role != '' && inputs.bucket != ''
+      shell: sh
+      env:
+        SIGN_PATH: ${{ inputs.path }}
+        BUCKET: ${{ inputs.bucket }}
+        FUNCTION_NAME: ${{ inputs.function-name }}
+        KEY_PREFIX: ${{ inputs.key-prefix }}
+      run: |
+        set -eu
+        RUN="${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
+
+        find "$SIGN_PATH" -name '*.exe' -print | while read -r path
+        do
+            rel=${path#"$SIGN_PATH"/}
+            file=$(basename "$path")
+            name=${file%.exe}
+            prefix="${KEY_PREFIX}/${RUN}"
+            src="${prefix}/unsigned/${rel}"
+            dst="${prefix}/signed/${rel}"
+
+            echo "::group::Sign ${rel}"
+            echo "Uploading unsigned to s3://${BUCKET}/${src}"
+            aws s3 cp --no-progress "$path" "s3://${BUCKET}/${src}" >/dev/null
+
+            echo "Invoking ${FUNCTION_NAME} Lambda"
+            payload=$(jq -nc \
+                --arg s "$src" \
+                --arg d "$dst" \
+                --arg p "$name" \
+                '{source_key: $s, dest_key: $d, program_name: $p}')
+            meta=$(aws lambda invoke \
+                --function-name "$FUNCTION_NAME" \
+                --cli-binary-format raw-in-base64-out \
+                --payload "$payload" \
+                --output json \
+                /tmp/sign-resp.json)
+            if echo "$meta" | jq -e '.FunctionError != null' >/dev/null
+            then
+                echo "::endgroup::"
+                echo "::error::code-signer Lambda failed for ${rel}"
+                cat /tmp/sign-resp.json >&2
+                exit 1
+            fi
+
+            echo "Downloading signed back to ${path}"
+            aws s3 cp --no-progress "s3://${BUCKET}/${dst}" "$path" >/dev/null
+
+            aws s3 rm "s3://${BUCKET}/${src}" >/dev/null 2>&1 || true
+            aws s3 rm "s3://${BUCKET}/${dst}" >/dev/null 2>&1 || true
+
+            # Sanity-check the bytes we got back actually carry an Authenticode
+            # signature that this machine can validate end to end.
+            status=$(powershell -NoProfile -Command "(Get-AuthenticodeSignature -FilePath '$path').Status" | tr -d '\r')
+            if [ "$status" != "Valid" ]
+            then
+                echo "::endgroup::"
+                echo "::error::${rel} signature status: ${status} (expected Valid)"
+                exit 1
+            fi
+
+            echo "Signed ${rel} (sha256=$(jq -r '.sha256' /tmp/sign-resp.json), status=${status})"
+            echo "::endgroup::"
+        done

+ 10 - 0
.github/workflows/release.yml

@@ -32,6 +32,9 @@ jobs:
   build-windows:
   build-windows:
     name: Build Windows
     name: Build Windows
     runs-on: windows-latest
     runs-on: windows-latest
+    permissions:
+      id-token: write
+      contents: read
     steps:
     steps:
       - uses: actions/checkout@v6
       - uses: actions/checkout@v6
 
 
@@ -54,6 +57,13 @@ jobs:
           mkdir build\dist\windows
           mkdir build\dist\windows
           mv dist\windows\wintun build\dist\windows\
           mv dist\windows\wintun build\dist\windows\
 
 
+      - name: Code-sign
+        uses: ./.github/actions/code-sign
+        with:
+          path: build
+          role: ${{ secrets.DEFINED_CODE_SIGNER_ROLE }}
+          bucket: ${{ secrets.DEFINED_CODE_SIGNER_BUCKET }}
+
       - name: Upload artifacts
       - name: Upload artifacts
         uses: actions/upload-artifact@v7
         uses: actions/upload-artifact@v7
         with:
         with: