Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 31 additions & 8 deletions .github/workflows/build_all_images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,22 @@ jobs:
discover_folders:
runs-on: ubuntu-latest
outputs:
language_folders: ${{ steps.find-folders.outputs.languages }}
base_node_folders: ${{ steps.find-folders.outputs.base_node }}
node_24_language_folders: ${{ steps.find-folders.outputs.node_24_languages }}
project_folders: ${{ steps.find-folders.outputs.projects }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd

- id: find-folders
run: |
language_folders=$(find src/languages -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | jq -R -s -c 'split("\n")[:-1]')
base_node_folders=$(find src/base_node -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | jq -R -s -c 'split("\n")[:-1]')
node_24_language_folders=$(find src/languages -mindepth 1 -maxdepth 1 -type d -name 'node_24*' -printf '%f\n' | jq -R -s -c 'split("\n")[:-1]')
project_folders=$(find src/projects -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | jq -R -s -c 'split("\n")[:-1]')
echo "languages=$language_folders" >> "$GITHUB_OUTPUT"
echo "projects=$project_folders" >> "$GITHUB_OUTPUT"
{
echo "base_node=$base_node_folders"
echo "node_24_languages=$node_24_language_folders"
echo "projects=$project_folders"
} >> "$GITHUB_OUTPUT"
package_base_docker_image:
uses: ./.github/workflows/build_multi_arch_image.yml
with:
Expand All @@ -36,25 +41,42 @@ jobs:
container_name: base
base_folder: "."
NO_CACHE: ${{ inputs.NO_CACHE }}
package_language_docker_images:
package_base_node_images:
needs:
- package_base_docker_image
- discover_folders
strategy:
fail-fast: false
matrix:
container_name: ${{ fromJson(needs.discover_folders.outputs.language_folders) }}
container_name: ${{ fromJson(needs.discover_folders.outputs.base_node_folders) }}
uses: ./.github/workflows/build_multi_arch_image.yml
with:
tag_latest: ${{ inputs.tag_latest }}
docker_tag: ${{ inputs.docker_tag }}
container_name: ${{ matrix.container_name }}
base_folder: "base_node"
NO_CACHE: ${{ inputs.NO_CACHE }}
EXTRA_COMMON: "common_node_24"
package_node_24_language_docker_images:
needs:
- package_base_docker_image
- package_base_node_images
- discover_folders
strategy:
fail-fast: false
matrix:
container_name: ${{ fromJson(needs.discover_folders.outputs.node_24_language_folders) }}
uses: ./.github/workflows/build_multi_arch_image.yml
with:
tag_latest: ${{ inputs.tag_latest }}
docker_tag: ${{ inputs.docker_tag }}
container_name: ${{ matrix.container_name }}
base_folder: "languages"
NO_CACHE: ${{ inputs.NO_CACHE }}
EXTRA_COMMON: "common_node_24"
package_project_docker_images:
needs:
- package_language_docker_images

- package_node_24_language_docker_images
- discover_folders
strategy:
fail-fast: false
Expand All @@ -67,3 +89,4 @@ jobs:
container_name: ${{ matrix.container_name }}
base_folder: "projects"
NO_CACHE: ${{ inputs.NO_CACHE }}
EXTRA_COMMON: "common_node_24"
64 changes: 27 additions & 37 deletions .github/workflows/build_multi_arch_image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ name: Build and push docker image
NO_CACHE:
required: true
type: boolean
EXTRA_COMMON:
required: false
type: string

jobs:
build_and_push_image:
Expand All @@ -27,6 +30,7 @@ jobs:
attestations: write
id-token: write
runs-on: '${{ matrix.runner }}'

strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -59,6 +63,10 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
fetch-depth: 0
- name: setup trivy
uses: aquasecurity/setup-trivy@3fb12ec12f41e471780db15c232d5dd185dcb514
with:
version: v0.69.1
- name: setup node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238
with:
Expand All @@ -72,17 +80,6 @@ jobs:
run: |
echo "Building image..."
make build-image

echo "Creating combined trivy ignore file"
# create combined trivy ignore file for use in trivy scan, combining common and specific ignore files if they exist
combined="src/${BASE_FOLDER}/${CONTAINER_NAME}/.trivyignore_combined.yaml"
common="src/common/.trivyignore.yaml"
specific="src/${BASE_FOLDER}/${CONTAINER_NAME}/.trivyignore.yaml"
echo "vulnerabilities:" > "$combined"
if [ -f "$common" ]; then sed -n '2,$p' "$common" >> "$combined"; fi
if [ -f "$specific" ]; then sed -n '2,$p' "$specific" >> "$combined"; fi
echo "Combined trivy ignore file created at $combined"

env:
ARCHITECTURE: '${{ matrix.arch }}'
CONTAINER_NAME: '${{ inputs.container_name }}'
Expand All @@ -92,41 +89,34 @@ jobs:
BASE_FOLDER: "${{ inputs.base_folder }}"
NO_CACHE: '${{ inputs.NO_CACHE }}'
- name: Check docker vulnerabilities - json output
uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284
with:
scan-type: "image"
image-ref: "ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.container_name }}:${{ inputs.docker_tag }}-${{ matrix.arch }}"
severity: "CRITICAL,HIGH"
scanners: "vuln"
vuln-type: "os,library"
format: "json"
output: "scan_results_docker.json"
exit-code: "0"
trivy-config: src/${{ inputs.base_folder }}/${{ inputs.container_name }}/trivy.yaml
run: |
make scan-image-json
env:
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f
name: Upload scan results
with:
name: "scan_results_docker_${{ inputs.container_name }}_${{ matrix.arch }}.json"
path: scan_results_docker.json
path: .out/scan_results_docker.json
- name: Check docker vulnerabilities - table output
uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284
with:
scan-type: "image"
image-ref: "ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.container_name }}:${{ inputs.docker_tag }}-${{ matrix.arch }}"
severity: "CRITICAL,HIGH"
scanners: "vuln"
vuln-type: "os,library"
format: "table"
output: "scan_results_docker.txt"
exit-code: "1"
trivy-config: src/${{ inputs.base_folder }}/${{ inputs.container_name }}/trivy.yaml

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 scan_results_docker.txt ]; then
cat scan_results_docker.txt
if [ -f .out/scan_results_docker.txt ]; then
cat .out/scan_results_docker.txt
fi
env:
ARCHITECTURE: '${{ matrix.arch }}'
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ jobs:
tag_format: ${{ needs.get_asdf_version.outputs.tag_format }}
secrets: inherit
build_all_images:
needs: tag_release
needs:
- tag_release
uses: ./.github/workflows/build_all_images.yml
with:
docker_tag: 'ci-${{ needs.tag_release.outputs.version_tag }}'
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ jobs:
tag_format: ${{ needs.get_asdf_version.outputs.tag_format }}
secrets: inherit
build_all_images:
needs: tag_release
needs:
- tag_release
uses: ./.github/workflows/build_all_images.yml
with:
docker_tag: '${{ needs.tag_release.outputs.version_tag }}'
Expand Down
25 changes: 19 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,32 +41,40 @@ build-githubactions-image: guard-BASE_IMAGE_NAME guard-BASE_IMAGE_TAG guard-IMAG
.

scan-image: guard-CONTAINER_NAME guard-BASE_FOLDER
mkdir -p .out
@combined="src/$${BASE_FOLDER}/$${CONTAINER_NAME}/.trivyignore_combined.yaml"; \
common="src/common/.trivyignore.yaml"; \
extra_common="src/$${EXTRA_COMMON}/.trivyignore.yaml"; \
specific="src/$${BASE_FOLDER}/$${CONTAINER_NAME}/.trivyignore.yaml"; \
exit_code="$${EXIT_CODE:-1}"; \
echo "vulnerabilities:" > "$$combined"; \
if [ -f "$$common" ]; then sed -n '2,$$p' "$$common" >> "$$combined"; fi; \
if [ -f "$$specific" ]; then sed -n '2,$$p' "$$specific" >> "$$combined"; fi
if [ -f "$$extra_common" ]; then sed -n '2,$$p' "$$extra_common" >> "$$combined"; fi; \
if [ -f "$$specific" ]; then sed -n '2,$$p' "$$specific" >> "$$combined"; fi; \
trivy image \
--severity HIGH,CRITICAL \
--config src/${BASE_FOLDER}/${CONTAINER_NAME}/trivy.yaml \
--scanners vuln \
--exit-code 1 \
--format table "${CONTAINER_PREFIX}$${CONTAINER_NAME}:$${IMAGE_TAG}"
--exit-code $$exit_code \
--format table \
--output .out/scan_results_docker.txt "${CONTAINER_PREFIX}$${CONTAINER_NAME}:$${IMAGE_TAG}"

scan-image-json: guard-CONTAINER_NAME guard-BASE_FOLDER guard-IMAGE_TAG
mkdir -p .out
@combined="src/$${BASE_FOLDER}/$${CONTAINER_NAME}/.trivyignore_combined.yaml"; \
common="src/common/.trivyignore.yaml"; \
extra_common="src/$${EXTRA_COMMON}/.trivyignore.yaml"; \
specific="src/$${BASE_FOLDER}/$${CONTAINER_NAME}/.trivyignore.yaml"; \
exit_code="$${EXIT_CODE:-1}"; \
echo "vulnerabilities:" > "$$combined"; \
if [ -f "$$common" ]; then sed -n '2,$$p' "$$common" >> "$$combined"; fi; \
if [ -f "$$specific" ]; then sed -n '2,$$p' "$$specific" >> "$$combined"; fi
mkdir -p .out
if [ -f "$$extra_common" ]; then sed -n '2,$$p' "$$extra_common" >> "$$combined"; fi; \
if [ -f "$$specific" ]; then sed -n '2,$$p' "$$specific" >> "$$combined"; fi; \
trivy image \
--severity HIGH,CRITICAL \
--config src/${BASE_FOLDER}/${CONTAINER_NAME}/trivy.yaml \
--scanners vuln \
--exit-code 1 \
--exit-code "$$exit_code" \
--format json \
--output .out/scan_results_docker.json "${CONTAINER_PREFIX}$${CONTAINER_NAME}:$${IMAGE_TAG}"

Expand All @@ -89,3 +97,8 @@ github-login:

lint-githubaction-scripts:
shellcheck .github/scripts/*.sh

clean:
rm -rf .out
find . -type f -name '.trivyignore_combined.yaml' -delete

37 changes: 27 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,18 +130,24 @@ It is important that
- there is `options: --user 1001:1001 --group-add 128` below image to ensure it uses the correct user id and is added to the docker group
- the default shell is set to be bash
- the first step copies .tool-versions from /home/vscode to $HOME/.tool-versions
## Using local or pull request images in visual studio code
You can use local or pull request images by changing IMAGE_VERSION in devcontainer.json.
For an image built locally following instructions below, you should put the IMAGE_VERSION=local-build.
For an image built from a pull request, you should put the IMAGE_VERSION=<tag of image as show in pull request job>.
You can only use images built from a pull request for testing changes in github actions.

# Project structure
We have 4 types of dev container. These are defined under src
We have 5 types of dev container. These are defined under src

`base` - this is the base image that all others are based on.
`languages` - this installs specific versions of node and python.
`base_node` - images that install node - most language projects rely on one of these
`languages` - this installs specific versions of python - normally based off a node image
`projects` - this is used for projects where more customization is needed than just a base language image.
`githubactions` - this just takes an existing image and remaps vscode user to be 1001 so it can be used by github actions.

Each image to be built contains a .devcontainer folder that defines how the devcontainer should be built. At a minimum, this should contain a devcontainer.json file. See https://containers.dev/implementors/json_reference/ for options for this

Images under languages should point to a dockerfile under src/common that is based off the base image. This also runs `.devcontainer/scripts/root_install.sh` and `.devcontainer/scripts/vscode_install.sh` as vscode user as part of the build. These files should be in the language specific folder.
Images under languages should point to a dockerfile under src/common or src/common_node_24 that is based off the base or node image. This also runs `.devcontainer/scripts/root_install.sh` and `.devcontainer/scripts/vscode_install.sh` as vscode user as part of the build. These files should be in the language specific folder.

We use trivy to scan for vulnerabilities in the built docker images. Known vulnerabilities in the base image are in `src/common/.trivyignore.yaml`. Vulnerabilities in specific images are in `.trivyignore.yaml` file in each images folder. These are combined before running a scan to exclude all known vulnerabilities

Expand Down Expand Up @@ -180,6 +186,14 @@ CONTAINER_NAME=base \
IMAGE_TAG=local-build \
make build-image
```
Base node 24 image
```
CONTAINER_NAME=node_24 \
BASE_VERSION_TAG=local-build \
BASE_FOLDER=base_node \
IMAGE_TAG=local-build \
make build-image
```
Language images
```
CONTAINER_NAME=node_24_python_3_14 \
Expand Down Expand Up @@ -212,11 +226,20 @@ CONTAINER_NAME=base \
IMAGE_TAG=local-build \
make scan-image
```
Base node 24 image
```
CONTAINER_NAME=node_24 \
BASE_FOLDER=base_node \
IMAGE_TAG=local-build \
EXTRA_COMMON=common_node_24 \
make scan-image
```
Language images
```
CONTAINER_NAME=node_24_python_3_12 \
CONTAINER_NAME=node_24_python_3_14 \
BASE_FOLDER=languages \
IMAGE_TAG=local-build \
EXTRA_COMMON=common_node_24 \
make scan-image
```
Project images
Expand Down Expand Up @@ -254,12 +277,6 @@ CONTAINER_NAME=base \
make shell-image
```

## Using local or pull request images in visual studio code
You can use local or pull request images by changing IMAGE_VERSION in devcontainer.json.
For an image built locally, you should put the IMAGE_VERSION=local-build.
For an image built from a pull request, you should put the IMAGE_VERSION=<tag of image as show in pull request job>.
You can only use images built from a pull request for testing changes in github actions.

## Generating a .trivyignore file
You can generate a .trivyignore file for known vulnerabilities by either downloading the json scan output generated by the build, or by generating it locally using the scanning images commands above with a make target of scan-image-json

Expand Down
8 changes: 6 additions & 2 deletions src/base/.devcontainer/Mk/check.mk
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ shellcheck:
fi

cfn-lint:
cfn-lint -I "cloudformation/**/*.y*ml" 2>&1 | awk '/Run scan/ { print } /^[EW][0-9]/ { print; getline; print }'
cfn-lint -I "SAMtemplates/**/*.y*ml" 2>&1 | awk '/Run scan/ { print } /^[EW][0-9]/ { print; getline; print }'
@if find cloudformation -type f \( -name "*.yaml" -o -name "*.yml" \) 2>/dev/null | grep -q .; then \
cfn-lint -I "cloudformation/**/*.y*ml" 2>&1 | awk '/Run scan/ { print } /^[EW][0-9]/ { print; getline; print; found=1 } END { exit found }'; \
fi
@if find SAMtemplates -type f \( -name "*.yaml" -o -name "*.yml" \) 2>/dev/null | grep -q .; then \
cfn-lint -I "SAMtemplates/**/*.y*ml" 2>&1 | awk '/Run scan/ { print } /^[EW][0-9]/ { print; getline; print; found=1 } END { exit found }'; \
fi

cdk-synth:
echo "Not implemented"
Expand Down
2 changes: 1 addition & 1 deletion src/base/.devcontainer/scripts/root_install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ apt-get -y install --no-install-recommends htop vim curl git build-essential \
libffi-dev libssl-dev libxml2-dev libxslt1-dev libjpeg8-dev libbz2-dev \
zlib1g-dev unixodbc unixodbc-dev libsecret-1-0 libsecret-1-dev libsqlite3-dev \
jq apt-transport-https ca-certificates gnupg-agent \
software-properties-common bash-completion make \
software-properties-common bash-completion make parallel \
libreadline-dev wget llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev liblzma-dev netcat-traditional libyaml-dev uuid-runtime xxd unzip

Expand Down
1 change: 1 addition & 0 deletions src/base_node/node_24/.devcontainer/.tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodejs 24.13.0
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
{
"name": "EPS Devcontainer node_24 python_3.10",
"name": "EPS Devcontainer node_24",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"build": {
"dockerfile": "../../../common/Dockerfile",
Expand Down
5 changes: 5 additions & 0 deletions src/base_node/node_24/.devcontainer/scripts/vscode_install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -e

asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git
asdf install
1 change: 1 addition & 0 deletions src/base_node/node_24/trivy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ignorefile: "src/base_node/node_24/.trivyignore_combined.yaml"
Loading