diff --git a/.github/workflows/nuget-sign-artifacts.yaml b/.github/workflows/nuget-sign-artifacts.yaml new file mode 100644 index 0000000..5fb4958 --- /dev/null +++ b/.github/workflows/nuget-sign-artifacts.yaml @@ -0,0 +1,144 @@ +name: Sign NuGet Artifacts + +on: + workflow_call: + inputs: + artifact-glob: + description: Directory path containing NuGet packages to sign (e.g. ./sign or ./artifacts) + required: true + type: string + output-path: + description: Output directory for signed packages (e.g. ./artifacts or full path) + required: false + type: string + default: ${{ github.workspace }} + artifact-name: + description: Name for the uploaded artifacts + required: false + type: string + default: signed-nuget-artifacts + retention-days: + description: Retention days for the artifacts + required: false + type: number + default: 7 + nuget-environment: + description: SSL.com environment name for NuGet signing + required: false + type: string + default: PROD + jvm-max-memory: + description: Maximum JVM memory for NuGet signing process + required: false + type: string + default: 1024M + malware-block: + description: Enable malware blocking during signing + required: false + type: boolean + default: false + override: + description: Override existing signatures + required: false + type: boolean + default: false + runs-on: + description: The runner to use for the build + required: false + type: string + default: ubuntu-latest + download-artifact-name: + description: Name of the artifact to download before signing (optional) + required: false + type: string + secrets: + ES_USERNAME: + description: SSL.com username for NuGet signing + required: true + ES_PASSWORD: + description: SSL.com password for NuGet signing + required: true + CREDENTIAL_ID: + description: SSL.com credential ID for NuGet signing + required: true + ES_TOTP_SECRET: + description: SSL.com TOTP secret for NuGet signing + required: true + +permissions: + contents: read + packages: read + +jobs: + sign-nuget: + runs-on: ${{ inputs.runs-on }} + steps: + - uses: actions/checkout@v4 + + - name: Download artifacts (if specified) + if: ${{ inputs.download-artifact-name != '' }} + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.download-artifact-name }} + path: ${{ inputs.artifact-glob }} + + - name: Check for NuGet packages + run: | + echo "Checking for NuGet packages in: ${{ inputs.artifact-glob }}" + echo "Current working directory: $(pwd)" + echo "Workspace directory: ${{ github.workspace }}" + + # Handle both relative and absolute paths + if [[ "${{ inputs.artifact-glob }}" == /* ]]; then + SEARCH_PATH="${{ inputs.artifact-glob }}" + else + SEARCH_PATH="${{ github.workspace }}/${{ inputs.artifact-glob }}" + fi + + echo "Searching in: $SEARCH_PATH" + NUGET_PACKAGES=$(find "$SEARCH_PATH" -name "*.nupkg" -type f 2>/dev/null || echo "") + if [ -n "$NUGET_PACKAGES" ]; then + echo "Found NuGet packages:" + echo "$NUGET_PACKAGES" + else + echo "No NuGet packages found in $SEARCH_PATH" + echo "Directory contents:" + ls -la "$SEARCH_PATH" 2>/dev/null || echo "Directory does not exist" + exit 1 + fi + + - name: Sign NuGet Packages with SSL.com + uses: sslcom/esigner-codesign@a272724cb13abe0abc579c6c40f7899969b6942b + with: + command: batch_sign + username: ${{secrets.ES_USERNAME}} + password: ${{secrets.ES_PASSWORD}} + credential_id: ${{secrets.CREDENTIAL_ID}} + totp_secret: ${{secrets.ES_TOTP_SECRET}} + dir_path: ${{ inputs.artifact-glob }} + output_path: ${{ inputs.output-path == 'artifacts' && format('{0}/artifacts', github.workspace) || inputs.output-path || github.workspace }} + malware_block: ${{ inputs.malware-block || false }} + override: ${{ inputs.override || false }} + + environment_name: ${{ inputs.nuget-environment }} + clean_logs: true + jvm_max_memory: ${{ inputs.jvm-max-memory }} + signing_method: v1 + + - name: Verify NuGet Packages + run: | + echo "Verifying signed NuGet packages..." + OUTPUT_DIR="${{ inputs.output-path == 'artifacts' && format('{0}/artifacts', github.workspace) || inputs.output-path || github.workspace }}" + if [ -d "$OUTPUT_DIR" ]; then + find "$OUTPUT_DIR" -name "*.nupkg" -type f | while read -r file; do + echo "Verifying: $file" + dotnet nuget verify "$file" --all || echo "Warning: Could not verify $file" + done + fi + + - name: Upload Signed NuGet Artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-name }} + path: ${{ inputs.output-path == 'artifacts' && format('{0}/artifacts', github.workspace) || inputs.output-path || github.workspace }} + retention-days: ${{ inputs.retention-days }} diff --git a/.github/workflows/reusable_sign-artifacts.yaml b/.github/workflows/reusable_sign-artifacts.yaml index 585daa6..6522aa5 100644 --- a/.github/workflows/reusable_sign-artifacts.yaml +++ b/.github/workflows/reusable_sign-artifacts.yaml @@ -4,7 +4,7 @@ on: workflow_call: inputs: artifact-glob: - description: Glob pattern to match artifacts to sign (e.g. dist/**/*.{jar,deb,rpm}) + description: Glob pattern to match artifacts to sign (e.g. dist/**/*.{jar,deb,rpm,nupkg}) required: true type: string artifact-name: @@ -16,7 +16,7 @@ on: description: Retention days for the artifacts required: false type: number - default: 1 + default: 7 artifactory-url: required: false description: JFrog Artifactory URL @@ -37,7 +37,6 @@ on: required: false type: string default: ubuntu-22.04 - secrets: gpg-private-key: required: true @@ -45,9 +44,11 @@ on: required: true gpg-key-pass: required: true + permissions: contents: read packages: read + jobs: sign: runs-on: ${{ inputs.runs-on }} @@ -65,10 +66,11 @@ jobs: run: | sudo apt-get update && sudo apt-get install dpkg-sig dpkg-dev -y - - name: Sign Artifacts + - name: Sign Artifacts with GPG run: | chmod +x ${{ github.workspace }}/.github/workflows/sign-artifacts/entrypoint.sh ${{ github.workspace }}/.github/workflows/sign-artifacts/entrypoint.sh "${{ inputs.artifact-glob }}" "${{ inputs.artifact-name }}" + - name: Upload Artifacts uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/sign-artifacts/entrypoint.sh b/.github/workflows/sign-artifacts/entrypoint.sh index cb608e3..1ff9b3a 100644 --- a/.github/workflows/sign-artifacts/entrypoint.sh +++ b/.github/workflows/sign-artifacts/entrypoint.sh @@ -40,7 +40,6 @@ echo "Processing all files in target directory: $TARGET_DIR" find "$TARGET_DIR" -type f | while read -r file; do echo "Processing: $file" - # Skip signature and checksum files to prevent infinite loops if [[ "$file" =~ \.(asc|sha256)$ ]]; then continue @@ -80,8 +79,13 @@ find "$TARGET_DIR" -type f | while read -r file; do # SHA256 checksum for signature file shasum -a 256 "$file.asc" > "$file.asc.sha256" - echo "Signed: $file" + echo "GPG Signed: $file" echo " Signature: $file.asc" echo " Checksum: $file.sha256" echo " Sig Checksum: $file.asc.sha256" + + # Note about NuGet packages + if [[ "$ext" == "nupkg" ]]; then + echo " Note: NuGet package detected - will be signed by SSL.com if enabled in workflow" + fi done diff --git a/.vscode/settings.json b/.vscode/settings.json index bac3dec..cb31e0d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "yaml.schemas": {}, - "cSpell.words": ["aerospike", "kennylong", "kennylong's"] + "cSpell.words": ["aerospike", "kennylong", "kennylong's"], + "postman.settings.dotenv-detection-notification-visibility": false } diff --git a/README.md b/README.md index 5a099f8..c8628cf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,151 @@ -# shared-workflows +# Shared Workflows + +This repository contains reusable GitHub Actions workflows for common CI/CD tasks. + +## Available Workflows + +### Sign Packages + +- **File**: `.github/workflows/reusable_sign-artifacts.yaml` +- **Purpose**: Sign RPM, DEB, and NuGet packages with GPG and SSL.com certificates +- **Usage**: See [Sign Artifacts Documentation](#sign-artifacts) + +### Upload Artifacts + +- **File**: `.github/workflows/reusable_upload-artifacts.yaml` +- **Purpose**: Upload artifacts to various destinations +- **Usage**: See [Upload Artifacts Documentation](#upload-artifacts) + +## Sign Artifacts + +The reusable sign artifacts workflow signs RPM, DEB, and NuGet packages using GPG and SSL.com certificates. This workflow is designed to be called after a build workflow that produces packages as artifacts. + +### Features + +- **Multi-format support**: Signs RPM and DEB packages with GPG, optionally signs NuGet packages with SSL.com certificates +- **Automatic detection**: Automatically detects NuGet packages and signs them when enabled +- **Flexible configuration**: Supports custom artifact patterns, paths, and signing settings +- **Secure signing**: Uses GPG for RPM/DEB and SSL.com certificates for NuGet packages +- **Verification**: Includes built-in package verification steps + +### Basic Usage + +```yaml +name: Build and Sign My Packages + +on: + workflow_dispatch: + push: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + steps: + # Your build steps here that produce packages + - name: Build Packages + run: | + # Build RPM/DEB packages + # Build NuGet packages + dotnet pack --output artifacts + + - name: Upload Packages + uses: actions/upload-artifact@v4 + with: + name: packages + path: artifacts/ + + sign: + needs: build + uses: aerospike/shared-workflows/.github/workflows/reusable_sign-artifacts.yaml@main + with: + artifact-glob: artifacts/**/*.{deb,rpm,nupkg} + enable-nuget-signing: true + secrets: + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-public-key: ${{ secrets.GPG_PUBLIC_KEY }} + gpg-key-pass: ${{ secrets.GPG_KEY_PASS }} + es-username: ${{ secrets.ES_USERNAME }} + es-password: ${{ secrets.ES_PASSWORD }} + credential-id: ${{ secrets.CREDENTIAL_ID }} + es-totp-secret: ${{ secrets.ES_TOTP_SECRET }} +``` + +### Advanced Usage + +```yaml +name: Build and Sign My Packages + +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + # Your build steps here + - name: Upload Packages + uses: actions/upload-artifact@v4 + with: + name: my-custom-packages + path: dist/ + + sign: + needs: build + uses: aerospike/shared-workflows/.github/workflows/reusable_sign-artifacts.yaml@main + with: + artifact-glob: dist/**/*.{deb,rpm,nupkg} + output-dir: signed-packages + retention-days: 30 + enable-nuget-signing: true + nuget-environment: PROD + jvm-max-memory: 2048M + secrets: + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-public-key: ${{ secrets.GPG_PUBLIC_KEY }} + gpg-key-pass: ${{ secrets.GPG_KEY_PASS }} + es-username: ${{ secrets.ES_USERNAME }} + es-password: ${{ secrets.ES_PASSWORD }} + credential-id: ${{ secrets.CREDENTIAL_ID }} + es-totp-secret: ${{ secrets.ES_TOTP_SECRET }} +``` + +### Required Secrets + +You need to set up the following secrets in your repository: + +**For GPG signing (RPM/DEB packages):** + +- `GPG_PRIVATE_KEY`: Your GPG private key +- `GPG_PUBLIC_KEY`: Your GPG public key +- `GPG_KEY_PASS`: Your GPG key passphrase + +**For SSL.com signing (NuGet packages):** + +- `ES_USERNAME`: Your SSL.com username +- `ES_PASSWORD`: Your SSL.com password +- `CREDENTIAL_ID`: Your SSL.com credential ID +- `ES_TOTP_SECRET`: Your SSL.com TOTP secret + +### Input Parameters + +| Parameter | Description | Required | Default | +| ---------------------- | ----------------------------------------- | -------- | ------------------ | +| `artifact-glob` | Glob pattern to match artifacts to sign | Yes | - | +| `output-dir` | Output directory for signed artifacts | No | `signed-artifacts` | +| `retention-days` | Number of days to retain artifacts | No | `7` | +| `enable-nuget-signing` | Enable SSL.com signing for NuGet packages | No | `false` | +| `nuget-environment` | SSL.com environment for NuGet signing | No | `PROD` | +| `jvm-max-memory` | Maximum JVM memory for NuGet signing | No | `1024M` | + +### How It Works + +1. **Download artifacts**: Downloads the specified artifacts containing packages +2. **GPG signing**: Signs RPM and DEB packages with GPG, creates detached signatures and checksums +3. **NuGet detection**: Automatically detects NuGet packages in the artifacts +4. **SSL.com signing**: Signs NuGet packages with SSL.com certificates (if enabled) +5. **Upload results**: Uploads all signed packages as artifacts +6. **Verify signatures**: Verifies signed packages and provides summaries ## Introduction @@ -61,18 +208,18 @@ GitHub Actions and Workflows in the same repository necessarily share a version. We suggest that you pin these actions/workflows to a specific sha with a comment of the semver tag. This way you can use dependabot to keep your workflows up to date. See [dependabot.yml](.github/dependabot.yml) for an example of this. ```yaml -# GOOD +# ✅ GOOD uses: aerospike/shared-workflows/actions/setup-gpg@ed780e9928d56ef074532dbc6877166d5460587a # v0.1.0 # pro: reproducible builds, allows you to specify a known version of the action # pro: dependabot can auto-PR updates to your repo, will also update version comment # pro: official GitHub security hardening best practice -# BAD +# ❌ BAD uses: aerospike/shared-workflows/actions/setup-gpg@v0.1.0 # pro: dependabot can auto-PR updates to your repo # con: tags are not immutable. 'semver' hint not usable with semver niceties (pessimistic versioning, etc) -# BAD +# ❌ BAD uses: aerospike/shared-workflows/actions/setup-gpg@main # con: unsupported versioning usage: if this breaks for you, you will be told you should've pinned to a sha # con: Requires that main is always backwards compatible and never breaks anything ever (not possible)