From 03546214f1ea4b54bb819c3a1f8cf72263851c6c Mon Sep 17 00:00:00 2001 From: Haard Shah <36398394+Haard30@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:52:51 -0400 Subject: [PATCH] Remove GitHub release pipeline and sign.py, which are no longer used (#397) --- .github/workflows/release.yml | 269 ------------------------- bin/sign.py | 367 ---------------------------------- 2 files changed, 636 deletions(-) delete mode 100644 .github/workflows/release.yml delete mode 100644 bin/sign.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index fd96d892..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,269 +0,0 @@ -name: Release - -on: - workflow_dispatch: - inputs: - version: - description: 'Version' - required: true - type: string - # We use SemVer, anything before 1.0.0 is a pre-release, but this could also include versions like 1.1.0-beta. - prerelease: - description: 'Prerelease' - required: true - default: true - type: boolean - -jobs: - # Special request from @kyle-rader and @goagain, so no one can create an invalid release. - validate: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: '3.10' - - name: Validate version - run: echo ${{ github.event.inputs.version }} | python ./bin/version.py - - build: - permissions: - actions: read - contents: read - security-events: write - statuses: write - id-token: write - - runs-on: ${{ matrix.os }} - needs: [validate] - strategy: - matrix: - # We build on Linux, but don't yet ship Linux because we can't easily sign those releases. - runtime: [osx-x64, osx-arm64, win10-x64] - include: - # macos-latest (currently 14) breaks this flow. Refer https://github.com/actions/runner-images/issues/9766. - - runtime: osx-x64 - os: macos-13 - - runtime: osx-arm64 - os: macos-13 - - runtime: win10-x64 - os: windows-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Get Azure DevOps Access Token - id: getToken - uses: "./.github/actions/get-ado-token" - with: - client-id: ${{ secrets.AZURE_RELEASE_WORKFLOW_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_RELEASE_WORKFLOW_TENANT_ID }} - organization: ${{ secrets.ADO_ORGANIZATION }} - - - name: Setup .NET 6 - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 6.0.x - - - name: Install dependencies - run: dotnet restore --runtime ${{ matrix.runtime }} - env: - ADO_TOKEN: ${{ steps.getToken.outputs.token }} - - name: Test - run: dotnet test --no-restore --configuration release - - - name: Build artifacts - run: dotnet publish src/AzureAuth/AzureAuth.csproj -p:Version=${{ github.event.inputs.version }} --configuration release --self-contained true --runtime ${{ matrix.runtime }} --output dist/${{ matrix.runtime }} - env: - ADO_TOKEN: ${{ steps.getToken.outputs.token }} - - - name: Upload artifacts - uses: actions/upload-artifact@v3 - with: - name: azureauth-${{ github.event.inputs.version }}-${{ matrix.runtime }} - path: dist/${{ matrix.runtime }} - - analyze: - runs-on: ubuntu-latest - permissions: - security-events: write - id-token: write - needs: [validate] - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Get Azure DevOps Access Token - id: getToken - uses: "./.github/actions/get-ado-token" - with: - client-id: ${{ secrets.AZURE_RELEASE_WORKFLOW_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_RELEASE_WORKFLOW_TENANT_ID }} - organization: ${{ secrets.ADO_ORGANIZATION }} - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: csharp - - # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - env: - ADO_TOKEN: ${{ steps.getToken.outputs.token }} - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:csharp" - - sign: - # This step has to run on Windows because ESRPClient.exe is currently only available for that platform. - runs-on: windows-latest - needs: [build, analyze] - strategy: - matrix: - runtime: [osx-x64, osx-arm64, win10-x64] - permissions: - id-token: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: '3.10' - - name: Setup NuGet - uses: NuGet/setup-nuget@v1 - with: - nuget-version: '5.x' - - name: Get Azure DevOps Access Token - id: getToken - uses: "./.github/actions/get-ado-token" - with: - client-id: ${{ secrets.AZURE_RELEASE_WORKFLOW_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_RELEASE_WORKFLOW_TENANT_ID }} - organization: ${{ secrets.ADO_ORGANIZATION }} - - name: Download ESRPClient.exe - env: - ESRP_VERSION: ${{ secrets.ESRP_VERSION }} - NUGET_CREDENTIALS: ${{ steps.getToken.outputs.token }} - run: | - nuget sources add -Name esrp -Username esrp-downloader -Password $env:NUGET_CREDENTIALS -Source https://pkgs.dev.azure.com/office/_packaging/Office/nuget/v3/index.json - nuget install Microsoft.EsrpClient -Version "$env:ESRP_VERSION" -OutputDirectory .\esrp -Source https://pkgs.dev.azure.com/office/_packaging/Office/nuget/v3/index.json - - name: Login to Azure - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - # We need these certificates installed so that we can run ESRPClient.exe. - - name: Install certificates - env: - AZURE_SUBSCRIPTION: ${{ secrets.AZURE_SUBSCRIPTION }} - AZURE_VAULT: ${{ secrets.AZURE_VAULT }} - ESRP_AAD_CERT_NAME: ${{ secrets.AZURE_VAULT_ESRP_AAD_CERT_NAME }} - ESRP_REQ_CERT_NAME: ${{ secrets.AZURE_VAULT_ESRP_REQ_CERT_NAME }} - run: | - az keyvault secret download --subscription "$env:AZURE_SUBSCRIPTION" --vault-name "$env:AZURE_VAULT" --name "$env:ESRP_AAD_CERT_NAME" -f cert.pfx - certutil -f -importpfx cert.pfx - Remove-Item cert.pfx - - az keyvault secret download --subscription "$env:AZURE_SUBSCRIPTION" --vault-name "$env:AZURE_VAULT" --name "$env:ESRP_REQ_CERT_NAME" -f cert.pfx - certutil -f -importpfx cert.pfx - Remove-Item cert.pfx - # We download all artifacts and overwrite them with signed files, but only upload ones which we can properly sign. - - name: Download all artifacts - uses: actions/download-artifact@v3 - - name: Sign artifacts - env: - SIGNING_AAD_ID: ${{ secrets.SIGNING_AAD_ID }} - SIGNING_TENANT_ID: ${{ secrets.SIGNING_TENANT_ID }} - SIGNING_KEY_CODE_AUTHENTICODE: ${{ secrets.SIGNING_KEY_CODE_AUTHENTICODE }} - SIGNING_KEY_CODE_MAC: ${{ secrets.SIGNING_KEY_CODE_MAC }} - SIGNING_KEY_CODE_LINUX: ${{ secrets.SIGNING_KEY_CODE_LINUX }} - SIGNING_CUSTOMER_CORRELATION_ID: ${{ secrets.SIGNING_CUSTOMER_CORRELATION_ID }} - ESRP_CLIENT_EXE: ".\\esrp\\Microsoft.EsrpClient.${{ secrets.ESRP_VERSION }}\\tools\\EsrpClient.exe" - run: python .\bin\sign.py "$env:ESRP_CLIENT_EXE" --runtime=${{ matrix.runtime }} --source=azureauth-${{ github.event.inputs.version }}-${{ matrix.runtime }} - - name: Upload signed artifacts - uses: actions/upload-artifact@v3 - with: - name: azureauth-${{ github.event.inputs.version }}-${{ matrix.runtime }} - path: azureauth-${{ github.event.inputs.version }}-${{ matrix.runtime }} - - - # Currently we package artifacts into the most commonly accessible archive format for their respective platforms. - package: - runs-on: ubuntu-latest - needs: [sign] - steps: - - name: Download all artifacts - uses: actions/download-artifact@v3 - - name: Install Zip - run: sudo apt install -y zip - - name: Create win10-x64 archive - run: | - cd azureauth-${{ github.event.inputs.version }}-win10-x64 - zip ../azureauth-${{ github.event.inputs.version }}-win10-x64.zip * - - name: Upload win10-x64 artifact - uses: actions/upload-artifact@v3 - with: - name: azureauth-${{ github.event.inputs.version }}-win10-x64.zip - path: azureauth-${{ github.event.inputs.version }}-win10-x64.zip - - name: Create osx-x64 archive - run: | - cd azureauth-${{ github.event.inputs.version }}-osx-x64 - chmod +x azureauth createdump *.dylib - tar -czf ../azureauth-${{ github.event.inputs.version }}-osx-x64.tar.gz * - - name: Upload osx-x64 artifact - uses: actions/upload-artifact@v3 - with: - name: azureauth-${{ github.event.inputs.version }}-osx-x64.tar.gz - path: azureauth-${{ github.event.inputs.version }}-osx-x64.tar.gz - - name: Create osx-arm64 archive - run: | - cd azureauth-${{ github.event.inputs.version }}-osx-arm64 - chmod +x azureauth createdump *.dylib - tar -czf ../azureauth-${{ github.event.inputs.version }}-osx-arm64.tar.gz * - - name: Upload osx-arm64 artifact - uses: actions/upload-artifact@v3 - with: - name: azureauth-${{ github.event.inputs.version }}-osx-arm64.tar.gz - path: azureauth-${{ github.event.inputs.version }}-osx-arm64.tar.gz - - release: - runs-on: ubuntu-latest - needs: [package] - # The 'release' environment is what requires reviews before creating the release. - environment: - name: release - # These permissions are required in order to use `softprops/action-gh-release` to upload. - permissions: - contents: write - steps: - - name: Download win10-x64 artifact - uses: actions/download-artifact@v3 - with: - name: azureauth-${{ github.event.inputs.version }}-win10-x64.zip - - name: Download osx-x64 artifact - uses: actions/download-artifact@v3 - with: - name: azureauth-${{ github.event.inputs.version }}-osx-x64.tar.gz - - name: Download osx-arm64 artifact - uses: actions/download-artifact@v3 - with: - name: azureauth-${{ github.event.inputs.version }}-osx-arm64.tar.gz - - - name: Create Release - uses: softprops/action-gh-release@v1 - with: - name: ${{ github.event.inputs.version }} - body: "Release ${{ github.event.inputs.version }}. See [`CHANGELOG.md`](https://github.com/AzureAD/microsoft-authentication-cli/blob/${{ github.event.inputs.version }}/CHANGELOG.md) for updates." - tag_name: ${{ github.event.inputs.version }} - prerelease: ${{ github.event.inputs.prerelease }} - files: | - azureauth-${{ github.event.inputs.version }}-win10-x64.zip - azureauth-${{ github.event.inputs.version }}-osx-x64.tar.gz - azureauth-${{ github.event.inputs.version }}-osx-arm64.tar.gz diff --git a/bin/sign.py b/bin/sign.py deleted file mode 100644 index 2b48ad78..00000000 --- a/bin/sign.py +++ /dev/null @@ -1,367 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -"""A script which wraps ESRPClient.exe for code signing.""" - -import json -import os -import subprocess -import sys -from argparse import ArgumentParser -from argparse import ArgumentDefaultsHelpFormatter -from argparse import Namespace -from collections.abc import Iterator -from collections.abc import Generator -from contextlib import ExitStack -from contextlib import contextmanager -from pathlib import Path -from typing import Any -from zipfile import ZipFile -from zipfile import ZIP_DEFLATED - -JSON = dict[str, Any] # A naive type alias for JSON. - - -def sign_operation(key_code: str, operation: str) -> JSON: - """Return the JSON signing operation for a given key code/operation.""" - return { - "KeyCode": key_code, - "OperationCode": operation, - "Parameters": { - "OpusName": "Microsoft", - "OpusInfo": "http://www.microsoft.com", - "FileDigest": '/fd "SHA256"', - "PageHash": "/NPH", - "TimeStamp": '/tr "http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer" /td sha256', - }, - "ToolName": "sign", - "ToolVersion": "1.0", - } - - -def sign_operation_linux(key_code: str, operation: str) -> JSON: - return { - "KeyCode": key_code, - "OperationCode": operation, - "Parameters": {}, - "ToolName": "sign", - "ToolVersion": "1.0", - } - - -def linux_sign(key_code: str) -> JSON: - """Return the JSON for a `LinuxSign` operation.""" - return sign_operation_linux(key_code, operation="LinuxSign") - - -def mac_app_developer_sign(key_code: str) -> JSON: - """Return the JSON for a `MacAppDeveloperSign` operation.""" - return sign_operation(key_code, operation="MacAppDeveloperSign") - - -def sign_tool_sign(key_code: str) -> JSON: - """Return the JSON for a `SigntoolSign` operation.""" - return sign_operation(key_code, operation="SigntoolSign") - - -def sign_tool_verify(key_code: str) -> JSON: - """Return the JSON for a `SigntoolVerify` operation.""" - return { - "KeyCode": key_code, - "OperationCode": "SigntoolVerify", - "Parameters": {}, - "ToolName": "sign", - "ToolVersion": "1.0", - } - - -def sign_request_file(source: Path, customer_correlation_id: str) -> JSON: - """Return the JSON for a `SignRequestFiles` entry.""" - return { - "CustomerCorrelationId": customer_correlation_id, - "SourceLocation": source.name, - "DestinationLocation": source.name, - } - - -def batch(source: Path, files: list[JSON], operations: list[JSON]) -> JSON: - """Return a single signing batch for a given set of files and operations.""" - return { - "SourceLocationType": "UNC", - "SourceRootDirectory": str(source), - "DestinationLocationType": "UNC", - "DestinationRootDirectory": str(source), - "SignRequestFiles": files, - "SigningInfo": {"Operations": operations}, - } - - -@contextmanager -def windows_batches( - source: Path, - key_codes: dict[str, str], - customer_correlation_id: str, -) -> Generator[JSON, None, None]: - """Yield the JSON signing batches for the win-x64 runtime.""" - files = [ - sign_request_file(path, customer_correlation_id) - for path in source.iterdir() - if path.suffix in [".exe", ".dll"] and path.is_file() - ] - - key_code = key_codes["authenticode"] - operations = [sign_tool_sign(key_code), sign_tool_verify(key_code)] - - # Yield the batches to ESRPClient.exe signing. - yield { - "Version": "1.0.0", - "SignBatches": [batch(source, files, operations)], - } - - -@contextmanager -def osx_batches( - source: Path, - key_codes: dict[str, str], - customer_correlation_id: str, -) -> Generator[JSON, None, None]: - """Yield the JSON signing batches for the osx-x64 and osx-arm64 runtimes.""" - dlls = [] - dylibs = [] - dylibs_zip = source / "dylibs.zip" - - # Find .dlls and .dylibs (including azureauth). - for path in source.iterdir(): - if path.suffix == ".dll" and path.is_file(): - dlls.append(path) - elif (path.name == "azureauth" or path.suffix == ".dylib") and path.is_file(): - dylibs.append(path) - - with ZipFile(dylibs_zip, mode="w", compression=ZIP_DEFLATED) as file: - for path in dylibs: - file.write(path, path.relative_to(source)) - - dll_files = [sign_request_file(dll, customer_correlation_id) for dll in dlls] - dylib_files = [sign_request_file(dylibs_zip, customer_correlation_id)] - - authenticode_key_code = key_codes["authenticode"] - mac_key_code = key_codes["mac"] - dll_operations = [ - sign_tool_sign(authenticode_key_code), - sign_tool_verify(authenticode_key_code), - ] - dylib_operations = [mac_app_developer_sign(mac_key_code)] - - # Yield the batches to ESRPClient.exe signing. - yield { - "Version": "1.0.0", - "SignBatches": [ - batch(source, dll_files, dll_operations), - batch(source, dylib_files, dylib_operations), - ], - } - - # At this point signing is finished. Extract the signed dylibs. - with ZipFile(dylibs_zip, mode="r") as file: - file.extractall(source) - dylibs_zip.unlink() - - -@contextmanager -def linux_batches( - source: Path, - key_codes: dict[str, str], - customer_correlation_id: str, -) -> Generator[JSON, None, None]: - """Yield the JSON signing batches for the linux-x64 runtime.""" - files = [ - sign_request_file(path, customer_correlation_id) - for path in source.iterdir() - if path.suffix in [".deb"] and path.is_file() - ] - - key_code = key_codes["linux"] - operations = [linux_sign(key_code)] - - # Yield the batches to ESRPClient.exe signing. - yield { - "Version": "1.0.0", - "SignBatches": [batch(source, files, operations)], - } - - -def auth(tenant_id: str, client_id: str) -> JSON: - """Return auth JSON metadata.""" - return { - "Version": "1.0.0", - "AuthenticationType": "AAD_CERT", - "TenantId": tenant_id, - "ClientId": client_id, - "AuthCert": { - "SubjectName": f"CN={client_id}.microsoft.com", - "StoreLocation": "LocalMachine", - "StoreName": "My", - "SendX5c": "true", - }, - "RequestSigningCert": { - "SubjectName": f"CN={client_id}", - "StoreLocation": "LocalMachine", - "StoreName": "My", - }, - } - - -def policy() -> JSON: - """Return policy JSON metadata.""" - return { - "Version": "1.0.0", - "Intent": "Product Release", - "ContentType": "Signed Binaries", - } - - -@contextmanager -def json_tempfile(path: Path, data: JSON) -> Generator[None, None, None]: - """Create a JSON file with the given data and later remove it.""" - with path.open(mode="w") as file: - json.dump(obj=data, fp=file, indent=2) - yield - path.unlink() - - -def parse_env_vars(runtime: str) -> tuple[str, str, str, JSON]: - """Parse and return environment variables""" - try: - aad_id = os.environ["SIGNING_AAD_ID"] - tenant_id = os.environ["SIGNING_TENANT_ID"] - customer_correlation_id = os.environ["SIGNING_CUSTOMER_CORRELATION_ID"] - match runtime: - case "win10-x64": - # This key code is used for signing .exes and .dlls on both Windows and Mac. - key_codes = { - "authenticode": os.environ["SIGNING_KEY_CODE_AUTHENTICODE"] - } - case "osx-x64" | "osx-arm64": - # SIGNING_KEY_CODE_AUTHENTICODE is used for signing .exes and .dlls on both Windows and Mac. - # SIGNING_KEY_CODE_MAC is used for signing .dylibs on Macs. - key_codes = { - "authenticode": os.environ["SIGNING_KEY_CODE_AUTHENTICODE"], - "mac": os.environ["SIGNING_KEY_CODE_MAC"], - } - case "linux-x64" | "linux-arm64": - # This key code is used for signing .deb on Linux. - key_codes = {"linux": os.environ["SIGNING_KEY_CODE_LINUX"]} - - return aad_id, tenant_id, customer_correlation_id, key_codes - except KeyError as exc: - # See https://stackoverflow.com/a/24999035/3288364. - name = str(exc).replace("'", "") - raise KeyError(f"Error: missing env var: {name}") - - -def parse_args() -> Namespace: - """Parse and return command line arguments.""" - cwd = Path.cwd() - parser = ArgumentParser( - description=__doc__, - formatter_class=ArgumentDefaultsHelpFormatter, - ) - - parser.add_argument( - "esrp_client", - help="the path to the ESRPClient.exe binary", - type=Path, - ) - parser.add_argument( - "--source", - metavar="SRC", - help="the source path", - type=Path, - default=str(cwd), - ) - parser.add_argument( - "--runtime", - choices=["win10-x64", "osx-x64", "osx-arm64", "linux-x64"], - help="the runtime of the build in source", - default="win10-x64", - ) - - return parser.parse_args() - - -def main() -> None: - """Determine target runtime, generate inputs, and run ESRPClient.exe.""" - # 1. Parse command line arguments. - args = parse_args() - runtime = args.runtime.lower() - - # 2. Read env vars. - aad_id, tenant_id, customer_correlation_id, key_codes = parse_env_vars(runtime) - - esrp_path = args.esrp_client.resolve() - source_path = args.source.resolve() - auth_path = Path("auth.json").resolve() - policy_path = Path("policy.json").resolve() - input_path = Path("input.json").resolve() - output_path = Path("output.json").resolve() - - # 3. Determine runtime & create a batchmaker. - match runtime: - case "win10-x64": - batchmaker = windows_batches( - source=source_path, - key_codes=key_codes, - customer_correlation_id=customer_correlation_id, - ) - case "osx-x64" | "osx-arm64": - batchmaker = osx_batches( - source=source_path, - key_codes=key_codes, - customer_correlation_id=customer_correlation_id, - ) - case "linux-x64": - batchmaker = linux_batches( - source=source_path, - key_codes=key_codes, - customer_correlation_id=customer_correlation_id, - ) - case _: - # This should be unreachable because of argparse, but let's be safe. - sys.exit(f"Error: Invalid runtime: {args.runtime}") - - # 4. Create the necessary context and run ESRPClient. - esrp_args = [ - str(esrp_path), - "sign", - "-a", - str(auth_path), - "-i", - str(input_path), - "-p", - str(policy_path), - "-o", - str(output_path), - "-l", - "Progress", - ] - - # All temporary files created in this context should be cleaned up. - with ExitStack() as stack: - # Generate auth.json. - auth_json = auth(tenant_id, aad_id) - stack.enter_context(json_tempfile(auth_path, auth_json)) - - # Generate policy.json. - policy_json = policy() - stack.enter_context(json_tempfile(policy_path, policy_json)) - - # Generate input.json (and any supporting intermediate files). - batches = stack.enter_context(batchmaker) - stack.enter_context(json_tempfile(input_path, batches)) - - # Run ESRPClient.exe. - subprocess.run(esrp_args, check=True) - - -if __name__ == "__main__": - main()