Skip to content

ci: Pin GitHub Actions to immutable commit SHAs #14

ci: Pin GitHub Actions to immutable commit SHAs

ci: Pin GitHub Actions to immutable commit SHAs #14

name: Docker Hardening Checks
on:
push:
branches: [main]
paths:
- "backend/Dockerfile"
- "frontend/Dockerfile"
- ".github/workflows/docker-hardening.yml"
pull_request:
branches: [main]
paths:
- "backend/Dockerfile"
- "frontend/Dockerfile"
- ".github/workflows/docker-hardening.yml"
workflow_dispatch:
jobs:
build:
name: Build ${{ matrix.service }} image
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- service: backend
context: ./backend
dockerfile: ./backend/Dockerfile
image: secuscan-backend
- service: frontend
context: ./frontend
dockerfile: ./frontend/Dockerfile
image: secuscan-frontend
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build ${{ matrix.service }} image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: ${{ matrix.context }}
file: ${{ matrix.dockerfile }}
push: false
load: true
tags: ${{ matrix.image }}:ci
cache-from: type=gha,scope=${{ matrix.service }}
cache-to: type=gha,scope=${{ matrix.service }},mode=max
- name: Save image as tar
run: docker save ${{ matrix.image }}:ci -o /tmp/${{ matrix.image }}.tar
- name: Upload image artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ${{ matrix.image }}-tar
path: /tmp/${{ matrix.image }}.tar
retention-days: 1
hardening-check:
name: Hardening checks – ${{ matrix.service }}
runs-on: ubuntu-latest
needs: build
strategy:
fail-fast: false
matrix:
include:
- service: backend
image: secuscan-backend
- service: frontend
image: secuscan-frontend
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Download image artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: ${{ matrix.image }}-tar
path: /tmp
- name: Load image
run: docker load -i /tmp/${{ matrix.image }}.tar
# Non-root user check
- name: Assert container does NOT run as root
run: |
WHOAMI=$(docker run --rm ${{ matrix.image }}:ci whoami 2>/dev/null || true)
UID_VAL=$(docker run --rm ${{ matrix.image }}:ci id -u 2>/dev/null || echo "0")
echo "Container user: ${WHOAMI} (UID=${UID_VAL})"
if [ "${UID_VAL}" = "0" ]; then
echo "FAIL: ${{ matrix.service }} container runs as root (UID 0)."
echo " Add a non-root USER instruction to the Dockerfile."
exit 1
fi
echo "PASS: running as non-root user '${WHOAMI}' (UID=${UID_VAL})"
# No SUID/SGID binaries
- name: Check for unexpected SUID/SGID binaries
run: |
SUID_FILES=$(docker run --rm --entrypoint find ${{ matrix.image }}:ci \
/ -xdev \( -perm -4000 -o -perm -2000 \) -type f 2>/dev/null || true)
if [ -n "${SUID_FILES}" ]; then
echo "WARNING: SUID/SGID binaries found in ${{ matrix.service }} image:"
echo "${SUID_FILES}"
# Warn but don't fail – some base images include ping/su; document if intentional
else
echo "PASS: No unexpected SUID/SGID binaries."
fi
# No secrets baked into image
- name: Scan image layers for secrets (Trivy secret scanner)
uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0
with:
image-ref: ${{ matrix.image }}:ci
format: table
exit-code: "1"
scanners: secret
severity: CRITICAL,HIGH,MEDIUM
# Dockerfile lint (hadolint)
- name: Lint ${{ matrix.service }} Dockerfile with hadolint
uses: hadolint/hadolint-action@54c9adbab1582c2ef04b2016b760714a4bfde3cf # v3.1.0
with:
dockerfile: ./${{ matrix.service }}/Dockerfile
failure-threshold: error