From e34d2504e382c5c778eeace13ed569502d4e351f Mon Sep 17 00:00:00 2001 From: abhisheksr01 Date: Sat, 13 Sep 2025 18:15:50 +0100 Subject: [PATCH 1/2] ci(gha): add trivy container image scan --- .github/workflows/pipeline.yml | 49 ++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index e7761ae..5b0ca21 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -154,14 +154,16 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} - name: Enable auto-merge for Dependabot PRs -# if: contains(steps.metadata.outputs.dependency-names, 'my-dependency') && steps.metadata.outputs.update-type == 'version-update:semver-patch' + if: contains(steps.metadata.outputs.dependency-names, 'my-dependency') && steps.metadata.outputs.update-type == 'version-update:semver-patch' run: gh pr merge --auto --merge "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GH_TOKEN: ${{secrets.GITHUB_TOKEN}} - docker-build-push: + docker-build-scan-push: if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest + env: + BASE_IMAGE: abhisheksr01/companieshouse needs: - unit-test - mutation-test @@ -182,13 +184,13 @@ jobs: with: dry-run: true # Since we are setting dryrun argument the bump-version will always be available until 'current-version' is pushed as release - name: check-bump-version-output + shell: bash run: | echo "previous-version: ${{ steps.bump-version.outputs.previous-version }}" echo "bump-version: ${{ steps.bump-version.outputs.bump-version }}" echo "current-version: ${{ steps.bump-version.outputs.current-version }}" echo "is-version-bumped: ${{ steps.bump-version.outputs.is-version-bumped }}" echo "is-dryrun-version-bumped: ${{ steps.bump-version.outputs.is-dryrun-version-bumped }}" - shell: bash - name: Login to Docker Hub uses: docker/login-action@v3 with: @@ -201,7 +203,7 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: abhisheksr01/companieshouse + images: ${{ env.BASE_IMAGE }} context: git tags: | type=ref,event=pr @@ -212,25 +214,56 @@ jobs: "org.opencontainers.image.url": "https://github.com/abhisheksr01/spring-boot-microservice-best-practices", "org.opencontainers.image.source": "https://github.com/abhisheksr01/spring-boot-microservice-best-practices", "org.opencontainers.image.version": ${{ steps.bump-version.outputs.bump-version }}, - "org.opencontainers.image.created": "2020-01-10T00:30:00.000Z", + "org.opencontainers.image.created": "$(date +"%Y%m%d%H%M%S")", "org.opencontainers.image.revision": ${{ github.sha }}, "org.opencontainers.image.licenses": "MIT" - - name: Build and push + - name: Build Image if: ${{ steps.bump-version.outputs.is-dryrun-version-bumped == 'true' }} uses: docker/build-push-action@v6 with: - push: ${{ github.event_name != 'pull_request' && steps.bump-version.outputs.is-dryrun-version-bumped == 'true' }} # Only push on main branch & when version is bumped with dryrun. We will create tags and creates separately after proper testing + load: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-to: type=registry,ref=${{ env.BASE_IMAGE }}:cache + cache-from: type=registry,ref=${{ env.BASE_IMAGE }}:cache,mode=max + - name: Scan Image + uses: aquasecurity/trivy-action@0.33.1 + with: + versin: 0.66.0 + image-ref: ${{ steps.meta.outputs.tags }} + format: 'table' + exit-code: '1' + ignore-unfixed: true + vuln-type: 'os,library' + scanners: 'vuln,secret,misconfig' + - name: Validate Container Image + run: | + docker run -d -p 8080:8080 ${{ steps.meta.outputs.tags }} + sleep 5 # Wait for container to start + HEALTH_STATUS=$(curl -s http://localhost:8080/companieshouse/actuator/health | jq -r '.status') + if [ "$HEALTH_STATUS" != "UP" ]; then + echo "Health check failed. Status: $HEALTH_STATUS" + exit 1 + fi + echo "Health check passed. Status: $HEALTH_STATUS" + - name: Re-Build & Push Image + uses: docker/build-push-action@v6 + with: + push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + cache-to: type=registry,ref=${{ env.BASE_IMAGE }}:cache + cache-from: type=registry,ref=${{ env.BASE_IMAGE }}:cache,mode=max sbom: true provenance: true + create-release: if: ${{ needs.docker-build-push.outputs.is-dryrun-version-bumped == 'true' }} # Only release when new version is available runs-on: ubuntu-latest permissions: contents: write # to be able to publish a GitHub release needs: - - docker-build-push + - docker-build-scan-push environment: name: approve-release # Manual Approval to decide if we are ready to push tags and release steps: From 81f92b7d1c127f9fa9aece3b27bf4e36bd793f75 Mon Sep 17 00:00:00 2001 From: abhisheksr01 Date: Sat, 13 Sep 2025 18:16:04 +0100 Subject: [PATCH 2/2] build(gradle): bump gradlew and Dockerfile to fix vulnerabilities --- Dockerfile | 56 ++++++++++++++++++------ gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index d1c5d66..e013e4a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,55 @@ # Stage 1: Build the jar -FROM gradle:8.12-jdk21 AS build -# Copy source code into the container and set the ownership to 'gradle' user +FROM gradle:8.14.3-jdk21-jammy AS build + +# Update system packages +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Copy source code and build COPY --chown=gradle:gradle . /home/gradle/src WORKDIR /home/gradle/src RUN gradle build -x test --no-daemon # Stage 2: Production image -FROM openjdk:21-slim AS production +FROM openjdk:21-slim-bookworm AS production EXPOSE 8080 -# Create a non-root user and group (using 'appuser' as an example) -RUN groupadd -r appgroup && useradd -r -g appgroup -m appuser +# Update system packages and install fixed versions +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y --no-install-recommends \ + libc6 \ + util-linux \ + && apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Create non-root user with fixed UID/GID +RUN groupadd -r appgroup -g 10001 && \ + useradd -r -g appgroup -u 10001 appuser && \ + mkdir /app && \ + chown 10001:10001 /app + +# Copy jar with specific name +COPY --from=build --chown=10001:10001 /home/gradle/src/build/libs/*.jar /app/companieshouse.jar -# Create the /app directory and set permissions -RUN mkdir /app && chown appuser:appgroup /app +WORKDIR /app +USER 10001 -# Copy the jar file from the build stage into the production image -COPY --from=build /home/gradle/src/build/libs/*.jar /app/companieshouse-*.jar +# Security-focused Java options +ENV JAVA_OPTS="-Djava.security.egd=file:/dev/./urandom \ + -Djava.awt.headless=true \ + -Dfile.encoding=UTF-8 \ + -XX:+ExitOnOutOfMemoryError \ + -XX:+UseContainerSupport \ + -XX:MaxRAMPercentage=75.0 \ + -Dspring.profiles.active=production \ + -Dserver.tomcat.accesslog.enabled=true" -# Change to non-root user -USER appuser +# Add healthcheck +HEALTHCHECK --interval=30s --timeout=3s \ + CMD curl -f http://localhost:8080/companieshouse/actuator/health || exit 1 -# Set the entrypoint to run the Java application -ENTRYPOINT ["java", "-jar", "/app/companieshouse-*.jar"] +# Use specific jar name in entrypoint +ENTRYPOINT ["java", "-jar", "/app/companieshouse.jar"] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d6e308a..3ae1e2f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists