diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2232f7e..bd590a8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -22,9 +22,6 @@ "--network=host" ], "remoteEnv": { "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" }, - "postCreateCommand": "bash ${SCRIPTS_DIR}/post_create.sh", - "postStartCommand": "bash ${SCRIPTS_DIR}/post_start.sh", - "postAttachCommand": "bash ${SCRIPTS_DIR}/post_attach.sh", "features": { }, "customizations": { diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4a87436..5d07868 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,7 +16,8 @@ updates: open-pull-requests-limit: 20 commit-message: prefix: "Upgrade: [dependabot] - " - + cooldown: + default-days: 7 ################################### # NPM workspace ################## ################################### @@ -30,6 +31,8 @@ updates: versioning-strategy: increase commit-message: prefix: "Upgrade: [dependabot] - " + cooldown: + default-days: 7 ################################### # Poetry ######################### @@ -44,3 +47,5 @@ updates: versioning-strategy: increase commit-message: prefix: "Upgrade: [dependabot] - " + cooldown: + default-days: 7 diff --git a/.github/workflows/build_all_images.yml b/.github/workflows/build_all_images.yml index fe3279a..8887abd 100644 --- a/.github/workflows/build_all_images.yml +++ b/.github/workflows/build_all_images.yml @@ -11,8 +11,11 @@ name: build_all_images NO_CACHE: required: true type: boolean -env: - BRANCH_NAME: '${{ github.event.pull_request.head.ref }}' +permissions: + attestations: write + contents: read + packages: write + id-token: write jobs: discover_folders: runs-on: ubuntu-latest @@ -22,6 +25,8 @@ jobs: project_folders: ${{ steps.find-folders.outputs.projects }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false - id: find-folders run: | diff --git a/.github/workflows/build_multi_arch_image.yml b/.github/workflows/build_multi_arch_image.yml index 269408f..4786826 100644 --- a/.github/workflows/build_multi_arch_image.yml +++ b/.github/workflows/build_multi_arch_image.yml @@ -20,6 +20,7 @@ name: Build and push docker image EXTRA_COMMON: required: false type: string +permissions: {} jobs: build_and_push_image: @@ -63,11 +64,17 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: fetch-depth: 0 + persist-credentials: false - name: setup node uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: node-version: '24.14.0' - + - name: setup syft and grype + run: | + mkdir -p "$RUNNER_TEMP/bin" + docker build --output="$RUNNER_TEMP/bin" -f "src/base/.devcontainer/Dockerfile.syft" src/base/.devcontainer/ + docker build --output="$RUNNER_TEMP/bin" -f "src/base/.devcontainer/Dockerfile.grype" src/base/.devcontainer/ + echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH" - name: make install run: | make install-node @@ -92,32 +99,18 @@ jobs: CONTAINER_NAME: '${{ inputs.container_name }}' BASE_FOLDER: "${{ inputs.base_folder }}" IMAGE_TAG: "${{ inputs.docker_tag }}-${{ matrix.arch }}" - EXIT_CODE: 0 - EXTRA_COMMON: "${{ inputs.extra_common }}" - # - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f - # name: Upload scan results - # with: - # name: "scan_results_docker_${{ inputs.container_name }}_${{ matrix.arch }}.json" - # path: .out/scan_results_docker.json - # - name: Check docker vulnerabilities - table output - # run: | - # make scan-image - # env: - # CONTAINER_NAME: '${{ inputs.container_name }}' - # BASE_FOLDER: "${{ inputs.base_folder }}" - # IMAGE_TAG: "${{ inputs.docker_tag }}-${{ matrix.arch }}" - # EXIT_CODE: "1" - # EXTRA_COMMON: "${{ inputs.extra_common }}" - # - name: Show docker vulnerability output - # if: always() - # run: | - # echo "Scan output for ghcr.io/nhsdigital/eps-devcontainers/base:${DOCKER_TAG}-${ARCHITECTURE}" - # if [ -f .out/scan_results_docker.txt ]; then - # cat .out/scan_results_docker.txt - # fi - # env: - # ARCHITECTURE: '${{ matrix.arch }}' - # DOCKER_TAG: '${{ inputs.docker_tag }}' + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f + name: Upload scan results + with: + name: "grype_${{ inputs.container_name }}_${{ inputs.docker_tag }}-${{ matrix.arch }}.json" + path: .grype_out/grype_${{ inputs.container_name }}_${{ inputs.docker_tag }}-${{ matrix.arch }}.json + - name: Check docker vulnerabilities - text output + run: | + make scan-image + env: + CONTAINER_NAME: '${{ inputs.container_name }}' + BASE_FOLDER: "${{ inputs.base_folder }}" + IMAGE_TAG: "${{ inputs.docker_tag }}-${{ matrix.arch }}" - name: Push tagged image and rebuild for github actions run: | echo "Pushing image..." diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eaa03c1..4fb3b03 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,12 +2,17 @@ name: merge to main workflow on: push: branches: [main] +permissions: {} jobs: get_config_values: uses: NHSDigital/eps-common-workflows/.github/workflows/get-repo-config.yml@f2d4d6942115472d3f08316cd25f400b02a9dc69 with: verify_published_from_main_image: true + permissions: + attestations: read + contents: read + packages: read quality_checks: uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks-devcontainer.yml@f2d4d6942115472d3f08316cd25f400b02a9dc69 needs: @@ -27,11 +32,15 @@ jobs: pinned_image: ${{ needs.get_config_values.outputs.pinned_image }} branch_name: main tag_format: ${{ needs.get_config_values.outputs.tag_format }} - secrets: inherit build_all_images: needs: - tag_release uses: ./.github/workflows/build_all_images.yml + permissions: + attestations: write + contents: read + packages: write + id-token: write with: docker_tag: 'ci-${{ needs.tag_release.outputs.version_tag }}' tag_latest: false diff --git a/.github/workflows/delete_old_images.yml b/.github/workflows/delete_old_images.yml index 96438b5..9e72b3f 100644 --- a/.github/workflows/delete_old_images.yml +++ b/.github/workflows/delete_old_images.yml @@ -7,6 +7,7 @@ on: - cron: "0 1 * * 6" push: branches: [main] +permissions: {} jobs: delete-old-pushed-images: @@ -21,8 +22,8 @@ jobs: - name: Checkout local code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: - ref: ${{ env.BRANCH_NAME }} fetch-depth: 0 + persist-credentials: false - name: delete unused images shell: bash diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index e98ebb5..8824ae5 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -3,13 +3,14 @@ name: pull_request pull_request: branches: - main -env: - BRANCH_NAME: '${{ github.event.pull_request.head.ref }}' +permissions: {} jobs: dependabot-auto-approve-and-merge: needs: quality_checks - uses: >- - NHSDigital/eps-common-workflows/.github/workflows/dependabot-auto-approve-and-merge.yml@f2d4d6942115472d3f08316cd25f400b02a9dc69 + uses: NHSDigital/eps-common-workflows/.github/workflows/dependabot-auto-approve-and-merge.yml@f2d4d6942115472d3f08316cd25f400b02a9dc69 + permissions: + contents: write + pull-requests: write secrets: AUTOMERGE_APP_ID: '${{ secrets.AUTOMERGE_APP_ID }}' AUTOMERGE_PEM: '${{ secrets.AUTOMERGE_PEM }}' @@ -17,6 +18,10 @@ jobs: uses: NHSDigital/eps-common-workflows/.github/workflows/get-repo-config.yml@f2d4d6942115472d3f08316cd25f400b02a9dc69 with: verify_published_from_main_image: false + permissions: + attestations: read + contents: read + packages: read quality_checks: uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks-devcontainer.yml@f2d4d6942115472d3f08316cd25f400b02a9dc69 needs: @@ -26,8 +31,9 @@ jobs: secrets: SONAR_TOKEN: '${{ secrets.SONAR_TOKEN }}' pr_title_format_check: - uses: >- - NHSDigital/eps-common-workflows/.github/workflows/pr_title_check.yml@f2d4d6942115472d3f08316cd25f400b02a9dc69 + uses: NHSDigital/eps-common-workflows/.github/workflows/pr_title_check.yml@f2d4d6942115472d3f08316cd25f400b02a9dc69 + permissions: + pull-requests: write get_issue_number: runs-on: ubuntu-22.04 needs: quality_checks @@ -63,7 +69,7 @@ jobs: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: - ref: '${{ env.BRANCH_NAME }}' + persist-credentials: false - name: Get Commit ID id: commit_id run: | @@ -75,6 +81,11 @@ jobs: - get_issue_number - get_commit_id uses: ./.github/workflows/build_all_images.yml + permissions: + attestations: write + contents: read + packages: write + id-token: write with: docker_tag: 'pr-${{ needs.get_issue_number.outputs.issue_number }}-${{ needs.get_commit_id.outputs.sha_short }}' tag_latest: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d74f451..32be7f4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,12 +3,17 @@ on: workflow_dispatch: schedule: - cron: "0 18 * * 3" +permissions: {} jobs: get_config_values: uses: NHSDigital/eps-common-workflows/.github/workflows/get-repo-config.yml@f2d4d6942115472d3f08316cd25f400b02a9dc69 with: verify_published_from_main_image: false + permissions: + attestations: read + contents: read + packages: read quality_checks: uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks-devcontainer.yml@f2d4d6942115472d3f08316cd25f400b02a9dc69 needs: @@ -34,6 +39,11 @@ jobs: needs: - tag_release uses: ./.github/workflows/build_all_images.yml + permissions: + attestations: write + contents: read + packages: write + id-token: write with: docker_tag: '${{ needs.tag_release.outputs.version_tag }}' tag_latest: true diff --git a/.gitignore b/.gitignore index e8b9796..28c4032 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ src/base/.devcontainer/language_versions/ .trivyignore_combined.yaml .out/ .envrc +.sbom/ .grype_out/ diff --git a/.grype.yaml b/.grype.yaml new file mode 100644 index 0000000..3d706b7 --- /dev/null +++ b/.grype.yaml @@ -0,0 +1,65 @@ +ignore: + # base image + - vulnerability: CVE-2025-4517 + - vulnerability: CVE-2025-68121 + - vulnerability: GHSA-p77j-4mvh-x3m3 + - vulnerability: GHSA-vmwr-mc7x-5vc3 + - vulnerability: CVE-2025-4330 + - vulnerability: CVE-2025-4435 + - vulnerability: CVE-2025-4138 + - vulnerability: CVE-2025-8194 + - vulnerability: CVE-2025-13836 + - vulnerability: CVE-2024-9287 + - vulnerability: CVE-2025-61726 + - vulnerability: CVE-2026-4519 + - vulnerability: CVE-2026-25679 + - vulnerability: CVE-2025-61725 + - vulnerability: CVE-2025-61723 + - vulnerability: CVE-2025-61729 + - vulnerability: GHSA-4vrq-3vrq-g6gg + - vulnerability: CVE-2025-58187 + - vulnerability: CVE-2026-27137 + - vulnerability: CVE-2025-47907 + - vulnerability: CVE-2025-61731 + - vulnerability: GHSA-9h8m-3fm2-qjrq + - vulnerability: CVE-2025-61732 + - vulnerability: GHSA-4c29-8rgm-jvjj + - vulnerability: CVE-2025-58188 + - vulnerability: CVE-2025-4674 + - vulnerability: GHSA-x744-4wpc-v9h2 +# node_24 vulnerabilities + - vulnerability: GHSA-c2c7-rcm5-vvqj + - vulnerability: GHSA-7r86-cg39-jmmj + - vulnerability: GHSA-3ppc-4f35-3m26 + - vulnerability: GHSA-23c5-xmqv-rm74 + - vulnerability: GHSA-9ppj-qmqm-q256 + - vulnerability: GHSA-qffp-2rhf-9h96 + - vulnerability: GHSA-83g3-92jg-28cx +# node_24_python_3_10 vulnerabilities + - vulnerability: GHSA-cx63-2mw6-8hw5 + - vulnerability: GHSA-r9hx-vwmv-q579 + - vulnerability: GHSA-5rjg-fvgr-3xxf +# eps-storage-terraform vulnerabilities + - vulnerability: CVE-2025-68119 +# eps-data-extract vulnerabilities + - vulnerability: GHSA-6fmv-xxpf-w3cw +# fhir-facade vulnerabilities + - vulnerability: CVE-2022-26485 + - vulnerability: CVE-2022-26486 + - vulnerability: CVE-2022-25235 + - vulnerability: CVE-2022-25236 + - vulnerability: CVE-2024-21147 + - vulnerability: CVE-2025-21587 + - vulnerability: CVE-2025-30749 + - vulnerability: CVE-2024-20952 + - vulnerability: CVE-2024-20918 + - vulnerability: CVE-2025-50106 + - vulnerability: CVE-2025-50059 + - vulnerability: CVE-2025-53066 + - vulnerability: CVE-2026-21945 + - vulnerability: CVE-2026-21932 +# node-24_python_3_14_java_24 vulnerabilities + - vulnerability: GHSA-6fmv-xxpf-w3cw + - vulnerability: CVE-2025-53066 + - vulnerability: CVE-2026-21945 + - vulnerability: CVE-2026-21932 diff --git a/Makefile b/Makefile index 177d709..ea03c3c 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,15 @@ build-all: build-base-image build-node-24-image build-node-24-python-3-10-image build-eps-storage-terraform-image build-eps-data-extract-image build-fhir-facade-image build-node-24-python-3-14-golang-1-24-image build-node-24-python-3-14-java-24-image \ build-regression-tests-image -build-image: guard-CONTAINER_NAME guard-BASE_VERSION_TAG guard-BASE_FOLDER guard-IMAGE_TAG +build-syft: + docker build -f src/base/.devcontainer/Dockerfile.syft --tag local_syft src/base/.devcontainer/ +build-grype: + docker build -f src/base/.devcontainer/Dockerfile.grype --tag local_grype src/base/.devcontainer/ + +build-grant: + docker build -f src/base/.devcontainer/Dockerfile.grant --tag local_grant src/base/.devcontainer/ + +build-image: build-syft build-grype build-grant guard-CONTAINER_NAME guard-BASE_VERSION_TAG guard-BASE_FOLDER guard-IMAGE_TAG workspace_folder="$${CONTAINER_NAME}"; \ case "$${CONTAINER_NAME}" in \ eps_*) workspace_folder="$$(printf '%s' "$${CONTAINER_NAME}" | tr '_' '-')" ;; \ @@ -86,12 +94,17 @@ build-githubactions-image: guard-BASE_IMAGE_NAME guard-BASE_IMAGE_TAG guard-IMAG --load \ -t "${CONTAINER_PREFIX}$${BASE_IMAGE_NAME}:githubactions-$${IMAGE_TAG}" \ . - -scan-image: guard-CONTAINER_NAME guard-BASE_FOLDER - echo "Not implemented" +scan-image: guard-CONTAINER_NAME guard-BASE_FOLDER guard-IMAGE_TAG + grype "${CONTAINER_PREFIX}$${CONTAINER_NAME}:$${IMAGE_TAG}" \ + --scope all-layers \ + --sort-by severity \ + --fail-on high scan-image-json: guard-CONTAINER_NAME guard-BASE_FOLDER guard-IMAGE_TAG - echo "Not implemented" + grype "${CONTAINER_PREFIX}$${CONTAINER_NAME}:$${IMAGE_TAG}" \ + --scope all-layers \ + --output json \ + --file ".grype_out/grype_${CONTAINER_NAME}_${IMAGE_TAG}.json" shell-image: guard-CONTAINER_NAME guard-IMAGE_TAG docker run -it \ diff --git a/README.md b/README.md index 1f6183e..8dd2bae 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ EPS DEV CONTAINERS - [Using local or pull request images in Visual Studio Code and GitHub Actions](#using-local-or-pull-request-images-in-visual-studio-code-and-github-actions) - [Common Makefile targets](#common-makefile-targets) - [Defined Targets](#targets) +- [Anchore tools](#anchore-tools-syft-grype-grant) - [Project structure](#project-structure) - [Pull requests and merge to main process](#pull-requests-and-merge-to-main-process) @@ -177,7 +178,19 @@ Check targets (`check.mk`) - `actionlint` - runs actionlint against GitHub Actions - `secret-scan` - runs git-secrets (including scanning history) against the repository - `guard-` - checks if an environment variable is set and errors if it is not -- `zizmor` runs [zizmor](https://github.com/zizmorcore/zizmor) in the local directory to check github workflows and actions +- `zizmor` - runs [zizmor](https://github.com/zizmorcore/zizmor) in the local directory to check github workflows and actions +- `syft-generate-sbom` - uses syft to generate an sbom in cyclonedx-json format. This *does not* include dev dependencies. Outputs file to .sbom/sbom.cdx.json. +- `syft-generate-sbom-dev-dependencies` - uses syft to generate an sbom in cyclonedx-json format. This *DOES* include dev dependencies. Outputs file to .sbom/sbom.dev.cdx.json. +- `grype-scan` - Uses grype to scan for vulnerabilities. Uses an sbom generated by `syft-generate-sbom` target to find dependencies. +- `grype-scan-dev-dependencies` - Uses grype to scan for vulnerabilities. Uses an sbom generated by `syft-generate-sbom-dev-dependencies` target to find dependencies. +- `grype-scan-json` - Uses grype to scan for vulnerabilities. Uses an sbom generated by `syft-generate-sbom` target to find dependencies. Outputs file to .sbom/grype_analysis.json +- `grype-scan-json-dev-dependencies` - Uses grype to scan for vulnerabilities. Uses an sbom generated by `syft-generate-sbom-dev-dependencies` target to find dependencies. Outputs file to .sbom/grype_analysis.dev.json +- `grype-scan-local` - Uses grype to scan local folders for vulnerabilities. This is installed as a pre-commit hook in each project. +- `grype-scan-docker-image` - Uses grype to scan a docker image for vulnerabilities. This image to scan must be set in the environment variable DOCKER_IMAGE +- `grant-scan` - Uses grant to scan for possible incompatible licenses. Uses an sbom generated by `syft-generate-sbom` target to find dependencies. +- `grant-scan-dev-dependencies` - Uses grant to scan for possible incompatible licenses. Uses an sbom generated by `syft-generate-sbom-dev-dependencies` target to find dependencies. +- `grant-scan-json` - Uses grant to scan for possible incompatible licenses. Uses an sbom generated by `syft-generate-sbom` target to find dependencies. Outputs file to .sbom/grant_analysis.json +- `grant-scan-json-dev-dependencies` - Uses grant to scan for possible incompatible licenses. Uses an sbom generated by `syft-generate-sbom-dev-dependencies` target to find dependencies. Outputs file to .sbom/grant_analysis.dev.json Credentials targets (`credentials.mk`) - `aws-configure` - configures an AWS SSO session @@ -195,6 +208,28 @@ These are all changed to not run anything and will be removed in a future releas - `trivy-scan-java` - `trivy-scan-docker` +# Anchore tools (syft, grype, grant) +We use tools from [anchore](https://oss.anchore.com/docs/projects/) for various analysis. The tools we use are +- syft to generate SBOM +- grype to scan for vulnerabilities +- grant to check for incompatible licenses + +## syft +This is used to generate SBOM (software bill of materials) for dependencies. +There are makefile targets defined that run with most common settings we need. There should be no need to modify any configuration files for use + +## grype +This scans for known vulnerabilities. +There are several makefile targets defined that can be used to run this - either outputting to console, or to a json file. It can also run for just runtime dependencies or include dev dependencies. +You may need to create a `.grype.yaml` with known accepted vulnerabilities in a project while we are waiting for downstream dependencies to update. Details of how to do this are documented at https://oss.anchore.com/docs/guides/vulnerability/filter-results/#ignore-specific-vulnerabilities-or-packages + +## grant +This scans for incompatible licenses in dependencies. +There are several makefile targets defined that can be used to run this - either outputting to console, or to a json file. It can also run for just runtime dependencies or include dev dependencies. +There is a default .grant.yaml file placed in home directories of devcontainers that lists acceptable licenses and known packages where the scanner incorrectly identified a license for a dependency. +If you need to modify this for a specific project, you must copy this to the root folder of the project and then modify it - eg `cp $HOME/.grant.yaml .`. +See https://oss.anchore.com/docs/guides/license/policies/ for details of what to put in the file. + # Project structure We have 5 types of dev container. These are defined under src diff --git a/package-lock.json b/package-lock.json index e8e1a35..b8a8515 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,10 @@ "license": "ISC", "dependencies": { "@devcontainers/cli": "^0.84.1" + }, + "devDependencies": { + "@types/node": "^25.5.0", + "tsx": "^4.21.0" } }, "node_modules/@devcontainers/cli": { @@ -23,6 +27,565 @@ "engines": { "node": ">=20.0.0" } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" } } } diff --git a/package.json b/package.json index 43f5056..48d4dd8 100644 --- a/package.json +++ b/package.json @@ -10,5 +10,9 @@ "description": "", "dependencies": { "@devcontainers/cli": "^0.84.1" + }, + "devDependencies": { + "@types/node": "^25.5.0", + "tsx": "^4.21.0" } } diff --git a/scripts/parse_grype_output.ts b/scripts/parse_grype_output.ts new file mode 100644 index 0000000..4d0ab12 --- /dev/null +++ b/scripts/parse_grype_output.ts @@ -0,0 +1,103 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +type JsonValue = + | string + | number + | boolean + | null + | JsonValue[] + | { [key: string]: JsonValue }; + +type JsonObject = { [key: string]: JsonValue }; + +const CRITICAL_SEVERITIES = new Set(['CRITICAL', 'HIGH']); + +const [, , filePath] = process.argv; + +if (!filePath) { + console.error('Usage: tsx scripts/parse_grype_output.ts '); + process.exit(1); +} + +const resolvedPath = resolve(filePath); + +function loadJsonFile(pathToFile: string): JsonValue { + const contents = readFileSync(pathToFile, 'utf8'); + return JSON.parse(contents) as JsonValue; +} + +let parsedJson: JsonValue; + +try { + parsedJson = loadJsonFile(resolvedPath); +} catch (error) { + console.error(`Failed to read or parse JSON file at ${resolvedPath}`); + console.error(error); + process.exit(1); +} + +function isJsonObject(value: JsonValue | undefined): value is JsonObject { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function asString(value: JsonValue | undefined): string | undefined { + return typeof value === 'string' ? value : undefined; +} + +if (!isJsonObject(parsedJson)) { + console.error('Unexpected JSON structure: root element is not an object.'); + process.exit(1); +} + +const matchesValue = parsedJson.matches; + +if (!Array.isArray(matchesValue)) { + console.error('Unexpected JSON structure: "matches" is missing or not an array.'); + process.exit(1); +} + +let matchCount = 0; + +for (const entry of matchesValue) { + if (!isJsonObject(entry)) { + continue; + } + + const vulnerability = isJsonObject(entry.vulnerability) ? entry.vulnerability : undefined; + const severity = asString(vulnerability?.severity)?.toUpperCase(); + + if (!severity || !CRITICAL_SEVERITIES.has(severity)) { + continue; + } + + matchCount += 1; + const vulnerabilityId = asString(vulnerability?.id) ?? 'Unknown ID'; + const vulnerabilityDescription = + asString(vulnerability?.description) ?? 'No description provided.'; + + const artifact = isJsonObject(entry.artifact) ? entry.artifact : undefined; + const locationsValue = artifact?.locations; + + const resolvedPaths = Array.isArray(locationsValue) + ? locationsValue + .map((location) => + isJsonObject(location) ? asString(location.path) ?? '(missing path)' : undefined, + ) + .filter((path): path is string => typeof path === 'string') + : []; + + const pathsToPrint = resolvedPaths.length > 0 ? resolvedPaths : ['(no locations provided)']; + + console.log('---'); + console.log(`vulnerability.id: ${vulnerabilityId}`); + console.log(`vulnerability.description: ${vulnerabilityDescription}`); + console.log('artifact.locations[*].path:'); + for (const path of pathsToPrint) { + console.log(` - ${path}`); + } +} + +if (matchCount === 0) { + console.log('No Critical or High severity matches found.'); +} diff --git a/src/base/.devcontainer/.grant.yaml b/src/base/.devcontainer/.grant.yaml new file mode 100644 index 0000000..f7c01ca --- /dev/null +++ b/src/base/.devcontainer/.grant.yaml @@ -0,0 +1,18 @@ +allow: + - MIT* + - Apache-* + - BSD-*-Clause + - BSD-3-Clause + - ISC* + - 0BSD + - Unlicense + - CC0-* + - BlueOak-* + - BSD + - MPL-* + - CC-BY-* + - Python-* + - Artistic-* +ignore-packages: + - "case" +require-license: false diff --git a/src/base/.devcontainer/Dockerfile b/src/base/.devcontainer/Dockerfile index 36691df..65931f1 100644 --- a/src/base/.devcontainer/Dockerfile +++ b/src/base/.devcontainer/Dockerfile @@ -1,3 +1,6 @@ +FROM local_syft AS syft-build +FROM local_grype AS grype-build +FROM local_grant AS grant-build FROM mcr.microsoft.com/devcontainers/base:ubuntu-22.04 ARG SCRIPTS_DIR=/usr/local/share/eps @@ -17,10 +20,15 @@ COPY --chmod=755 Mk ${SCRIPTS_DIR}/Mk WORKDIR ${SCRIPTS_DIR}/${CONTAINER_NAME} RUN ./root_install.sh +COPY --from=syft-build /syft /usr/local/bin/syft +COPY --from=grype-build /grype /usr/local/bin/grype +COPY --from=grant-build /grant /usr/local/bin/grant + COPY --chmod=755 scripts/vscode_install.sh ${SCRIPTS_DIR}/${CONTAINER_NAME}/vscode_install.sh USER vscode COPY --chown=vscode:vscode .tool-versions.asdf /home/vscode/.tool-versions.asdf COPY --chown=vscode:vscode .tool-versions /home/vscode/.tool-versions +COPY --chown=vscode:vscode .grant.yaml /home/vscode/.grant.yaml ENV PATH="/home/vscode/.asdf/shims:/home/vscode/.guard/bin:$PATH" WORKDIR ${SCRIPTS_DIR}/${CONTAINER_NAME} diff --git a/src/base/.devcontainer/Dockerfile.grant b/src/base/.devcontainer/Dockerfile.grant new file mode 100644 index 0000000..382491e --- /dev/null +++ b/src/base/.devcontainer/Dockerfile.grant @@ -0,0 +1,16 @@ +FROM alpine:3.23.3 AS build +ARG TARGETARCH +ARG GRANT_VERSION="0.6.4" +ENV GRANT_VERSION=${GRANT_VERSION} +RUN apk add --no-cache cosign bash curl jq +COPY --chmod=755 scripts/install_anchore_tool.sh /tmp/install_anchore_tool.sh +RUN case "${TARGETARCH}" in \ + x86_64|amd64) ANCHORE_ARCH=amd64 ;; \ + aarch64|arm64) ANCHORE_ARCH=arm64 ;; \ + *) echo "Unsupported TARGETARCH: ${TARGETARCH}" && exit 1 ;; \ + esac \ + && INSTALL_DIR=/tmp/anchore/ TOOL=grant ARCH="${ANCHORE_ARCH}" VERSION="${GRANT_VERSION}" /tmp/install_anchore_tool.sh + +FROM scratch +COPY --from=build /tmp/anchore/grant /grant +ENTRYPOINT ["/grant"] diff --git a/src/base/.devcontainer/Dockerfile.grype b/src/base/.devcontainer/Dockerfile.grype new file mode 100644 index 0000000..a502379 --- /dev/null +++ b/src/base/.devcontainer/Dockerfile.grype @@ -0,0 +1,16 @@ +FROM alpine:3.23.3 AS build +ARG TARGETARCH +ARG GRYPE_VERSION="0.110.0" +ENV GRYPE_VERSION=${GRYPE_VERSION} +RUN apk add --no-cache cosign bash curl jq +COPY --chmod=755 scripts/install_anchore_tool.sh /tmp/install_anchore_tool.sh +RUN case "${TARGETARCH}" in \ + x86_64|amd64) ANCHORE_ARCH=amd64 ;; \ + aarch64|arm64) ANCHORE_ARCH=arm64 ;; \ + *) echo "Unsupported TARGETARCH: ${TARGETARCH}" && exit 1 ;; \ + esac \ + && INSTALL_DIR=/tmp/anchore/ TOOL=grype ARCH="${ANCHORE_ARCH}" VERSION="${GRYPE_VERSION}" /tmp/install_anchore_tool.sh + +FROM scratch +COPY --from=build /tmp/anchore/grype /grype +ENTRYPOINT ["/grype"] diff --git a/src/base/.devcontainer/Dockerfile.syft b/src/base/.devcontainer/Dockerfile.syft new file mode 100644 index 0000000..7ad118f --- /dev/null +++ b/src/base/.devcontainer/Dockerfile.syft @@ -0,0 +1,16 @@ +FROM alpine:3.23.3 AS build +ARG TARGETARCH +ARG SYFT_VERSION="1.42.3" +ENV SYFT_VERSION=${SYFT_VERSION} +RUN apk add --no-cache cosign bash curl jq +COPY --chmod=755 scripts/install_anchore_tool.sh /tmp/install_anchore_tool.sh +RUN case "${TARGETARCH}" in \ + x86_64|amd64) ANCHORE_ARCH=amd64 ;; \ + aarch64|arm64) ANCHORE_ARCH=arm64 ;; \ + *) echo "Unsupported TARGETARCH: ${TARGETARCH}" && exit 1 ;; \ + esac \ + && INSTALL_DIR=/tmp/anchore/ TOOL=syft ARCH="${ANCHORE_ARCH}" VERSION="${SYFT_VERSION}" /tmp/install_anchore_tool.sh + +FROM scratch +COPY --from=build /tmp/anchore/syft /syft +ENTRYPOINT ["/syft"] diff --git a/src/base/.devcontainer/Mk/check.mk b/src/base/.devcontainer/Mk/check.mk index 7dd1c67..dbfc858 100644 --- a/src/base/.devcontainer/Mk/check.mk +++ b/src/base/.devcontainer/Mk/check.mk @@ -94,3 +94,75 @@ guard-%: zizmor: zizmor --min-severity medium . + +syft-generate-sbom: + mkdir -p .sbom + syft \ + --exclude './.github/**' \ + --output cyclonedx-json=.sbom/sbom.cdx.json \ + dir:./ + +syft-generate-sbom-dev-dependencies: + mkdir -p .sbom + SYFT_JAVASCRIPT_INCLUDE_DEV_DEPENDENCIES=true \ + syft \ + --exclude './.github/**' \ + --output cyclonedx-json=.sbom/sbom.dev.cdx.json \ + dir:./ + +grype-scan: syft-generate-sbom + grype \ + --fail-on high \ + .sbom/sbom.cdx.json + +grype-scan-dev-dependencies: syft-generate-sbom-dev-dependencies + grype \ + --fail-on high \ + .sbom/sbom.dev.cdx.json + +grype-scan-json: syft-generate-sbom + grype \ + --fail-on high \ + .sbom/sbom.cdx.json \ + --output json=".sbom/grype_analysis.json" + +grype-scan-json-dev-dependencies: syft-generate-sbom-dev-dependencies + grype \ + --fail-on high \ + .sbom/sbom.dev.cdx.json \ + --output json=".sbom/grype_analysis.dev.json" + +grype-scan-local: + grype \ + --fail-on high \ + . + +grype-scan-docker-image: guard-DOCKER_IMAGE + grype "${DOCKER_IMAGE}" \ + --scope all-layers \ + --sort-by severity \ + --fail-on high + +grant-scan: syft-generate-sbom + grant check \ + --dry-run \ + .sbom/sbom.cdx.json + +grant-scan-dev-dependencies: syft-generate-sbom-dev-dependencies + grant check \ + --dry-run \ + .sbom/sbom.dev.cdx.json + +grant-scan-json: syft-generate-sbom + grant check .sbom/sbom.cdx.json \ + --output json \ + --quiet \ + --dry-run \ + --output-file ".sbom/grant_analysis.json" + +grant-scan-json-dev-dependencies: syft-generate-sbom-dev-dependencies + grant check .sbom/sbom.dev.cdx.json \ + --output json \ + --quiet \ + --dry-run \ + --output-file ".sbom/grant_analysis.dev.json" diff --git a/src/base/.devcontainer/scripts/install_anchore_tool.sh b/src/base/.devcontainer/scripts/install_anchore_tool.sh new file mode 100755 index 0000000..3c56bde --- /dev/null +++ b/src/base/.devcontainer/scripts/install_anchore_tool.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +set -euo pipefail + +DEFAULT_INSTALL_DIR="/usr/local/bin" +INSTALL_DIR="${INSTALL_DIR:-$DEFAULT_INSTALL_DIR}" +BASE_URL="https://github.com/anchore/${TOOL}/releases/download/v${VERSION}" +ARCHIVE="${TOOL}_${VERSION}_linux_${ARCH}.tar.gz" +CHECKSUMS="${TOOL}_${VERSION}_checksums.txt" +CHECKSUMS_PEM="${TOOL}_${VERSION}_checksums.txt.pem" +CHECKSUMS_SIG="${TOOL}_${VERSION}_checksums.txt.sig" + +usage() { + cat <<'EOF' +Usage: install_anchore_tool.sh + +Downloads an Anchore tool (syft or grype) archive and its sigstore bundle to a temporary directory, +verifies the sigstore bundle following +https://oss.anchore.com/docs/installation/verification/, +and installs the Anchore tool binary into INSTALL_DIR (default: /usr/local/bin). + +Environment variables: + INSTALL_DIR Directory to install the Anchore tool binary into (default: /usr/local/bin) + VERSION Anchore tool version tag to install + ARCH Architecture suffix used in the download + TOOL Anchore tool name, either "syft" or "grype" +EOF +} + +if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then + usage + exit 0 +fi + +for cmd in curl cosign; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "Error: $cmd is required but not found in PATH" >&2 + exit 1 + fi +done + +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT + +download() { + local url="${1}" dest="${2}" + echo "Downloading ${dest} from ${url} ..." + curl -fsSL "${url}" -o "${dest}" +} +ARCHIVE_PATH="${TMP_DIR}/${ARCHIVE}" +ALL_CHECKSUMS_PATH="${TMP_DIR}/${CHECKSUMS}" +CHECKSUM_PATH="${TMP_DIR}/${ARCHIVE}.sha256sum" +CHECKSUMS_PEM_PATH="${TMP_DIR}/${CHECKSUMS_PEM}" +CHECKSUMS_SIG_PATH="${TMP_DIR}/${CHECKSUMS_SIG}" +download "${BASE_URL}/${ARCHIVE}" "${ARCHIVE_PATH}" +download "${BASE_URL}/${CHECKSUMS}" "${ALL_CHECKSUMS_PATH}" +download "${BASE_URL}/${CHECKSUMS_PEM}" "${CHECKSUMS_PEM_PATH}" +download "${BASE_URL}/${CHECKSUMS_SIG}" "${CHECKSUMS_SIG_PATH}" + +cosign verify-blob "${ALL_CHECKSUMS_PATH}" \ + --certificate "${CHECKSUMS_PEM_PATH}" \ + --signature "${CHECKSUMS_SIG_PATH}" \ + --certificate-identity-regexp "https://github\.com/anchore/${TOOL}/\.github/workflows/.+" \ + --certificate-oidc-issuer "https://token.actions.githubusercontent.com" + +echo "Sigstore verification passed" + +grep "${ARCHIVE}" "${ALL_CHECKSUMS_PATH}" > "${CHECKSUM_PATH}" + +cd "${TMP_DIR}" +sha256sum -c "${CHECKSUM_PATH}" +echo "Checksum verification passed" +tar -xzf "${ARCHIVE_PATH}" -C "${TMP_DIR}" + +mkdir -p "$INSTALL_DIR" +install -m 0755 "$TMP_DIR/${TOOL}" "${INSTALL_DIR}/${TOOL}" + +echo "${TOOL} ${VERSION} installed to ${INSTALL_DIR}" diff --git a/src/base/.devcontainer/scripts/lifecycle/post_attach.sh b/src/base/.devcontainer/scripts/lifecycle/post_attach.sh index 6fe214c..a3dd07d 100755 --- a/src/base/.devcontainer/scripts/lifecycle/post_attach.sh +++ b/src/base/.devcontainer/scripts/lifecycle/post_attach.sh @@ -2,4 +2,6 @@ # Script to run as devcontainer postAttachCommand set -euo pipefail +echo "Running common post-attach script" + # currently empty diff --git a/src/base/.devcontainer/scripts/lifecycle/post_create.sh b/src/base/.devcontainer/scripts/lifecycle/post_create.sh index 13c7512..27a71f1 100755 --- a/src/base/.devcontainer/scripts/lifecycle/post_create.sh +++ b/src/base/.devcontainer/scripts/lifecycle/post_create.sh @@ -2,6 +2,9 @@ # Script to run as devcontainer postCreateCommand set -euo pipefail +echo "Running common post-create script" + + # Install git-secrets, register AWS patterns and NHS rules in an idempotent way if ! git config --get-all secrets.patterns | grep -Fq AKIA; then git-secrets --register-aws diff --git a/src/base/.devcontainer/scripts/lifecycle/post_start.sh b/src/base/.devcontainer/scripts/lifecycle/post_start.sh index 63dbbc0..f7dfaa3 100755 --- a/src/base/.devcontainer/scripts/lifecycle/post_start.sh +++ b/src/base/.devcontainer/scripts/lifecycle/post_start.sh @@ -2,4 +2,6 @@ # Script to run as devcontainer postStartCommand set -euo pipefail +echo "Running common post-start script" + # currently empty diff --git a/src/base_node/node_24/.devcontainer/devcontainer.json b/src/base_node/node_24/.devcontainer/devcontainer.json index c14ec52..90d861a 100644 --- a/src/base_node/node_24/.devcontainer/devcontainer.json +++ b/src/base_node/node_24/.devcontainer/devcontainer.json @@ -13,9 +13,6 @@ }, "context": "." }, - "postCreateCommand": "bash ${SCRIPTS_DIR}/post_create.sh", - "postStartCommand": "bash ${SCRIPTS_DIR}/post_start.sh", - "postAttachCommand": "bash ${SCRIPTS_DIR}/post_attach.sh", "features": {} } diff --git a/src/languages/node_24_python_3_10/.devcontainer/devcontainer.json b/src/languages/node_24_python_3_10/.devcontainer/devcontainer.json index 3e8304b..a810718 100644 --- a/src/languages/node_24_python_3_10/.devcontainer/devcontainer.json +++ b/src/languages/node_24_python_3_10/.devcontainer/devcontainer.json @@ -13,9 +13,6 @@ }, "context": "." }, - "postCreateCommand": "bash ${SCRIPTS_DIR}/post_create.sh", - "postStartCommand": "bash ${SCRIPTS_DIR}/post_start.sh", - "postAttachCommand": "bash ${SCRIPTS_DIR}/post_attach.sh", "features": {} } diff --git a/src/languages/node_24_python_3_12/.devcontainer/devcontainer.json b/src/languages/node_24_python_3_12/.devcontainer/devcontainer.json index 95b9ffb..0ac0328 100644 --- a/src/languages/node_24_python_3_12/.devcontainer/devcontainer.json +++ b/src/languages/node_24_python_3_12/.devcontainer/devcontainer.json @@ -13,9 +13,6 @@ }, "context": "." }, - "postCreateCommand": "bash ${SCRIPTS_DIR}/post_create.sh", - "postStartCommand": "bash ${SCRIPTS_DIR}/post_start.sh", - "postAttachCommand": "bash ${SCRIPTS_DIR}/post_attach.sh", "features": {} } diff --git a/src/languages/node_24_python_3_13/.devcontainer/devcontainer.json b/src/languages/node_24_python_3_13/.devcontainer/devcontainer.json index 95b9ffb..0ac0328 100644 --- a/src/languages/node_24_python_3_13/.devcontainer/devcontainer.json +++ b/src/languages/node_24_python_3_13/.devcontainer/devcontainer.json @@ -13,9 +13,6 @@ }, "context": "." }, - "postCreateCommand": "bash ${SCRIPTS_DIR}/post_create.sh", - "postStartCommand": "bash ${SCRIPTS_DIR}/post_start.sh", - "postAttachCommand": "bash ${SCRIPTS_DIR}/post_attach.sh", "features": {} } diff --git a/src/languages/node_24_python_3_14/.devcontainer/devcontainer.json b/src/languages/node_24_python_3_14/.devcontainer/devcontainer.json index 01708ae..8580944 100644 --- a/src/languages/node_24_python_3_14/.devcontainer/devcontainer.json +++ b/src/languages/node_24_python_3_14/.devcontainer/devcontainer.json @@ -13,9 +13,6 @@ }, "context": "." }, - "postCreateCommand": "bash ${SCRIPTS_DIR}/post_create.sh", - "postStartCommand": "bash ${SCRIPTS_DIR}/post_start.sh", - "postAttachCommand": "bash ${SCRIPTS_DIR}/post_attach.sh", "features": {} } diff --git a/src/projects/eps-data-extract/.devcontainer/devcontainer.json b/src/projects/eps-data-extract/.devcontainer/devcontainer.json index bfcb365..8b10457 100644 --- a/src/projects/eps-data-extract/.devcontainer/devcontainer.json +++ b/src/projects/eps-data-extract/.devcontainer/devcontainer.json @@ -13,9 +13,6 @@ }, "context": "." }, - "postCreateCommand": "bash ${SCRIPTS_DIR}/post_create.sh", - "postStartCommand": "bash ${SCRIPTS_DIR}/post_start.sh", - "postAttachCommand": "bash ${SCRIPTS_DIR}/post_attach.sh", "features": {} } diff --git a/src/projects/eps-storage-terraform/.devcontainer/devcontainer.json b/src/projects/eps-storage-terraform/.devcontainer/devcontainer.json index 9c56e49..95c0a22 100644 --- a/src/projects/eps-storage-terraform/.devcontainer/devcontainer.json +++ b/src/projects/eps-storage-terraform/.devcontainer/devcontainer.json @@ -13,9 +13,6 @@ }, "context": "." }, - "postCreateCommand": "bash ${SCRIPTS_DIR}/post_create.sh", - "postStartCommand": "bash ${SCRIPTS_DIR}/post_start.sh", - "postAttachCommand": "bash ${SCRIPTS_DIR}/post_attach.sh", "features": {} } diff --git a/src/projects/fhir_facade_api/.devcontainer/devcontainer.json b/src/projects/fhir_facade_api/.devcontainer/devcontainer.json index 9c56e49..95c0a22 100644 --- a/src/projects/fhir_facade_api/.devcontainer/devcontainer.json +++ b/src/projects/fhir_facade_api/.devcontainer/devcontainer.json @@ -13,9 +13,6 @@ }, "context": "." }, - "postCreateCommand": "bash ${SCRIPTS_DIR}/post_create.sh", - "postStartCommand": "bash ${SCRIPTS_DIR}/post_start.sh", - "postAttachCommand": "bash ${SCRIPTS_DIR}/post_attach.sh", "features": {} } diff --git a/src/projects/node_24_python_3_14_golang_1_24/.devcontainer/devcontainer.json b/src/projects/node_24_python_3_14_golang_1_24/.devcontainer/devcontainer.json index 67e5a9b..e84c9ef 100644 --- a/src/projects/node_24_python_3_14_golang_1_24/.devcontainer/devcontainer.json +++ b/src/projects/node_24_python_3_14_golang_1_24/.devcontainer/devcontainer.json @@ -13,9 +13,6 @@ }, "context": "." }, - "postCreateCommand": "bash ${SCRIPTS_DIR}/post_create.sh", - "postStartCommand": "bash ${SCRIPTS_DIR}/post_start.sh", - "postAttachCommand": "bash ${SCRIPTS_DIR}/post_attach.sh", "features": {} } diff --git a/src/projects/node_24_python_3_14_java_24/.devcontainer/devcontainer.json b/src/projects/node_24_python_3_14_java_24/.devcontainer/devcontainer.json index 2a6b023..65abfbe 100644 --- a/src/projects/node_24_python_3_14_java_24/.devcontainer/devcontainer.json +++ b/src/projects/node_24_python_3_14_java_24/.devcontainer/devcontainer.json @@ -13,9 +13,6 @@ }, "context": "." }, - "postCreateCommand": "bash ${SCRIPTS_DIR}/post_create.sh", - "postStartCommand": "bash ${SCRIPTS_DIR}/post_start.sh", - "postAttachCommand": "bash ${SCRIPTS_DIR}/post_attach.sh", "features": {} } diff --git a/src/projects/regression_tests/.devcontainer/devcontainer.json b/src/projects/regression_tests/.devcontainer/devcontainer.json index 9c56e49..95c0a22 100644 --- a/src/projects/regression_tests/.devcontainer/devcontainer.json +++ b/src/projects/regression_tests/.devcontainer/devcontainer.json @@ -13,9 +13,6 @@ }, "context": "." }, - "postCreateCommand": "bash ${SCRIPTS_DIR}/post_create.sh", - "postStartCommand": "bash ${SCRIPTS_DIR}/post_start.sh", - "postAttachCommand": "bash ${SCRIPTS_DIR}/post_attach.sh", "features": {} }