diff --git a/.github/actions/create-signed-pull-request/action.yml b/.github/actions/create-signed-pull-request/action.yml new file mode 100644 index 000000000000..48e9690fc6cd --- /dev/null +++ b/.github/actions/create-signed-pull-request/action.yml @@ -0,0 +1,58 @@ +name: 'Create Signed Pull Request' +description: 'Creates or updates a pull request whose commit is signed by GitHub' + +# Thin wrapper over peter-evans/create-pull-request that always sets sign-commits +# Signing needs a bot token: our dd-octo-sts tokens are GitHub App tokens, so the commit is signed +# and attributed to the app. A PAT would silently produce unsigned commits. + +inputs: + token: + description: 'GitHub App token used to commit and manage the pull request. Needs contents:write and pull_requests:write.' + required: true + branch: + description: 'Head branch to create or update' + required: true + commit-message: + description: 'Message for the commit' + required: true + title: + description: 'Pull request title' + required: true + base: + description: 'Base branch for the pull request. Defaults to the branch that was checked out.' + required: false + body: + description: 'Pull request body' + required: false + labels: + description: 'Comma-separated labels to apply' + required: false + reviewers: + description: 'Comma-separated reviewers to request. GitHub usernames, not teams.' + required: false + +outputs: + pull-request-number: + description: 'Number of the created or updated pull request' + value: ${{ steps.pr.outputs.pull-request-number }} + pull-request-url: + description: 'URL of the created or updated pull request' + value: ${{ steps.pr.outputs.pull-request-url }} + +runs: + using: "composite" + steps: + - name: Create pull request + id: pr + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + token: ${{ inputs.token }} + sign-commits: true + branch: ${{ inputs.branch }} + commit-message: ${{ inputs.commit-message }} + title: ${{ inputs.title }} + base: ${{ inputs.base }} + body: ${{ inputs.body }} + labels: ${{ inputs.labels }} + reviewers: ${{ inputs.reviewers }} + delete-branch: true diff --git a/.github/workflows/auto_bump_smoke_test_docker_images.yml b/.github/workflows/auto_bump_smoke_test_docker_images.yml index 443efd2cc581..41251269ace0 100644 --- a/.github/workflows/auto_bump_smoke_test_docker_images.yml +++ b/.github/workflows/auto_bump_smoke_test_docker_images.yml @@ -27,6 +27,8 @@ jobs: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 with: @@ -51,7 +53,7 @@ jobs: - name: Create Pull Request id: pr - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + uses: ./.github/actions/create-signed-pull-request with: token: ${{ steps.octo-sts.outputs.token }} # The branch name is referenced in .azure-pipelines/ultimate-pipeline.yml @@ -59,7 +61,6 @@ jobs: # Keep the two in sync if this ever changes. branch: "bot/smoke-test-docker-image-bump" commit-message: "[Smoke Test Docker Image Bump]" - delete-branch: true base: master title: "[Smoke Test Docker Image Bump] Updating docker image tags " labels: "area:dependabot,area:test-apps,dependencies" diff --git a/.github/workflows/auto_bump_test_package_versions.yml b/.github/workflows/auto_bump_test_package_versions.yml index 81a781c42899..9acc10107cdc 100644 --- a/.github/workflows/auto_bump_test_package_versions.yml +++ b/.github/workflows/auto_bump_test_package_versions.yml @@ -29,6 +29,8 @@ jobs: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 with: @@ -53,15 +55,13 @@ jobs: - name: Create Pull Request id: pr - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + uses: ./.github/actions/create-signed-pull-request with: token: ${{ steps.octo-sts.outputs.token }} branch: "bot/test-package-versions-bump" commit-message: "[Test Package Versions Bump]" - delete-branch: true base: master title: "[Test Package Versions Bump] Updating package versions " - milestone: "${{steps.rename.outputs.milestone}}" labels: "area:dependabot,area:test-apps,dependencies" body: | Updates the package versions for integration tests. See below for a summary of the changes. diff --git a/.github/workflows/auto_create_version_bump_pr.yml b/.github/workflows/auto_create_version_bump_pr.yml index 31cfbed2cd59..abf31590a816 100644 --- a/.github/workflows/auto_create_version_bump_pr.yml +++ b/.github/workflows/auto_create_version_bump_pr.yml @@ -46,11 +46,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ steps.select_branch.outputs.ref }} - - - name: "Configure Git Credentials" - run: | - git config user.name "${{ github.actor }}" - git config user.email "${{ github.actor }}@users.noreply.github.com" + persist-credentials: false - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 with: @@ -77,12 +73,12 @@ jobs: - name: Create Pull Request id: pr - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + uses: ./.github/actions/create-signed-pull-request with: token: ${{ steps.octo-sts.outputs.token }} branch: "version-bump-${{steps.versions.outputs.full_version}}" commit-message: "[Version Bump] ${{steps.versions.outputs.full_version}}" - delete-branch: true + base: ${{ steps.select_branch.outputs.ref }} title: "[Version Bump] ${{steps.versions.outputs.full_version}}" reviewers: "DataDog/apm-dotnet" body: "${{steps.changes.outputs.release_notes}}" diff --git a/.github/workflows/create_hotfix_branch.yml b/.github/workflows/create_hotfix_branch.yml index fcecc7127bce..eae53877b40b 100644 --- a/.github/workflows/create_hotfix_branch.yml +++ b/.github/workflows/create_hotfix_branch.yml @@ -40,11 +40,8 @@ jobs: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - - name: "Configure Git Credentials" - run: | - git config user.name "${{ github.actor }}" - git config user.email "${{ github.actor }}@users.noreply.github.com" + with: + persist-credentials: false - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 with: @@ -61,9 +58,16 @@ jobs: id: versions run: .\tracer\build.ps1 OutputCurrentVersionToGitHub + - name: "Stage version bump" + run: git add . + - name: "Push hotfix branch" - run: | - git checkout -b hotfix/${{ steps.versions.outputs.full_version }} - git add . - git commit -m "[Version Bump] ${{steps.versions.outputs.full_version}}" - git push origin -u hotfix/${{ steps.versions.outputs.full_version }} + uses: DataDog/commit-headless@2801f6e08acb3a69b6c4d7b0d5deef27c1a15bc7 # action/v3.3.1 + with: + command: commit + branch: hotfix/${{ steps.versions.outputs.full_version }} + message: "[Version Bump] ${{steps.versions.outputs.full_version}}" + # Keeps whoever dispatched the workflow as a Co-authored-by trailer. + author: "${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>" + head-sha: ${{ github.sha }} + create-branch: true diff --git a/.github/workflows/force_manual_version_bump.yml b/.github/workflows/force_manual_version_bump.yml index 1751dce716ac..a3ca43b13cb5 100644 --- a/.github/workflows/force_manual_version_bump.yml +++ b/.github/workflows/force_manual_version_bump.yml @@ -35,6 +35,8 @@ jobs: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 with: @@ -53,12 +55,11 @@ jobs: - name: Create Pull Request id: pr - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + uses: ./.github/actions/create-signed-pull-request with: token: ${{ steps.octo-sts.outputs.token }} branch: "version-bump-${{steps.versions.outputs.full_version}}" commit-message: "[Version Bump] ${{steps.versions.outputs.full_version}}" - delete-branch: true title: "[Version Bump] ${{steps.versions.outputs.full_version}}" reviewers: "DataDog/apm-dotnet" body: "${{steps.changes.outputs.release_notes}}" diff --git a/.github/workflows/generate_package_versions.yml b/.github/workflows/generate_package_versions.yml index 959085983c37..c355db15a49f 100644 --- a/.github/workflows/generate_package_versions.yml +++ b/.github/workflows/generate_package_versions.yml @@ -23,6 +23,8 @@ jobs: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 with: @@ -31,11 +33,21 @@ jobs: - name: "Regenerating package versions" run: .\tracer\build.ps1 GeneratePackageVersions - - name: Create commits + - name: Stage regenerated files run: | - git config user.name 'github-actions[bot]' - git config user.email 'github-actions[bot]@users.noreply.github.com' - git commit -am "Updated package versions" - git push - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + git add -u + + # Regenerating and finding no changes means something is wrong, so fail instead. + if (-not (git diff --cached --name-only)) { + Write-Host "::error::GeneratePackageVersions produced no changes" + exit 1 + } + + - name: Push signed commit + uses: DataDog/commit-headless@2801f6e08acb3a69b6c4d7b0d5deef27c1a15bc7 # action/v3.3.1 + with: + command: commit + branch: ${{ github.ref_name }} + message: "Updated package versions" + # Suppress commit trailer + author: '' diff --git a/docs/development/GitHubActionsSecurity.md b/docs/development/GitHubActionsSecurity.md index 24728727faf4..5881f5c7a44d 100644 --- a/docs/development/GitHubActionsSecurity.md +++ b/docs/development/GitHubActionsSecurity.md @@ -47,6 +47,32 @@ Dependabot preserves the SHA-pin + `# vX.Y.Z` comment format when bumping. Revie --- +## Signed commits + +Commits reaching `master` must be signed, and `repository.datadog.yml` grants no exceptions. A workflow therefore must never create a commit with `git push` — git-created commits are unsigned and will block the PR on the `commit-signatures` merge gate. + +Instead, push through the GitHub API, which signs the commit server-side. Two options: + +- **Creating or updating a pull request** — use the local composite action: + ```yaml + - uses: ./.github/actions/create-signed-pull-request + with: + token: ${{ steps.octo-sts.outputs.token }} + branch: bot/my-branch + commit-message: "[My Bot] Update things" + title: "[My Bot] Update things" + base: master + ``` + It wraps `peter-evans/create-pull-request` with `sign-commits: true` always set, so the commit is created through the GitHub API and signed. Use the action rather than calling `peter-evans/create-pull-request` directly — that way the signing cannot be left off by mistake. + +- **Pushing to a branch without a pull request** — stage the changes with `git add`, then use [`DataDog/commit-headless`](https://github.com/DataDog/commit-headless) directly with `command: commit`. It builds the commit from the index, so no local `git commit` is needed. See `create_hotfix_branch.yml` and `generate_package_versions.yml`. + +Any installation token works, including the default `GITHUB_TOKEN`, so a job that only needs to push a commit does not need dd-octo-sts — just `permissions: contents: write`. Use dd-octo-sts where the existing reason for it still applies, such as needing a PR to trigger other workflows. + +Background: [Commit Headless (sign bot commits)](https://datadoghq.atlassian.net/wiki/spaces/DEVX/pages/5580588264) and the [Commit Signing Enforcement FAQ](https://datadoghq.atlassian.net/wiki/spaces/DEVX/pages/5105058311). + +--- + ## Reviewer checklist When reviewing a PR that touches `.github/workflows/` or `.github/actions/`: @@ -55,4 +81,5 @@ When reviewing a PR that touches `.github/workflows/` or `.github/actions/`: - [ ] Every new action is on the allowlist (or the allowlist has been updated in the same/accompanying change). - [ ] Local `./` refs (`uses: ./.github/actions/...`, `uses: ./.github/workflows/...`) are **not** version-pinned — leave them as-is. - [ ] The `# vX.Y.Z` comment reflects the actual version the SHA resolves to. +- [ ] No step creates a commit with `git push` — see [Signed commits](#signed-commits). diff --git a/repository.datadog.yml b/repository.datadog.yml index 05135e0943e8..4844e1c12154 100644 --- a/repository.datadog.yml +++ b/repository.datadog.yml @@ -8,7 +8,4 @@ kind: mergegate rules: - require: pull-request-freshness enforcement: disabled - - require: commit-signatures - excluded_emails: - - '49699333+dependabot[bot]@users.noreply.github.com' # dependabot - - '41898282+github-actions[bot]@users.noreply.github.com' # version-bump bots \ No newline at end of file + - require: commit-signatures \ No newline at end of file