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
75 changes: 75 additions & 0 deletions .github/actions/build-and-scan/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Build and scan runtime images
description: Build both runtime images for amd64 and arm64, then scan each image
inputs:
nextcloud_version:
description: Nextcloud version passed to the app image build
required: true
runs:
using: composite
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: arm64

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build app image (linux/amd64)
uses: docker/build-push-action@v6
with:
context: .docker/app
platforms: linux/amd64
load: true
build-args: |
NEXTCLOUD_VERSION=${{ inputs.nextcloud_version }}
tags: scan/app:amd64
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Build app image (linux/arm64)
uses: docker/build-push-action@v6
with:
context: .docker/app
platforms: linux/arm64
load: true
build-args: |
NEXTCLOUD_VERSION=${{ inputs.nextcloud_version }}
tags: scan/app:arm64
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Build web image (linux/amd64)
uses: docker/build-push-action@v6
with:
context: .docker/web
platforms: linux/amd64
load: true
tags: scan/web:amd64
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Build web image (linux/arm64)
uses: docker/build-push-action@v6
with:
context: .docker/web
platforms: linux/arm64
load: true
tags: scan/web:arm64
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Install Trivy
uses: aquasecurity/setup-trivy@81e514348e19b6112ce2a7e3ecbafe19c1e1f567 # v0.3.1
with:
version: v0.74.0
cache: true

- name: Scan runtime images
shell: bash
run: |
bash scripts/scan-images.sh \
'app@linux/amd64=scan/app:amd64' \
'app@linux/arm64=scan/app:arm64' \
'web@linux/amd64=scan/web:amd64' \
'web@linux/arm64=scan/web:arm64'
138 changes: 113 additions & 25 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,54 @@ on:
branches:
- main

permissions:
contents: read

env:
REGISTRY: ghcr.io

jobs:
build:
verify:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Read Nextcloud version
id: nextcloud_version
run: |
version=$(grep -m1 '^NEXTCLOUD_VERSION=' .env.example | cut -d= -f2-)
if [ -z "$version" ]; then
echo "NEXTCLOUD_VERSION is missing from .env.example" >&2
exit 1
fi
echo "value=$version" >> "$GITHUB_OUTPUT"

- name: Build and scan runtime images
uses: ./.github/actions/build-and-scan
with:
nextcloud_version: ${{ steps.nextcloud_version.outputs.value }}

- name: Save SARIF reports
uses: actions/upload-artifact@v4
with:
name: trivy-sarif-reports
path: trivy-results/*.sarif
if-no-files-found: warn

publish:
if: github.event_name == 'push'
runs-on: ubuntu-latest

permissions:
contents: read
packages: write
security-events: write

steps:
- name: Checkout repository
Expand All @@ -37,35 +75,85 @@ jobs:
id: repo_name
run: echo "value=${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and scan runtime images
uses: ./.github/actions/build-and-scan
with:
nextcloud_version: ${{ steps.nextcloud_version.outputs.value }}

- name: Upload app amd64 SARIF to GitHub code scanning
if: hashFiles('trivy-results/app-linux-amd64.sarif') != ''
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results/app-linux-amd64.sarif
category: trivy-app-linux-amd64

- name: Upload app arm64 SARIF to GitHub code scanning
if: hashFiles('trivy-results/app-linux-arm64.sarif') != ''
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results/app-linux-arm64.sarif
category: trivy-app-linux-arm64

- name: Upload web amd64 SARIF to GitHub code scanning
if: hashFiles('trivy-results/web-linux-amd64.sarif') != ''
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results/web-linux-amd64.sarif
category: trivy-web-linux-amd64

- name: Upload web arm64 SARIF to GitHub code scanning
if: hashFiles('trivy-results/web-linux-arm64.sarif') != ''
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results/web-linux-arm64.sarif
category: trivy-web-linux-arm64

- name: Save SARIF reports
uses: actions/upload-artifact@v4
with:
name: trivy-sarif-reports
path: trivy-results/*.sarif
if-no-files-found: warn

- name: Log in to Container Registry
if: github.event_name == 'push'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build app image
uses: docker/build-push-action@v6
with:
context: .docker/app
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name == 'push' }}
build-args: |
NEXTCLOUD_VERSION=${{ steps.nextcloud_version.outputs.value }}
tags: ${{ env.REGISTRY }}/${{ steps.repo_name.outputs.value }}-app:latest
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Build web image
uses: docker/build-push-action@v6
with:
context: .docker/web
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name == 'push' }}
tags: ${{ env.REGISTRY }}/${{ steps.repo_name.outputs.value }}-web:latest
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Push scanned architecture images
env:
APP_IMAGE: ${{ env.REGISTRY }}/${{ steps.repo_name.outputs.value }}-app
WEB_IMAGE: ${{ env.REGISTRY }}/${{ steps.repo_name.outputs.value }}-web
IMAGE_VERSION: sha-${{ github.sha }}
run: |
set -euo pipefail

docker tag scan/app:amd64 "${APP_IMAGE}:${IMAGE_VERSION}-amd64"
docker tag scan/app:arm64 "${APP_IMAGE}:${IMAGE_VERSION}-arm64"
docker tag scan/web:amd64 "${WEB_IMAGE}:${IMAGE_VERSION}-amd64"
docker tag scan/web:arm64 "${WEB_IMAGE}:${IMAGE_VERSION}-arm64"

docker push "${APP_IMAGE}:${IMAGE_VERSION}-amd64"
docker push "${APP_IMAGE}:${IMAGE_VERSION}-arm64"
docker push "${WEB_IMAGE}:${IMAGE_VERSION}-amd64"
docker push "${WEB_IMAGE}:${IMAGE_VERSION}-arm64"

- name: Publish multi-platform image tags
env:
APP_IMAGE: ${{ env.REGISTRY }}/${{ steps.repo_name.outputs.value }}-app
WEB_IMAGE: ${{ env.REGISTRY }}/${{ steps.repo_name.outputs.value }}-web
IMAGE_VERSION: sha-${{ github.sha }}
run: |
set -euo pipefail

docker buildx imagetools create \
--tag "${APP_IMAGE}:latest" \
"${APP_IMAGE}:${IMAGE_VERSION}-amd64" \
"${APP_IMAGE}:${IMAGE_VERSION}-arm64"

docker buildx imagetools create \
--tag "${WEB_IMAGE}:latest" \
"${WEB_IMAGE}:${IMAGE_VERSION}-amd64" \
"${WEB_IMAGE}:${IMAGE_VERSION}-arm64"
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
!/backups/.gitkeep
.env
docker-compose.override.yml
.trivy-cache/
trivy-results/
23 changes: 22 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
COMPOSE ?= docker compose
GARAGES3_COMPOSE_FILE ?= docker-compose-garages3.yml

.PHONY: up-garages3 down-garages3 bootstrap-garages3 garage-status-garages3 start-garages3 wait-nextcloud-garages3 setup-garages3 test-hooks
.PHONY: up-garages3 down-garages3 bootstrap-garages3 garage-status-garages3 start-garages3 wait-nextcloud-garages3 setup-garages3 test-hooks test-scan-images scan-images

up-garages3:
$(COMPOSE) -f $(GARAGES3_COMPOSE_FILE) up -d garage
Expand All @@ -28,3 +28,24 @@ setup-garages3:

test-hooks:
bash tests/test-hooks.sh

test-scan-images:
bash tests/test-scan-images.sh

scan-images:
@set -e; \
version="$$(sed -n 's/^NEXTCLOUD_VERSION=//p' .env.example | head -n 1)"; \
test -n "$$version" || { echo 'NEXTCLOUD_VERSION is missing from .env.example' >&2; exit 1; }; \
docker buildx build --platform linux/amd64 --load --tag nextcloud-app:scan-amd64 \
--build-arg "NEXTCLOUD_VERSION=$$version" --file .docker/app/Dockerfile .docker/app; \
docker buildx build --platform linux/arm64 --load --tag nextcloud-app:scan-arm64 \
--build-arg "NEXTCLOUD_VERSION=$$version" --file .docker/app/Dockerfile .docker/app; \
docker buildx build --platform linux/amd64 --load --tag nextcloud-web:scan-amd64 \
--file .docker/web/Dockerfile .docker/web; \
docker buildx build --platform linux/arm64 --load --tag nextcloud-web:scan-arm64 \
--file .docker/web/Dockerfile .docker/web; \
bash scripts/scan-images.sh \
'app@linux/amd64=nextcloud-app:scan-amd64' \
'app@linux/arm64=nextcloud-app:scan-arm64' \
'web@linux/amd64=nextcloud-web:scan-amd64' \
'web@linux/arm64=nextcloud-web:scan-arm64'
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,15 @@ docker compose build --pull
docker compose up -d
```

## Vulnerability scanning

Published `app` and `web` images are scanned for vulnerabilities. Contributors
can run the scan locally with:

```bash
make scan-images
```

## Logs

If you want to see the logs, run:
Expand Down
71 changes: 71 additions & 0 deletions scripts/scan-images.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env bash
set -uo pipefail

if [[ $# -eq 0 ]]; then
printf 'Usage: %s label@linux/amd64=image [label@linux/arm64=image ...]\n' "$0" >&2
exit 2
fi

trivy_bin="${TRIVY_BIN:-trivy}"
if ! command -v "$trivy_bin" >/dev/null 2>&1; then
if [[ "$trivy_bin" == "trivy" ]] && command -v trivy.exe >/dev/null 2>&1; then
trivy_bin=trivy.exe
else
printf 'Trivy is required. Install the version documented in README.md.\n' >&2
exit 127
fi
fi

script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd -- "${script_dir}/.." && pwd)"
config="${repo_root}/trivy.yaml"
report_dir="${TRIVY_REPORT_DIR:-${repo_root}/trivy-results}"

if ! mkdir -p "$report_dir"; then
printf 'Could not create SARIF output directory: %s\n' "$report_dir" >&2
exit 1
fi

trivy_config="$config"
if [[ "$trivy_bin" == "trivy.exe" ]] && command -v wslpath >/dev/null 2>&1; then
trivy_config="$(wslpath -w "$config")"
fi

scan_status=0
for image_spec in "$@"; do
if [[ "$image_spec" != *=* ]]; then
printf 'Expected label=image, received: %s\n' "$image_spec" >&2
exit 2
fi

label_platform="${image_spec%%=*}"
label="${label_platform%%@*}"
platform="${label_platform#*@}"
image="${image_spec#*=}"

if [[ "$label_platform" != *@* || ! "$label" =~ ^[A-Za-z0-9_-]+$ || -z "$image" ]]; then
printf 'Invalid image specification: %s\n' "$image_spec" >&2
exit 2
fi
if [[ "$platform" != linux/amd64 && "$platform" != linux/arm64 ]]; then
printf 'Unsupported platform in image specification: %s\n' "$image_spec" >&2
exit 2
fi

printf '\nScanning %s for %s (%s), table output\n' "$label" "$platform" "$image"
if ! "$trivy_bin" image --config "$trivy_config" --platform "$platform" --format table "$image"; then
scan_status=1
fi

printf '\nScanning %s for %s (%s), SARIF output\n' "$label" "$platform" "$image"
platform_suffix="${platform//\//-}"
sarif_output="${report_dir}/${label}-${platform_suffix}.sarif"
if [[ "$trivy_bin" == "trivy.exe" ]] && command -v wslpath >/dev/null 2>&1; then
sarif_output="$(wslpath -w "$sarif_output")"
fi
if ! "$trivy_bin" image --config "$trivy_config" --platform "$platform" --format sarif --output "$sarif_output" "$image"; then
scan_status=1
fi
done

exit "$scan_status"
Loading
Loading