diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 25b63368..892fae06 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "args": { "DOCKER_GID": "${env:DOCKER_GID:}", "IMAGE_NAME": "node_24_python_3_14", - "IMAGE_VERSION": "v1.2.0", + "IMAGE_VERSION": "v1.4.4", "USER_UID": "${localEnv:USER_ID:}", "USER_GID": "${localEnv:GROUP_ID:}" }, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..0492a665 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# restrict access to approving workflow changes +.github/workflows/ @NHSDigital/eps-admins diff --git a/.github/workflows/combine-dependabot-prs.yml b/.github/workflows/combine-dependabot-prs.yml deleted file mode 100644 index b73f45ff..00000000 --- a/.github/workflows/combine-dependabot-prs.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: "Combine PRs" - -on: - workflow_call: - inputs: - branchPrefix: - description: "Branch prefix to find combinable PRs based on" - default: "dependabot" - type: string - mustBeGreen: - description: "Only combine PRs that are green (status is success)" - default: true - type: boolean - combineBranchName: - description: "Name of the branch to combine PRs into" - default: "combine-dependabot-PRs" - type: string - ignoreLabel: - description: "Exclude PRs with this label" - default: "nocombine" - type: string - - # Allow manual triggering of the workflow for this repo - workflow_dispatch: - inputs: - branchPrefix: - description: "Branch prefix to find combinable PRs based on" - default: "dependabot" - type: string - mustBeGreen: - description: "Only combine PRs that are green (status is success)" - default: true - type: boolean - combineBranchName: - description: "Name of the branch to combine PRs into" - default: "combine-dependabot-PRs" - type: string - ignoreLabel: - description: "Exclude PRs with this label" - default: "nocombine" - type: string - -jobs: - combine-prs: - runs-on: ubuntu-22.04 - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - repository: NHSDigital/eps-common-workflows - sparse-checkout-cone-mode: false - sparse-checkout: | - combine-prs.js - - - name: Create Combined PR - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd - id: create-combined-pr - env: - branchPrefix: ${{ inputs.branchPrefix }} - mustBeGreen: ${{ inputs.mustBeGreen }} - combineBranchName: ${{ inputs.combineBranchName }} - ignoreLabel: ${{ inputs.ignoreLabel }} - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - const combinePRs = require('./combine-prs.js'); - await combinePRs({ github, context, core }); diff --git a/.github/workflows/dependabot-auto-approve-and-merge.yml b/.github/workflows/dependabot-auto-approve-and-merge.yml index 6ed23f00..54e73182 100644 --- a/.github/workflows/dependabot-auto-approve-and-merge.yml +++ b/.github/workflows/dependabot-auto-approve-and-merge.yml @@ -8,14 +8,14 @@ on: AUTOMERGE_PEM: required: true -permissions: - pull-requests: write - contents: write - +permissions: {} jobs: dependabot: runs-on: ubuntu-22.04 - if: ${{ github.actor == 'dependabot[bot]' }} + permissions: + pull-requests: write + contents: write + if: (github.event.pull_request.user.login == 'dependabot[bot]' || github.event.pull_request.user.login == 'eps-create-pull-request[bot]') && github.repository == github.event.pull_request.head.repo.full_name steps: - name: Get token from Github App id: get_app_token diff --git a/.github/workflows/get-repo-config.yml b/.github/workflows/get-repo-config.yml index cc6a9260..d7d7bc52 100644 --- a/.github/workflows/get-repo-config.yml +++ b/.github/workflows/get-repo-config.yml @@ -39,9 +39,14 @@ on: description: Resolved digest for the supplied image reference value: ${{ jobs.verify_attestation.outputs.resolved_digest }} +permissions: {} jobs: get_config_values: runs-on: ubuntu-22.04 + permissions: + attestations: read + contents: read + packages: read outputs: tag_format: ${{ steps.load-config.outputs.TAG_FORMAT }} devcontainer_version: ${{ steps.load-config.outputs.DEVCONTAINER_VERSION }} @@ -51,8 +56,8 @@ jobs: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: - ref: ${{ env.BRANCH_NAME }} fetch-depth: 0 + persist-credentials: false - name: Load config value id: load-config diff --git a/.github/workflows/pr_title_check.yml b/.github/workflows/pr_title_check.yml index f8fb08ef..f26e1f9b 100644 --- a/.github/workflows/pr_title_check.yml +++ b/.github/workflows/pr_title_check.yml @@ -3,6 +3,7 @@ name: PR Title Check on: workflow_call: +permissions: {} jobs: pr_title_format_check: runs-on: ubuntu-22.04 @@ -50,43 +51,6 @@ jobs: exit 1 fi - - name: Comment on PR with Jira Link - if: steps.extract_ticket_reference.outcome == 'success' && steps.extract_ticket_reference.outputs.TICKET_REF != 'dependabot' - uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TICKET_REF: ${{ steps.extract_ticket_reference.outputs.TICKET_REF }} - with: - message: | - This PR is linked to a ticket in an NHS Digital JIRA Project. Here's a handy link to the ticket: - # [${{ env.TICKET_REF }}](https://nhsd-jira.digital.nhs.uk/browse/${{ env.TICKET_REF }}) - comment-tag: pr-link - - - name: Comment on PR for dependabot - if: steps.extract_ticket_reference.outcome == 'success' && steps.extract_ticket_reference.outputs.TICKET_REF == 'dependabot' - uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - message: | - This PR is raised by Dependabot to update a dependency. - comment-tag: pr-link - - - name: Comment on PR for bad format - if: steps.check_prefix.outcome != 'success' || steps.check_ticket_reference.outcome != 'success' - uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - message: | - The PR title does not conform to the required format. - Please ensure your PR title is prefixed with a change type (Fix, Update, New, Breaking, Docs, Build, Upgrade, Chore) - and contains a ticket reference (eg. 'Fix: [AEA-####] - ...', or 'Chore: [dependabot] - ...'), - then push an empty commit or recreate your PR. - See the contributing guide for more details: - https://github.com/NHSDigital/eps-common-workflows/blob/main/CONTRIBUTING.md - comment-tag: pr-link - - name: Fail job due to invalid PR title format if: steps.check_prefix.outcome != 'success' || steps.check_ticket_reference.outcome != 'success' run: | diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index c21199fe..5b28367f 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -7,25 +7,39 @@ on: env: BRANCH_NAME: ${{ github.event.pull_request.head.ref }} +permissions: {} jobs: dependabot-auto-approve-and-merge: needs: quality_checks uses: ./.github/workflows/dependabot-auto-approve-and-merge.yml + permissions: + contents: write + pull-requests: write secrets: AUTOMERGE_APP_ID: ${{ secrets.AUTOMERGE_APP_ID }} AUTOMERGE_PEM: ${{ secrets.AUTOMERGE_PEM }} pr_title_format_check: uses: ./.github/workflows/pr_title_check.yml + permissions: + pull-requests: write get_config_values: uses: ./.github/workflows/get-repo-config.yml + permissions: + attestations: read + contents: read + packages: read with: verify_published_from_main_image: false quality_checks: uses: ./.github/workflows/quality-checks-devcontainer.yml needs: [get_config_values] + permissions: + contents: read + id-token: write + packages: read with: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} secrets: @@ -43,4 +57,3 @@ jobs: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} branch_name: ${{ github.event.pull_request.head.ref }} tag_format: ${{ needs.get_config_values.outputs.tag_format }} - secrets: inherit diff --git a/.github/workflows/quality-checks-devcontainer.yml b/.github/workflows/quality-checks-devcontainer.yml index fc562b42..085a5e8b 100644 --- a/.github/workflows/quality-checks-devcontainer.yml +++ b/.github/workflows/quality-checks-devcontainer.yml @@ -25,9 +25,15 @@ on: type: string required: true +permissions: {} + jobs: quality_checks: runs-on: ubuntu-22.04 + permissions: + contents: read + id-token: write + packages: read container: image: ${{ inputs.pinned_image }} options: --user 1001:1001 --group-add 128 @@ -36,16 +42,19 @@ jobs: shell: bash steps: - &init_tool_versions - name: copy .tool-versions + name: copy needed files from devcontainer user to runner home directory or bin directory run: | cp /home/vscode/.tool-versions "$HOME/.tool-versions" + cp /home/vscode/.grant.yaml "$HOME/.grant.yaml" + mkdir -p "$HOME/.local/bin" + sudo cp /home/vscode/.local/bin/zizmor /usr/local/bin/zizmor - &checkout name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: - ref: ${{ env.BRANCH_NAME }} fetch-depth: 0 + persist-credentials: false - &setup_npmrc name: Setting up .npmrc @@ -67,12 +76,18 @@ jobs: make install - name: Run secrets scan run: | + git-secrets --register-aws + git-secrets --add-provider -- cat /usr/share/secrets-scanner/nhsd-rules-deny.txt make secret-scan - name: Run actionlint run: | make actionlint - - name: Check language tools used and setup trivy config + - name: Run zizmor + run: | + make zizmor + + - name: Check language tools used id: check_languages run: | if [ -f "pyproject.toml" ] && grep -q '\[tool.poetry\]' "pyproject.toml"; then @@ -121,14 +136,7 @@ jobs: fi - name: Check licenses run: | - make trivy-license-check - - - name: Show license scan output - if: always() - run: | - if [ -f license_scan.txt ]; then - cat .trivy_out/license_scan.txt - fi + make grant-scan - name: Run code lint run: | make lint @@ -140,51 +148,19 @@ jobs: - name: Run unit tests run: | make test - - name: make generate sbom + - name: Generate sbom run: | - make trivy-generate-sbom + make syft-generate-sbom-dev-dependencies - name: Upload sbom uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f with: - name: sbom.cdx.json - path: .trivy_out/sbom.cdx.json + name: sbom.dev.cdx.json + path: .sbom/sbom.dev.cdx.json - - name: Check python vulnerabilities - if: ${{ steps.check_languages.outputs.uses_poetry == 'true' }} - continue-on-error: ${{ github.actor == 'dependabot[bot]' }} + - name: Check vulnerabilities run: | - make trivy-scan-python + make grype-scan-dev-dependencies - - name: Check node vulnerabilities - if: ${{ steps.check_languages.outputs.uses_node == 'true' }} - continue-on-error: ${{ github.actor == 'dependabot[bot]' }} - run: | - make trivy-scan-node - - name: Check go vulnerabilities - if: ${{ steps.check_languages.outputs.uses_go == 'true' }} - continue-on-error: ${{ github.actor == 'dependabot[bot]' }} - run: | - make trivy-scan-go - - name: Check java vulnerabilities - if: ${{ steps.check_languages.outputs.uses_java == 'true' }} - continue-on-error: ${{ github.actor == 'dependabot[bot]' }} - run: | - make trivy-scan-java - - name: Show vulnerability output - if: always() - run: | - if [ -f .trivy_out/dependency_results_python.txt ]; then - cat .trivy_out/dependency_results_python.txt - fi - if [ -f .trivy_out/dependency_results_node.txt ]; then - cat .trivy_out/dependency_results_node.txt - fi - if [ -f .trivy_out/dependency_results_java.txt ]; then - cat .trivy_out/dependency_results_java.txt - fi - if [ -f .trivy_out/dependency_results_go.txt ]; then - cat .trivy_out/dependency_results_go.txt - fi - name: "check is SONAR_TOKEN exists" env: super_secret: ${{ secrets.SONAR_TOKEN }} @@ -279,6 +255,10 @@ jobs: echo "images=$NORMALIZED" >> "$GITHUB_OUTPUT" docker_vulnerability_scan: + permissions: + contents: read + id-token: write + packages: read runs-on: ubuntu-22.04 needs: get_docker_images_to_scan container: @@ -310,19 +290,15 @@ jobs: - name: Check docker vulnerabilities continue-on-error: ${{ github.actor == 'dependabot[bot]' }} run: | - make trivy-scan-docker + make grype-scan-docker-image env: DOCKER_IMAGE: ${{ matrix.docker_image }} - - name: Show docker vulnerability output - if: always() - run: | - echo "Scan output for ${{ matrix.docker_image }}" - if [ -f .trivy_out/dependency_results_docker.txt ]; then - cat .trivy_out/dependency_results_docker.txt - fi - IaC-validation: + permissions: + contents: read + id-token: write + packages: read runs-on: ubuntu-22.04 container: image: ${{ inputs.pinned_image }} diff --git a/.github/workflows/quality-checks.properties.json b/.github/workflows/quality-checks.properties.json deleted file mode 100644 index 0eed82a5..00000000 --- a/.github/workflows/quality-checks.properties.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "Quality Checks Workflow", - "description": "A workflow template for running quality checks including linting, testing, and SBOM generation.", - "iconName": "octicon check", - "categories": [ - "Continuous integration", - "Security" - ], - "filePatterns": [ - "^Makefile$", - "^\\.tool-versions$", - "^package\\.json$" - ] - } - \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dbb82b14..0b95dfc3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,12 +7,21 @@ on: env: BRANCH_NAME: ${{ github.event.ref.BRANCH_NAME }} +permissions: {} jobs: get_config_values: uses: ./.github/workflows/get-repo-config.yml + permissions: + attestations: read + contents: read + packages: read quality_checks: needs: [get_config_values] uses: ./.github/workflows/quality-checks-devcontainer.yml + permissions: + contents: read + id-token: write + packages: read with: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} secrets: @@ -28,4 +37,3 @@ jobs: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} branch_name: main tag_format: ${{ needs.get_config_values.outputs.tag_format }} - secrets: inherit diff --git a/.github/workflows/sync_copilot.yml b/.github/workflows/sync_copilot.yml index f47c62b8..a010386d 100644 --- a/.github/workflows/sync_copilot.yml +++ b/.github/workflows/sync_copilot.yml @@ -5,6 +5,7 @@ on: schedule: - cron: "0 6 * * 1" +permissions: {} jobs: sync-copilot-instructions: runs-on: ubuntu-22.04 diff --git a/.github/workflows/tag-release-devcontainer.yml b/.github/workflows/tag-release-devcontainer.yml index 52fca83c..28823353 100644 --- a/.github/workflows/tag-release-devcontainer.yml +++ b/.github/workflows/tag-release-devcontainer.yml @@ -79,6 +79,8 @@ on: PYPI_TOKEN: required: false description: "PyPI token to publish packages" + +permissions: {} jobs: tag_release: permissions: @@ -114,6 +116,7 @@ jobs: with: repository: ${{ github.repository }} ref: ${{ github.sha }} + persist-credentials: ${{ ! inputs.dry_run }} # only persist credentials when not running in dry-run mode - name: Checkout semantic-release workflow uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd @@ -127,6 +130,7 @@ jobs: release.config.cjs releaseNotesTemplates/commit.hbs packages/ + persist-credentials: false - name: Install semantic release dependencies globally run: | cd common_workflow_config @@ -249,29 +253,6 @@ jobs: run: | npx semantic-release --tag-format "${TAG_FORMAT}" - - name: Get release for editing - if: ${{ !inputs.dry_run }} - id: get_release - # version 1.2.4 - uses: cardinalby/git-get-release-action@5172c3a026600b1d459b117738c605fabc9e4e44 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - tag: ${{ steps.output_version_tag.outputs.VERSION_TAG }} - - - name: Edit Release - if: ${{ !inputs.dry_run }} - # version 1.2.0 - uses: irongut/EditRelease@ccf529ad26dddf9996e7dd0f24ca5da4ea507cc2 - with: - token: ${{ secrets.GITHUB_TOKEN }} - id: ${{ steps.get_release.outputs.id }} - body: | - ## Info - [Release workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) - Workflow ID: ${{ github.run_id }} - - It was initialized by [${{ github.event.sender.login }}](${{ github.event.sender.html_url }}) - - name: Checkout gh-pages branch if: ${{ !inputs.dry_run }} uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd @@ -279,12 +260,12 @@ jobs: repository: ${{ github.repository }} ref: gh-pages path: gh-pages + persist-credentials: true # needed for push to gh-pages - name: Publish release notes to gh-pages if: ${{ !inputs.dry_run }} working-directory: gh-pages env: - RELEASE_ID: ${{ steps.get_release.outputs.id }} VERSION_TAG: ${{ steps.output_version_tag.outputs.VERSION_TAG }} GH_REPO: ${{ github.repository }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -294,8 +275,7 @@ jobs: notes_dir="release_notes" mkdir -p "$notes_dir" note_file="$notes_dir/${VERSION_TAG}.md" - - gh api "/repos/${GH_REPO}/releases/${RELEASE_ID}" | jq -r '.body // ""' > "$note_file" + cp ../electronic-prescription-service-api/CHANGELOG.md "$note_file" if [ ! -s "$note_file" ]; then echo "Release notes are empty; skipping gh-pages update." @@ -319,5 +299,7 @@ jobs: shell: bash run: | TIMESTAMP=$(date +%s) - VERSION=$(echo ${{ steps.output_version_tag.outputs.VERSION_TAG }} | tr . -) + VERSION=$(echo "${VERSION_TAG}" | tr . -) echo CHANGE_SET_VERSION="$VERSION-$TIMESTAMP" >> "$GITHUB_OUTPUT" + env: + VERSION_TAG: ${{ steps.output_version_tag.outputs.VERSION_TAG }} diff --git a/.github/workflows/update-dev-container-version.yml b/.github/workflows/update-dev-container-version.yml index 765cd09c..a9546b48 100644 --- a/.github/workflows/update-dev-container-version.yml +++ b/.github/workflows/update-dev-container-version.yml @@ -4,6 +4,7 @@ on: workflow_dispatch: schedule: - cron: "0 6 * * 4" + permissions: {} jobs: diff --git a/.grype.yaml b/.grype.yaml new file mode 100644 index 00000000..d59c9b25 --- /dev/null +++ b/.grype.yaml @@ -0,0 +1,6 @@ +ignore: + # undici + - vulnerability: GHSA-v9p9-hfj2-hcw8 + - vulnerability: GHSA-vrm6-8vpv-qv8q + # picomatch + - vulnerability: GHSA-c2c7-rcm5-vvqj diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2f7f8656..2e5156cf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,6 +17,14 @@ repos: files: ^(.github) - repo: local hooks: + - id: grype-scan-local + name: Grype scan local changes + entry: make + args: ["grype-scan-local"] + language: system + pass_filenames: false + always_run: true + - id: check-commit-signing name: Check commit signing description: Ensures that commits are GPG signed @@ -45,6 +53,7 @@ repos: - -c - "git-secrets --pre_commit_hook" language: system + - id: lint-githubactions name: Lint github actions entry: make diff --git a/.trivyignore.yaml b/.trivyignore.yaml deleted file mode 100644 index 5376a7b0..00000000 --- a/.trivyignore.yaml +++ /dev/null @@ -1,36 +0,0 @@ -vulnerabilities: - - id: CVE-2026-24842 - paths: - - "package-lock.json" - statement: downstream dependency for tar - waiting for new npm release - expired_at: 2026-06-01 - - id: CVE-2026-26996 - statement: minimatch vulnerability accepted as risk - expired_at: 2026-06-01 - - id: CVE-2026-27903 - statement: minimatch vulnerability accepted as risk - dependency of npm (multiple) - expired_at: 2026-06-01 - - id: CVE-2026-27904 - statement: minimatch vulnerability accepted as risk - dependency of npm (multiple) - expired_at: 2026-06-01 - - id: CVE-2026-26960 - statement: tar vulnerability accepted as risk - expired_at: 2026-06-01 - - id: GHSA-qffp-2rhf-9h96 - statement: tar vulnerability accepted as risk - dependency of npm (multiple) - expired_at: 2026-06-01 - - id: CVE-2026-29786 - statement: tar vulnerability accepted as risk - dependency of npm (multiple) - expired_at: 2026-06-01 - - id: CVE-2026-31802 - statement: tar vulnerability accepted as risk - dependency of npm (multiple) - expired_at: 2026-06-01 - - id: CVE-2026-1526 - statement: undici vulnerability accepted as risk - expired_at: 2026-06-01 - - id: CVE-2026-2229 - statement: undici vulnerability accepted as risk - expired_at: 2026-06-01 - - id: CVE-2026-33036 - statement: fast-xml-parser vulnerability accepted as risk - expired_at: 2026-06-01 diff --git a/Makefile b/Makefile index e4662905..4f55190e 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ install: install-node install-python install-hooks install-node: - npm ci + npm ci --ignore-scripts true install-python: poetry install diff --git a/README.md b/README.md index 4ece54c1..13270b95 100644 --- a/README.md +++ b/README.md @@ -6,86 +6,16 @@ The workflows that are available to use are ## Workflow Index -- [Combine Dependabot PRs](#combine-dependabot-prs) - [Dependabot Auto Approve and Merge](#dependabot-auto-approve-and-merge) -- [Sync copilot instructions](#sync-copilot-instructions) - [PR Title Check](#pr-title-check) - [Get Repo Config](#get-repo-config) -- [Quality Checks](#quality-checks) - [Quality Checks - Dev Container Version](#quality-checks---dev-container-version) -- [Update Dev Container Version](#update-dev-container-version) -- [Tag Release](#tag-release) - [Tag Release - Devcontainer Version](#tag-release---devcontainer-version) ## Other Docs -- [Adding Exclusions to Trivy Scanning](#adding-exclusions-to-trivy-scanning) -- [Secret Scanning Docker](#secret-scanning-docker) - [Run All Releases](#run-all-releases) -## Adding Exclusions to Trivy Scanning -The quality checks job uses Trivy to scan for vulnerabilities. -There may be times you want to add an exclusion for a known vulnerability that we are happy to accept -To do this, in the calling repo, add trivy.yaml with this content -``` -ignorefile: ".trivyignore.yaml" -``` -and add a .trivyignore.yaml with this content -``` -vulnerabilities: - - id: CVE-2026-24842 - paths: - - "package-lock.json" - statement: downstream dependency for tar - waiting for new npm release - expired_at: 2026-06-01 -``` -See https://trivy.dev/docs/latest/configuration/filtering/#trivyignoreyaml for more details - -## Combine Dependabot PRs - -This workflow can be called to combine multiple open Dependabot PRs into a single PR. - -#### Inputs - -- `branchPrefix`: Branch prefix to find combinable PRs based on. Default: `dependabot` -- `mustBeGreen`: Only combine PRs that are green (status is success). Default: `true` -- `combineBranchName`: Name of the branch to combine PRs into. Default: `combine-dependabot-PRs` -- `ignoreLabel`: Exclude PRs with this label. Default: `nocombine` - -#### Example - -```yaml -name: Combine Dependabot PRs - -on: - workflow_dispatch: - inputs: - branchPrefix: - description: "Branch prefix to find combinable PRs based on" - required: true - type: string - mustBeGreen: - description: "Only combine PRs that are green (status is success)" - required: true - type: boolean - combineBranchName: - description: "Name of the branch to combine PRs into" - required: true - type: string - ignoreLabel: - description: "Exclude PRs with this label" - required: true - type: string - -jobs: - combine-dependabot-prs: - uses: NHSDigital/eps-common-workflows/.github/workflows/combine-dependabot-prs.yml@f5c8313a10855d0cc911db6a9cd666494c00045a - with: - branchPrefix: ${{ github.event.inputs.branchPrefix }} - mustBeGreen: ${{ github.event.inputs.mustBeGreen }} - combineBranchName: ${{ github.event.inputs.combineBranchName }} - ignoreLabel: ${{ github.event.inputs.ignoreLabel }} -``` ## Dependabot Auto Approve and Merge This workflow can be called to automatically approve and merge Dependabot PRs as part of the pull request workflow. @@ -110,40 +40,6 @@ jobs: AUTOMERGE_APP_ID: ${{ secrets.AUTOMERGE_APP_ID }} AUTOMERGE_PEM: ${{ secrets.AUTOMERGE_PEM }} ``` -## Sync copilot instructions -This workflow syncs Copilot instructions from this repo into another repo and opens a PR with the changes. -It uses the environment secrets CREATE_PULL_REQUEST_APP_ID and CREATE_PULL_REQUEST_PEM that are defined in the create_pull_request environment in each repo - -#### Inputs - -- `common_workflows_ref`: Branch in common workflows repo to sync from. Default: `main` -- `calling_repo_base_branch`: The base branch in the calling repository. Default: `main`. - - - -#### Example - -```yaml -name: Sync Copilot Instructions - -on: - workflow_dispatch: - inputs: - common_workflows_ref: - description: "Branch to sync from" - required: false - type: string - default: main - -jobs: - sync-copilot: - uses: NHSDigital/eps-common-workflows/.github/workflows/sync_copilot.yml@f5c8313a10855d0cc911db6a9cd666494c00045a - with: - ref: ${{ github.event.inputs.common_workflows_ref }} - secrets: - CREATE_PULL_REQUEST_APP_ID: ${{ secrets.CREATE_PULL_REQUEST_APP_ID }} - CREATE_PULL_REQUEST_PEM: ${{ secrets.CREATE_PULL_REQUEST_PEM }} -``` ## PR Title Check This workflow checks that all pull requests have a title that matches the required format, and comments on the PR with a link to the relevant ticket if a ticket reference is found. @@ -198,53 +94,6 @@ jobs: uses: NHSDigital/eps-common-workflows/.github/workflows/get-repo-config.yml@f5c8313a10855d0cc911db6a9cd666494c00045a ``` -## Quality Checks -This workflow runs common quality checks. -To use this, you must have the following Makefile targets defined -- install -- lint -- test -- install-node (only for cdk projects) -- compile (only for cdk projects) -- cdk-synth (only for cdk projects) -- docker-build (only if run_docker_scan is set to true) - -#### Inputs - -- `install_java`: Whether to install Java or not -- `run_sonar`: Whether to run Sonar checks or not. -- `asdfVersion`: Override the version of asdf to install. -- `reinstall_poetry`: If you are using this from a primarily Python based project, you should set this to true to force a poetry reinstallation after Python is installed -- `run_docker_scan`: whether to run a scan of Docker images -- `docker_images`: csv list of Docker images to scan. These must match images produced by make docker-build - -#### Secret Inputs -- `SONAR_TOKEN`: Token used to authenticate to Sonar - -#### Outputs - -None - -#### Example - -To use this workflow in your repository, call it from another workflow file: - -```yaml -name: Release - -on: - workflow_dispatch: - -jobs: - quality_checks: - uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks.yml@f5c8313a10855d0cc911db6a9cd666494c00045a - needs: [get_asdf_version] - with: - asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} - secrets: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} -``` - ## Quality Checks - Dev Container Version This workflow runs common quality checks using a prebuilt devcontainer (https://github.com/NHSDigital/eps-devcontainers). To use this, you must have overridden any common makefile targets described in https://github.com/NHSDigital/eps-devcontainers?tab=readme-ov-file#common-makefile-targets @@ -286,90 +135,6 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} ``` -## Update Dev Container Version -This workflow updates `.devcontainer/devcontainer.json` with the latest published `v*` version for your configured devcontainer image from GHCR, then opens (or updates) a pull request with that change. - -#### Requirements - -- `.devcontainer/devcontainer.json` must include `build.args.IMAGE_NAME` and `build.args.IMAGE_VERSION`. -- `CREATE_PULL_REQUEST_APP_ID` and `CREATE_PULL_REQUEST_PEM` secrets must be configured so the workflow can create a GitHub App token for PR creation. - -#### Inputs - -- `base_branch`: Target branch for the pull request. Default: `main`. - -#### Secret Inputs - -- `CREATE_PULL_REQUEST_APP_ID`: GitHub App ID used to generate an installation token. -- `CREATE_PULL_REQUEST_PEM`: GitHub App private key used to generate an installation token. - -#### Outputs - -None - -#### Example - -To use this workflow in your repository, call it from another workflow file: - -```yaml -name: Update Devcontainer Version - -on: - workflow_dispatch: - -jobs: - update_devcontainer_version: - uses: NHSDigital/eps-common-workflows/.github/workflows/update-dev-container-version.yml@f5c8313a10855d0cc911db6a9cd666494c00045a - with: - base_branch: main - secrets: - CREATE_PULL_REQUEST_APP_ID: ${{ secrets.CREATE_PULL_REQUEST_APP_ID }} - CREATE_PULL_REQUEST_PEM: ${{ secrets.CREATE_PULL_REQUEST_PEM }} -``` - -## Tag Release -This workflow uses the semantic-release npm package to generate a new version tag, changelog, and GitHub release for a repo. - -#### Inputs - -- `dry_run`: Whether to run in dry_run mode (do not create tags) or not -- `tag_format`: Default `v\\${version}`. A template for the version tag. -- `branch_name`: The branch name to base the release on -- `publish_packages`: comma separated list of package folders to publish to an npm registry -- `asdfVersion`: Override the version of asdf to install. -- `main_branch`: The branch to use for publishing. Defaults to main -- `extra_artifact_name`: optional param to include an extra artifact in the release -- `extra_artifact_id`: optional param of the extra artifact id to include in the release -- `extra_artifact_run_id`: optional param of the run id to download the extra artifact id to include in the release -- `extra_artifact_repository`: optional param to indicate which repo the run to download the artifact was from - -#### Outputs - -- `version_tag`: The version tag created by semantic-release. -- `change_set_version`: A timestamped string that can be used for creating changesets. -- `next_version_tag`: The next version tag that will be created. - -#### Example - -To use this workflow in your repository, call it from another workflow file: - -```yaml -name: Release - -on: - workflow_dispatch: - -jobs: - tag_release: - uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release.yml@f5c8313a10855d0cc911db6a9cd666494c00045a - with: - tag_format: "v\\${version}-beta" - dry_run: true - asdfVersion: 0.18.0 - branch_name: main - publish_packages: "" -``` - ## Tag Release - Devcontainer Version This workflow uses the semantic-release npm package to generate a new version tag, changelog, and GitHub release for a repo. *The devcontainer MUST have Node installed* @@ -418,44 +183,6 @@ jobs: ``` -## Secret Scanning Docker - -The secret scanning also has a Dockerfile, which can be run against a repo in order to scan it manually (or as part of pre-commit hooks). This can be done like so: -```bash -docker build -f https://raw.githubusercontent.com/NHSDigital/eps-workflow-quality-checks/refs/tags/v3.0.0/dockerfiles/nhsd-git-secrets.dockerfile -t git-secrets . -docker run -v /path/to/repo:/src git-secrets --scan-history . -``` -For usage of the script, see the [source repo](https://github.com/NHSDigital/software-engineering-quality-framework/blob/main/tools/nhsd-git-secrets/git-secrets). Generally, you will either need `--scan -r .` or `--scan-history .`. The arguments default to `--scan -r .`, i.e. scanning the current state of the code. - -In order to enable the pre-commit hook for secret scanning (to prevent developers from committing secrets in the first place), add the following to the `.devcontainer/devcontainer.json` file: -```json -{ - "remoteEnv": { "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" }, - "postAttachCommand": "docker build -f https://raw.githubusercontent.com/NHSDigital/eps-workflow-quality-checks/refs/tags/v4.0.2/dockerfiles/nhsd-git-secrets.dockerfile -t git-secrets . && pre-commit install --install-hooks -f", - "features": { - "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { - "version": "latest", - "moby": "true", - "installDockerBuildx": "true" - } - } -} -``` - -And add this pre-commit hook to the `.pre-commit-config.yaml` file: -```yaml -repos: -- repo: local - hooks: - - id: git-secrets - name: Git Secrets - description: git-secrets scans commits, commit messages, and --no-ff merges to prevent adding secrets into your git repositories. - entry: bash - args: - - -c - - 'docker run -v "$LOCAL_WORKSPACE_FOLDER:/src" git-secrets --pre_commit_hook' - language: system -``` ## Run All Releases diff --git a/combine-prs.js b/combine-prs.js deleted file mode 100644 index 70e5c5c9..00000000 --- a/combine-prs.js +++ /dev/null @@ -1,127 +0,0 @@ -module.exports = async ({ github, context, core }) => { - const branchPrefix = process.env.branchPrefix; - const mustBeGreen = Boolean(process.env.mustBeGreen); - const combineBranchName = process.env.combineBranchName; - const ignoreLabel = process.env.ignoreLabel; - - const pulls = await github.paginate('GET /repos/:owner/:repo/pulls', { - owner: context.repo.owner, - repo: context.repo.repo - }); - let branchesAndPRStrings = []; - let baseBranch = null; - let baseBranchSHA = null; - - for (const pull of pulls) { - const branch = pull['head']['ref']; - console.log('Pull for branch: ' + branch); - - if (branch.startsWith(branchPrefix)) { - console.log('Branch matched prefix: ' + branch); - let statusOK = true; - if (mustBeGreen) { - console.log('Checking green status: ' + branch); - const stateQuery = `query($owner: String!, $repo: String!, $pull_number: Int!) { - repository(owner: $owner, name: $repo) { - pullRequest(number:$pull_number) { - commits(last: 1) { - nodes { - commit { - statusCheckRollup { - state - } - } - } - } - } - } - }` - const vars = { - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: pull['number'] - }; - const result = await github.graphql(stateQuery, vars); - const [{ commit }] = result.repository.pullRequest.commits.nodes; - const state = commit.statusCheckRollup.state - console.log('Validating status: ' + state); - if (state != 'SUCCESS') { - console.log('Discarding ' + branch + ' with status ' + state); - statusOK = false; - } - } - - console.log('Checking labels: ' + branch); - const labels = pull['labels']; - for (const label of labels) { - const labelName = label['name']; - console.log('Checking label: ' + labelName); - if (labelName == ignoreLabel) { - console.log('Discarding ' + branch + ' with label ' + labelName); - statusOK = false; - } - } - - if (statusOK) { - console.log('Adding branch to array: ' + branch); - const prString = '#' + pull['number'] + ' ' + pull['title']; - branchesAndPRStrings.push({ branch, prString }); - baseBranch = pull['base']['ref']; - baseBranchSHA = pull['base']['sha']; - } - } - } - - if (branchesAndPRStrings.length == 0) { - core.setFailed('No PRs/branches matched criteria'); - return; - } - - try { - await github.rest.git.createRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: 'refs/heads/' + combineBranchName, - sha: baseBranchSHA - }); - } catch (error) { - console.log(error); - core.setFailed('Failed to create combined branch - maybe a branch by that name already exists?'); - return; - } - - let combinedPRs = []; - let mergeFailedPRs = []; - for (const { branch, prString } of branchesAndPRStrings) { - try { - await github.rest.repos.merge({ - owner: context.repo.owner, - repo: context.repo.repo, - base: combineBranchName, - head: branch, - }); - console.log('Merged branch ' + branch); - combinedPRs.push(prString); - } catch (error) { - console.log('Failed to merge branch ' + branch); - mergeFailedPRs.push(prString); - } - } - - console.log('Creating combined PR'); - const combinedPRsString = combinedPRs.join('\n'); - let body = '✅ This PR was created by the Combine PRs action by combining the following PRs:\n' + combinedPRsString; - if (mergeFailedPRs.length > 0) { - const mergeFailedPRsString = mergeFailedPRs.join('\n'); - body += '\n\n⚠️ The following PRs were left out due to merge conflicts:\n' + mergeFailedPRsString - } - - await github.rest.pulls.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: 'Combined PR', - head: combineBranchName, - base: baseBranch, - body: body - }); -} diff --git a/trivy.yaml b/trivy.yaml deleted file mode 100644 index eb243375..00000000 --- a/trivy.yaml +++ /dev/null @@ -1 +0,0 @@ -ignorefile: ".trivyignore.yaml" diff --git a/zizmor.yml b/zizmor.yml new file mode 100644 index 00000000..e427669e --- /dev/null +++ b/zizmor.yml @@ -0,0 +1,17 @@ +rules: + secrets-outside-env: + ignore: + # these workflows use secrets outside of an environment because it is passed into the workflow + - tag-release-devcontainer.yml + - update-dev-container-version.yml + - quality-checks-devcontainer.yml + - dependabot-auto-approve-and-merge.yml + unpinned-images: + # these workflows use unpinned images because they are using a full image passed in that contains the tag + ignore: + - quality-checks-devcontainer.yml + - tag-release-devcontainer.yml + artipacked: + ignore: + # this is ignored as its based on using an input to the workflow + - tag-release-devcontainer.yml:114:15