Skip to content

Container Security Scan #8

Container Security Scan

Container Security Scan #8

name: Container Security Scan
on:
schedule:
# Run nightly security scans at 2 AM UTC
- cron: '0 2 * * *'
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
security-scan:
name: Container Security Analysis
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
security-events: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
# 1. DOCKERFILE SECURITY
- name: Run Hadolint (Dockerfile Linter)
uses: hadolint/[email protected]
with:
dockerfile: Dockerfile
format: sarif
output-file: hadolint-results.sarif
no-color: true
failure-threshold: error
- name: Upload Hadolint results to GitHub Security
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: hadolint-results.sarif
# 2. BUILD IMAGE ONCE
- name: Build container image for security testing
id: build
uses: docker/build-push-action@v6
with:
context: .
load: true
tags: ${{ env.IMAGE_NAME }}:security-test
cache-from: type=gha
cache-to: type=gha,mode=max
# 3. VULNERABILITY SCANNING (Trivy only - more comprehensive than Grype)
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.IMAGE_NAME }}:security-test
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results to GitHub Security
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: 'trivy-results.sarif'
- name: Run Trivy for high/critical vulnerabilities (fail build)
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.IMAGE_NAME }}:security-test
format: 'table'
severity: 'HIGH,CRITICAL'
exit-code: '1'
# 4. CONTAINER STRUCTURE TESTS
- name: Install container-structure-test
run: |
curl -LO https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64
chmod +x container-structure-test-linux-amd64
sudo mv container-structure-test-linux-amd64 /usr/local/bin/container-structure-test
- name: Create structure test config
run: |
cat > container-structure-test.yaml << EOF
schemaVersion: 2.0.0
commandTests:
- name: "Check binary exists and is executable"
command: "ls"
args: ["-la", "/app/Honua.Server"]
expectedOutput: [".*-rwxr-xr-x.*Honua.Server"]
- name: "Check non-root user"
command: "whoami"
expectedOutput: ["honua"]
- name: "Check required directories exist"
command: "ls"
args: ["-la", "/tmp"]
expectedOutput: [".*honua-logs.*", ".*honua-cache.*"]
fileExistenceTests:
- name: 'Application binary'
path: '/app/Honua.Server'
shouldExist: true
permissions: '-rwxr-xr-x'
- name: 'Log directory'
path: '/tmp/honua-logs'
shouldExist: true
isDirectory: true
- name: 'Cache directory'
path: '/tmp/honua-cache'
shouldExist: true
isDirectory: true
metadataTest:
exposedPorts: ["8080"]
user: "honua"
workdir: "/app"
EOF
- name: Run container structure tests
run: |
container-structure-test test \
--image ${{ env.IMAGE_NAME }}:security-test \
--config container-structure-test.yaml
# 5. RUNTIME SECURITY TESTING
- name: Start container for runtime security analysis
run: |
# Start container in detached mode with security constraints
docker run -d \
--name honua-runtime-test \
--read-only \
--cap-drop ALL \
--security-opt no-new-privileges:true \
-p 8080:8080 \
${{ env.IMAGE_NAME }}:security-test
# Wait for container to start
sleep 10
- name: Test runtime security constraints
run: |
echo "πŸ”’ Testing runtime security constraints..."
# Test read-only filesystem
if docker exec honua-runtime-test touch /test-write 2>/dev/null; then
echo "❌ Container filesystem is writable (should be read-only)"
exit 1
else
echo "βœ… Container filesystem is read-only"
fi
# Test user
user=$(docker exec honua-runtime-test whoami)
if [[ "$user" != "honua" ]]; then
echo "❌ Container not running as expected user: $user"
exit 1
else
echo "βœ… Container running as non-root user: $user"
fi
# Test application health
if curl -f http://localhost:8080/healthz/live; then
echo "βœ… Application liveness check passed"
else
echo "❌ Application liveness check failed"
exit 1
fi
- name: Cleanup runtime test
if: always()
run: |
docker stop honua-runtime-test || true
docker rm honua-runtime-test || true
# 6. GENERATE SECURITY SUMMARY
- name: Generate security summary
if: always()
run: |
echo "# πŸ”’ Container Security Report" > security-report.md
echo "" >> security-report.md
echo "**Scan Date**: $(date)" >> security-report.md
echo "**Commit**: ${{ github.sha }}" >> security-report.md
echo "**Image**: ${{ env.IMAGE_NAME }}:security-test" >> security-report.md
echo "" >> security-report.md
echo "## πŸ›‘οΈ Security Checks Completed" >> security-report.md
echo "" >> security-report.md
echo "βœ… Dockerfile Security Analysis (Hadolint)" >> security-report.md
echo "βœ… Vulnerability Scanning (Trivy)" >> security-report.md
echo "βœ… Container Structure Tests" >> security-report.md
echo "βœ… Runtime Security Analysis" >> security-report.md
echo "" >> security-report.md
echo "## πŸ”§ Security Features Validated" >> security-report.md
echo "" >> security-report.md
echo "- Non-root user execution" >> security-report.md
echo "- Read-only filesystem" >> security-report.md
echo "- Minimal Linux capabilities" >> security-report.md
echo "- No privilege escalation" >> security-report.md
echo "- Secure application startup" >> security-report.md
- name: Upload security report
uses: actions/upload-artifact@v4
if: always()
with:
name: container-security-report
path: security-report.md
retention-days: 30