Skip to content

chore(deps): bump the github-actions group across 1 directory with 10 updates #17

chore(deps): bump the github-actions group across 1 directory with 10 updates

chore(deps): bump the github-actions group across 1 directory with 10 updates #17

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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Build ${{ matrix.service }} image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download image artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
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@ed142fd0673e97e23eac54620cfb913e5ce36c25 # 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@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0
with:
dockerfile: ./${{ matrix.service }}/Dockerfile
failure-threshold: error