diff --git a/.github/actions/container-size-diff/action.yml b/.github/actions/container-size-diff/action.yml index 6c846019..fe10f7f3 100644 --- a/.github/actions/container-size-diff/action.yml +++ b/.github/actions/container-size-diff/action.yml @@ -18,14 +18,10 @@ outputs: runs: using: "composite" steps: - - run: echo "$GITHUB_ACTION_PATH" >> "$GITHUB_PATH" - shell: bash - env: - GITHUB_ACTION_PATH: ${{ github.action_path }} - run: | EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) echo "markdown<<${EOF}" >> "${GITHUB_OUTPUT}" - echo "$(container-size-diff.sh ${INPUT_FROM_CONTAINER} ${INPUT_TO_CONTAINER})" >> "${GITHUB_OUTPUT}" + echo "$(${GITHUB_ACTION_PATH}/container-size-diff.sh ${INPUT_FROM_CONTAINER} ${INPUT_TO_CONTAINER})" >> "${GITHUB_OUTPUT}" echo "${EOF}" >> "${GITHUB_OUTPUT}" id: size-diff shell: bash diff --git a/.github/actions/container-size-diff/container-size-diff.sh b/.github/actions/container-size-diff/container-size-diff.sh index 5a5c3362..3a914e83 100755 --- a/.github/actions/container-size-diff/container-size-diff.sh +++ b/.github/actions/container-size-diff/container-size-diff.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -Eeuo pipefail + FROM_CONTAINER=${1:?} TO_CONTAINER=${2:?} diff --git a/.github/actions/update-apt-packages/action.yml b/.github/actions/update-apt-packages/action.yml index 3d952ee6..612c1104 100644 --- a/.github/actions/update-apt-packages/action.yml +++ b/.github/actions/update-apt-packages/action.yml @@ -15,16 +15,12 @@ outputs: runs: using: "composite" steps: - - run: echo "$GITHUB_ACTION_PATH" >> "$GITHUB_PATH" - shell: bash - env: - GITHUB_ACTION_PATH: ${{ github.action_path }} - run: | apt-get update apt-get install --no-install-recommends -y jq shell: bash - run: | - update-apt-packages.sh ${INPUT_FILE} + ${GITHUB_ACTION_PATH}/update-apt-packages.sh ${INPUT_FILE} echo "updated-dependencies=$(cat updated-packages.json)" >> "${GITHUB_OUTPUT}" rm updated-packages.json id: update-extensions diff --git a/.github/actions/update-vscode-extensions/action.yml b/.github/actions/update-vscode-extensions/action.yml index 57baea42..ff4f6143 100644 --- a/.github/actions/update-vscode-extensions/action.yml +++ b/.github/actions/update-vscode-extensions/action.yml @@ -18,10 +18,6 @@ outputs: runs: using: "composite" steps: - - run: echo "$GITHUB_ACTION_PATH" >> "$GITHUB_PATH" - shell: bash - env: - GITHUB_ACTION_PATH: ${{ github.action_path }} - run: | sudo apt-get update sudo apt-get install --no-install-recommends -y jq @@ -30,7 +26,7 @@ runs: - run: | EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) echo "markdown-summary<<${EOF}" >> "${GITHUB_OUTPUT}" - echo "$(update-vscode-extensions.sh ${INPUT_FILE})" >> "${GITHUB_OUTPUT}" + echo "$(${GITHUB_ACTION_PATH}/update-vscode-extensions.sh ${INPUT_FILE})" >> "${GITHUB_OUTPUT}" echo "${EOF}" >> "${GITHUB_OUTPUT}" echo "updated-dependencies=$(cat updated-extensions.json)" >> "${GITHUB_OUTPUT}" diff --git a/.github/linters/.trivyignore.yml b/.github/linters/.trivyignore.yml new file mode 100644 index 00000000..b79a6b06 --- /dev/null +++ b/.github/linters/.trivyignore.yml @@ -0,0 +1,15 @@ +--- +misconfigurations: + - id: AVD-DS-0002 + statement: We allow root access in our container that we use for development purposes (https://avd.aquasec.com/misconfig/dockerfile/general/avd-ds-0002/) +vulnerabilities: + - id: CVE-2025-50181 + paths: + - ".devcontainer/cpp/requirements.txt" + expired_at: 2025-10-01 + statement: This vulnerable dependency comes in via the Conan package, work is in-progress on supporting a non-vulnerable version (https://github.com/conan-io/conan/issues/13948) + - id: CVE-2025-50182 + paths: + - ".devcontainer/cpp/requirements.txt" + expired_at: 2025-10-01 + statement: This vulnerable dependency comes in via the Conan package, work is in-progress on supporting a non-vulnerable version (https://github.com/conan-io/conan/issues/13948) diff --git a/.github/workflows/linting-formatting.yml b/.github/workflows/linting-formatting.yml index 74924087..24fc26e9 100644 --- a/.github/workflows/linting-formatting.yml +++ b/.github/workflows/linting-formatting.yml @@ -28,6 +28,7 @@ jobs: with: fetch-depth: 0 persist-credentials: false + - uses: zizmorcore/zizmor-action@f52a838cfabf134edcbaa7c8b3677dde20045018 # v0.1.1 # flavors/dotnet is the smallest flavor of MegaLinter that contains the linters # we are interested in. - uses: oxsecurity/megalinter/flavors/dotnet@e08c2b05e3dbc40af4c23f41172ef1e068a7d651 # v8.8.0 diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 75d15d2e..b9970032 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -26,52 +26,67 @@ jobs: id-token: write packages: write pull-requests: write + with: + # Disable the cache for release builds + enable-cache: false apply-release-notes-template: runs-on: ubuntu-latest steps: - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 with: egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Amend release description - env: - GITHUB_TOKEN: ${{ github.token }} run: | - CURRENT_NOTES=$(gh release view ${{ github.ref_name }} --json body -q '.body') + set -Eeuo pipefail + CURRENT_NOTES=$(gh release view "${REF_NAME}" --json body -q '.body') HEADER=$(echo "$CURRENT_NOTES" | awk '/^## / {print; exit}') TEMPLATE=$(cat "$GITHUB_WORKSPACE/.github/RELEASE_TEMPLATE.md") BODY=$(echo "$CURRENT_NOTES" | sed "0,/^## /d") - gh release edit ${{ github.ref_name }} --notes "${HEADER}${TEMPLATE}${BODY}" + gh release edit "${REF_NAME}" --notes "${HEADER}${TEMPLATE}${BODY}" + env: + GITHUB_TOKEN: ${{ github.token }} + REF_NAME: ${{ github.ref_name }} update-release-notes: strategy: matrix: flavor: [cpp, rust] runs-on: ubuntu-latest - needs: build-push-test + needs: [build-push-test, apply-release-notes-template] env: + CONTAINER_FLAVOR: ${{ matrix.flavor }} + REF_NAME: ${{ github.ref_name }} REGISTRY: ghcr.io steps: - - uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 + - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 with: egress-policy: audit - name: Inspect manifest and extract digest id: inspect-manifest run: | set -Eeuo pipefail - output=$(docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ github.repository }}-${{ matrix.flavor }}:${{ github.ref_name }} --format '{{json .}}') + output=$(docker buildx imagetools inspect "${REGISTRY}/${{ github.repository }}-${CONTAINER_FLAVOR}:${REF_NAME}" --format '{{json .}}') echo "digest=$(echo "$output" | jq -r '.manifest.digest // .manifests[0].digest')" >> "$GITHUB_OUTPUT" - name: Upload provenance to release + run: | + set -Eeuo pipefail + FORMATTED_DIGEST=${DIGEST//:/_} + gh attestation verify --repo ${{ github.repository }} "oci://${REGISTRY}/${{ github.repository }}-${CONTAINER_FLAVOR}@${DIGEST}" --format json --jq '.[] | .attestation.bundle.dsseEnvelope | select(.payloadType == "application/vnd.in-toto+json").payload' | base64 -d | jq . > "${REPOSITORY_OWNER}-${REPOSITORY_NAME}-${CONTAINER_FLAVOR}_${FORMATTED_DIGEST}.intoto.jsonl" + gh release upload "${REF_NAME}" ./*.intoto.jsonl env: + DIGEST: ${{ steps.inspect-manifest.outputs.digest }} GH_TOKEN: ${{ github.token }} - run: | - RAW_SHA=${{ steps.inspect-manifest.outputs.digest }} - FORMATTED_SHA=${RAW_SHA//:/_} - gh attestation verify --repo ${{ github.repository }} oci://${{ env.REGISTRY }}/${{ github.repository }}-${{ matrix.flavor }}@${{ steps.inspect-manifest.outputs.digest }} --format json --jq '.[] | .attestation.bundle.dsseEnvelope | select(.payloadType == "application/vnd.in-toto+json").payload' | base64 -d | jq . > "${{ github.repository_owner }}-${{ github.event.repository.name }}-${{ matrix.flavor }}_${FORMATTED_SHA}.intoto.jsonl" - gh release upload ${{ github.ref_name }} ./*.intoto.jsonl + REPOSITORY_OWNER: ${{ github.repository_owner }} + REPOSITORY_NAME: ${{ github.event.repository.name }} - name: Update package details in release + run: | + set -Eeuo pipefail + UPDATED_NOTES=$(gh release view "${REF_NAME}" --json body -q '.body') + UPDATED_NOTES=${UPDATED_NOTES//"{{ amp-devcontainer-${CONTAINER_FLAVOR}-version }}"/"${REF_NAME}"} + UPDATED_NOTES=${UPDATED_NOTES//"{{ amp-devcontainer-${CONTAINER_FLAVOR}-sha }}"/"${DIGEST}"} + gh release edit "${REF_NAME}" --notes "${UPDATED_NOTES}" env: + DIGEST: ${{ steps.inspect-manifest.outputs.digest }} GH_TOKEN: ${{ github.token }} - run: | - UPDATED_NOTES=$(gh release view ${{ github.ref_name }} --json body -q '.body') - UPDATED_NOTES=${UPDATED_NOTES//'{{ amp-devcontainer-${{ matrix.flavor }}-version }}'/'${{ github.ref_name }}'} - UPDATED_NOTES=${UPDATED_NOTES//'{{ amp-devcontainer-${{ matrix.flavor }}-sha }}'/'${{ steps.inspect-manifest.outputs.digest }}'} - gh release edit ${{ github.ref_name }} --notes "${UPDATED_NOTES}" diff --git a/.github/workflows/wc-acceptance-test.yml b/.github/workflows/wc-acceptance-test.yml index dc35ff7d..4907e221 100644 --- a/.github/workflows/wc-acceptance-test.yml +++ b/.github/workflows/wc-acceptance-test.yml @@ -39,8 +39,9 @@ jobs: gh secret set -a codespaces IMAGE_VERSION --body "edge" fi - echo CODESPACE_NAME="$(gh codespace create -R "${{ github.repository }}" -b "$HEAD_REF" -m basicLinux32gb --devcontainer-path ".devcontainer/${{ inputs.flavor }}-test/devcontainer.json" --idle-timeout 10m --retention-period 1h)" >> "$GITHUB_ENV" + echo CODESPACE_NAME="$(gh codespace create -R "${{ github.repository }}" -b "$HEAD_REF" -m basicLinux32gb --devcontainer-path ".devcontainer/${CONTAINER_FLAVOR}-test/devcontainer.json" --idle-timeout 10m --retention-period 1h)" >> "$GITHUB_ENV" env: + CONTAINER_FLAVOR: ${{ inputs.flavor }} GH_TOKEN: ${{ secrets.TEST_GITHUB_TOKEN }} HEAD_REF: ${{ github.head_ref }} - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 @@ -56,7 +57,7 @@ jobs: SECONDS_ELAPSED=0 while true; do - STATE=$(gh codespace list --json name,state --jq ".[] | select(.name == \"$CODESPACE_NAME\") | .state") + STATE=$(gh codespace list --json name,state --jq ".[] | select(.name == \"${CODESPACE_NAME}\") | .state") echo "Current state: $STATE" if [ "$STATE" == "Available" ]; then echo "Codespace is active!" @@ -71,8 +72,9 @@ jobs: done env: GH_TOKEN: ${{ secrets.TEST_GITHUB_TOKEN }} - - run: cd test/${{ inputs.flavor }}/features && npm test + - run: cd "test/${CONTAINER_FLAVOR}/features" && npm test env: + CONTAINER_FLAVOR: ${{ inputs.flavor }} GITHUB_USER: ${{ secrets.TEST_GITHUB_USER }} GITHUB_PASSWORD: ${{ secrets.TEST_GITHUB_PASSWORD }} GITHUB_TOTP_SECRET: ${{ secrets.TEST_GITHUB_TOTP_SECRET }} @@ -85,7 +87,7 @@ jobs: retention-days: 10 - run: | set -Eeuo pipefail - gh codespace delete --force --codespace "$CODESPACE_NAME" + gh codespace delete --force --codespace "${CODESPACE_NAME}" gh secret set -a codespaces IMAGE_VERSION --body "latest" if: always() env: diff --git a/.github/workflows/wc-build-push-test.yml b/.github/workflows/wc-build-push-test.yml index b13e56ad..d247d218 100644 --- a/.github/workflows/wc-build-push-test.yml +++ b/.github/workflows/wc-build-push-test.yml @@ -3,6 +3,11 @@ name: Build, Push & Test on: workflow_call: + inputs: + enable-cache: + required: false + type: boolean + default: true permissions: contents: read @@ -13,7 +18,6 @@ jobs: matrix: flavor: [cpp, rust] uses: ./.github/workflows/wc-build-push.yml - secrets: inherit permissions: actions: read attestations: write @@ -23,6 +27,7 @@ jobs: pull-requests: write with: flavor: ${{ matrix.flavor }} + enable-cache: ${{ inputs.enable-cache }} dependency-review: runs-on: ubuntu-latest diff --git a/.github/workflows/wc-build-push.yml b/.github/workflows/wc-build-push.yml index a1e37df4..52cd2b8b 100644 --- a/.github/workflows/wc-build-push.yml +++ b/.github/workflows/wc-build-push.yml @@ -7,11 +7,16 @@ on: flavor: required: true type: string + enable-cache: + required: false + type: boolean + default: true permissions: contents: read env: + CONTAINER_FLAVOR: ${{ inputs.flavor }} REGISTRY: ghcr.io jobs: @@ -43,7 +48,7 @@ jobs: images: ${{ env.REGISTRY }}/${{ github.repository }}-${{ inputs.flavor }} # Generate image LABEL for devcontainer.metadata # the sed expression is a workaround for quotes being eaten in arrays (e.g. ["x", "y", "z"] -> ["x",y,"z"]) - - run: echo "metadata=$(jq -cj '[.]' .devcontainer/${{ inputs.flavor }}/devcontainer-metadata-vscode.json | sed 's/,"/, "/g')" >> "$GITHUB_OUTPUT" + - run: echo "metadata=$(jq -cj '[.]' ".devcontainer/${CONTAINER_FLAVOR}/devcontainer-metadata-vscode.json" | sed 's/,"/, "/g')" >> "$GITHUB_OUTPUT" id: devcontainer-metadata - run: echo "git-commit-epoch=$(git log -1 --pretty=%ct)" >> "$GITHUB_OUTPUT" id: devcontainer-epoch @@ -63,13 +68,15 @@ jobs: annotations: ${{ steps.metadata.outputs.annotations }} sbom: true outputs: type=image,push-by-digest=true,name-canonical=true - cache-to: type=gha,mode=max,scope=${{ github.repository }}-${{ inputs.flavor }}-${{ matrix.runner }} - cache-from: type=gha,scope=${{ github.repository }}-${{ inputs.flavor }}-${{ matrix.runner }} + cache-to: ${{ inputs.enable-cache && format('type=gha,mode=max,scope={0}-{1}-{2}', github.repository, inputs.flavor, matrix.runner) || '' }} + cache-from: ${{ inputs.enable-cache && format('type=gha,scope={0}-{1}-{2}', github.repository, inputs.flavor, matrix.runner) || '' }} - name: Export digest run: | + set -Eeuo pipefail mkdir -p ${{ runner.temp }}/digests - digest="${{ steps.build-and-push.outputs.digest }}" - touch "${{ runner.temp }}/digests/${digest#sha256:}" + touch "${{ runner.temp }}/digests/${DIGEST#sha256:}" + env: + DIGEST: ${{ steps.build-and-push.outputs.digest }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: digests-${{ inputs.flavor }}-${{ steps.devcontainer-arch.outputs.arch }} @@ -123,15 +130,13 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} - name: Create manifest list and push - working-directory: ${{ runner.temp }}/digests - shell: python run: | import os import json import subprocess - CONTAINER = '${{ env.REGISTRY }}/${{ github.repository }}-${{ inputs.flavor }}' - METADATA = json.loads('${{ steps.metadata.outputs.json }}') + CONTAINER = f"{os.getenv('REGISTRY')}/${{ github.repository }}-{os.getenv('CONTAINER_FLAVOR')}" + METADATA = json.loads(os.getenv('METADATA_JSON')) digests = [f for f in os.listdir('.') if f.startswith('sha256:') or len(f) == 64] @@ -143,12 +148,18 @@ jobs: print(' '.join(command)) subprocess.run(command, check=True) + env: + METADATA_JSON: ${{ steps.metadata.outputs.json }} + shell: python + working-directory: ${{ runner.temp }}/digests - name: Inspect manifest and extract digest id: inspect-manifest run: | set -Eeuo pipefail - output=$(docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ github.repository }}-${{ inputs.flavor }}:${{ steps.metadata.outputs.version }} --format '{{json .}}') + output=$(docker buildx imagetools inspect "${REGISTRY}/${{ github.repository }}-${CONTAINER_FLAVOR}:${CONTAINER_VERSION}" --format '{{json .}}') echo "digest=$(echo "$output" | jq -r '.manifest.digest // .manifests[0].digest')" >> "$GITHUB_OUTPUT" + env: + CONTAINER_VERSION: ${{ steps.metadata.outputs.version }} - uses: ./.github/actions/container-size-diff id: container-size-diff with: @@ -170,7 +181,7 @@ jobs: show-summary: false push-to-registry: true - name: Verify attestation + run: gh attestation verify --repo ${{ github.repository }} "oci://${REGISTRY}/${{ github.repository }}-${CONTAINER_FLAVOR}@${DIGEST}" env: + DIGEST: ${{ steps.inspect-manifest.outputs.digest }} GH_TOKEN: ${{ github.token }} - run: | - gh attestation verify --repo ${{ github.repository }} oci://${{ env.REGISTRY }}/${{ github.repository }}-${{ inputs.flavor }}@${{ steps.inspect-manifest.outputs.digest }} diff --git a/.github/workflows/wc-integration-test.yml b/.github/workflows/wc-integration-test.yml index cc6dec32..8c0860e7 100644 --- a/.github/workflows/wc-integration-test.yml +++ b/.github/workflows/wc-integration-test.yml @@ -14,6 +14,10 @@ on: permissions: contents: read +env: + CONTAINER_FLAVOR: ${{ inputs.flavor }} + RUNNER: ${{ inputs.runner }} + jobs: determine-container: runs-on: ${{ inputs.runner }} @@ -29,7 +33,7 @@ jobs: with: path: ${{ runner.temp }} pattern: digests-${{ inputs.flavor }}-${{ steps.runner-arch.outputs.arch }} - - run: echo "container=$(printf 'ghcr.io/${{ github.repository }}-${{ inputs.flavor }}@sha256:%s ' *)" >> "$GITHUB_OUTPUT" + - run: echo "container=$(printf "ghcr.io/${{ github.repository }}-${CONTAINER_FLAVOR}@sha256:%s " *)" >> "$GITHUB_OUTPUT" working-directory: ${{ runner.temp }}/digests-${{ inputs.flavor }}-${{ steps.runner-arch.outputs.arch }} id: set-container run-test: @@ -50,7 +54,7 @@ jobs: key: xwin-cache-${{ inputs.runner }} restore-keys: | xwin-cache - - run: bats --formatter junit test/${{ inputs.flavor }}/integration-tests.bats | tee test-report-${{ inputs.flavor }}-${{ inputs.runner }}.xml + - run: bats --formatter junit "test/${CONTAINER_FLAVOR}/integration-tests.bats" | tee "test-report-${CONTAINER_FLAVOR}-${RUNNER}.xml" - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: always() with: diff --git a/.mega-linter.yml b/.mega-linter.yml index f865708e..1326467a 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -24,3 +24,4 @@ FILTER_REGEX_EXCLUDE: (CHANGELOG.md|package-lock.json) # dynamically based upon context (e.g. installed extensions) # hence the exclusion JSON_V8R_FILTER_REGEX_EXCLUDE: (\.vscode) +REPOSITORY_TRIVY_ARGUMENTS: --ignorefile .github/linters/.trivyignore.yml diff --git a/.trivyignore b/.trivyignore deleted file mode 100644 index 4d0c37f7..00000000 --- a/.trivyignore +++ /dev/null @@ -1,3 +0,0 @@ -# See: https://avd.aquasec.com/misconfig/dockerfile/general/avd-ds-0002/ -# We allow root access in our container that we use for development purposes -DS002