ci: add cosign image signing with Vault Transit #29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| pull_request: | |
| branches: [main] | |
| push: | |
| branches: [main] | |
| tags: ['v*'] | |
| workflow_dispatch: | |
| env: | |
| # Registries | |
| GHCR_REGISTRY: ghcr.io | |
| HARBOR_REGISTRY: ps-registry.pack-test.net | |
| # Image names | |
| HARBOR_IMAGE: suite-epargne/se-api | |
| GHCR_IMAGE: pack-solutions/se-api | |
| JAVA_VERSION: '21' | |
| jobs: | |
| # ============================================================================== | |
| # Job 1: Tests (runs on PRs and pushes) | |
| # ============================================================================== | |
| test: | |
| name: Tests | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup JDK 21 | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: 'temurin' | |
| java-version: ${{ env.JAVA_VERSION }} | |
| cache: 'gradle' | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v3 | |
| with: | |
| gradle-version: wrapper | |
| - name: Run Unit Tests | |
| run: ./gradlew test --no-daemon --parallel | |
| - name: Run Integration Tests | |
| run: ./gradlew integrationTest --no-daemon --parallel | |
| - name: Publish Test Results | |
| if: always() | |
| uses: EnricoMi/publish-unit-test-result-action@v2 | |
| with: | |
| files: | | |
| **/build/test-results/test/TEST-*.xml | |
| **/build/test-results/integrationTest/TEST-*.xml | |
| check_name: 'Test Results' | |
| # ============================================================================== | |
| # Job 2: Build and Push Docker Image (only on push to main or tags) | |
| # ============================================================================== | |
| build: | |
| name: Build & Push Image | |
| runs-on: ubuntu-latest | |
| needs: [test] | |
| if: github.event_name == 'push' | |
| permissions: | |
| contents: read | |
| packages: write | |
| outputs: | |
| harbor_tag: ${{ steps.meta.outputs.harbor_primary_tag }} | |
| ghcr_tag: ${{ steps.meta.outputs.ghcr_tag }} | |
| build_type: ${{ steps.meta.outputs.build_type }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Generate image metadata | |
| id: meta | |
| run: | | |
| SHA_SHORT="${GITHUB_SHA::7}" | |
| TIMESTAMP=$(date -u +%Y%m%d) | |
| # Extract version from gradle.properties for GHCR | |
| GHCR_VERSION=$(grep '^version=' gradle.properties | cut -d'=' -f2) | |
| if [[ "$GITHUB_REF" == refs/tags/v* ]]; then | |
| # Release tag - use semver for Harbor | |
| SEMVER="${GITHUB_REF#refs/tags/v}" | |
| HARBOR_PRIMARY_TAG="${SEMVER}" | |
| HARBOR_TAGS="${SEMVER},${SHA_SHORT}" | |
| BUILD_TYPE="release" | |
| else | |
| # Push to main - use timestamp-sha for Harbor | |
| HARBOR_PRIMARY_TAG="${TIMESTAMP}-${SHA_SHORT}" | |
| HARBOR_TAGS="${TIMESTAMP}-${SHA_SHORT},${SHA_SHORT}" | |
| BUILD_TYPE="dev" | |
| fi | |
| echo "harbor_primary_tag=${HARBOR_PRIMARY_TAG}" >> $GITHUB_OUTPUT | |
| echo "harbor_tags=${HARBOR_TAGS}" >> $GITHUB_OUTPUT | |
| echo "ghcr_tag=${GHCR_VERSION}" >> $GITHUB_OUTPUT | |
| echo "sha_short=${SHA_SHORT}" >> $GITHUB_OUTPUT | |
| echo "build_type=${BUILD_TYPE}" >> $GITHUB_OUTPUT | |
| echo "## Image Tags" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Build Type:** ${BUILD_TYPE}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Harbor Primary:** ${HARBOR_PRIMARY_TAG}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **GHCR:** ${GHCR_VERSION}" >> $GITHUB_STEP_SUMMARY | |
| - name: Setup JDK 21 | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: 'temurin' | |
| java-version: ${{ env.JAVA_VERSION }} | |
| cache: 'gradle' | |
| - name: Build application JAR | |
| run: ./gradlew assembly:buildFatJar --no-daemon --parallel -x test | |
| # GHCR login disabled - Harbor only for now | |
| # - name: Log in to GHCR | |
| # id: ghcr_login | |
| # continue-on-error: true | |
| # run: | | |
| # echo "${{ secrets.GHCR_PAT }}" | docker login ${{ env.GHCR_REGISTRY }} -u "${{ github.actor }}" --password-stdin | |
| - name: Build Docker image | |
| run: | | |
| docker build \ | |
| --build-arg APP_VERSION=${{ steps.meta.outputs.harbor_primary_tag }} \ | |
| --build-arg BUILD_DATE=${{ github.event.head_commit.timestamp }} \ | |
| --build-arg VCS_REF=${{ github.sha }} \ | |
| -t ${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_IMAGE }}:${{ steps.meta.outputs.harbor_primary_tag }} \ | |
| -t ${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_IMAGE }}:${{ steps.meta.outputs.sha_short }} \ | |
| -f ./Dockerfile . | |
| - name: Install skopeo and cosign | |
| run: | | |
| sudo apt-get update && sudo apt-get install -y skopeo | |
| # Install cosign | |
| COSIGN_VERSION="v2.4.1" | |
| curl -sLO "https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64" | |
| chmod +x cosign-linux-amd64 | |
| sudo mv cosign-linux-amd64 /usr/local/bin/cosign | |
| cosign version | |
| - name: Push to Harbor with skopeo | |
| run: | | |
| # Push primary tag (direct daemon→registry, no tarball) | |
| skopeo copy --dest-creds "${{ secrets.HARBOR_USERNAME }}:${{ secrets.HARBOR_PASSWORD }}" \ | |
| docker-daemon:${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_IMAGE }}:${{ steps.meta.outputs.harbor_primary_tag }} \ | |
| docker://${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_IMAGE }}:${{ steps.meta.outputs.harbor_primary_tag }} | |
| # Push sha short tag | |
| skopeo copy --dest-creds "${{ secrets.HARBOR_USERNAME }}:${{ secrets.HARBOR_PASSWORD }}" \ | |
| docker-daemon:${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_IMAGE }}:${{ steps.meta.outputs.sha_short }} \ | |
| docker://${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_IMAGE }}:${{ steps.meta.outputs.sha_short }} | |
| # GHCR push disabled - Harbor only for now | |
| # - name: Tag and push to GHCR (backup) | |
| # if: steps.ghcr_login.outcome == 'success' | |
| # run: | | |
| # docker tag ${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_IMAGE }}:${{ steps.meta.outputs.harbor_primary_tag }} \ | |
| # ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE }}:${{ steps.meta.outputs.ghcr_tag }} | |
| # docker push ${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE }}:${{ steps.meta.outputs.ghcr_tag }} | |
| - name: Get image digest | |
| id: digest | |
| run: | | |
| DIGEST=$(skopeo inspect --creds "${{ secrets.HARBOR_USERNAME }}:${{ secrets.HARBOR_PASSWORD }}" \ | |
| docker://${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_IMAGE }}:${{ steps.meta.outputs.harbor_primary_tag }} \ | |
| | jq -r '.Digest') | |
| echo "digest=${DIGEST}" >> $GITHUB_OUTPUT | |
| echo "Image digest: ${DIGEST}" | |
| - name: Authenticate to Vault | |
| uses: hashicorp/vault-action@v3 | |
| with: | |
| url: ${{ secrets.VAULT_ADDR }} | |
| method: approle | |
| roleId: ${{ secrets.VAULT_ROLE_ID }} | |
| secretId: ${{ secrets.VAULT_SECRET_ID }} | |
| exportToken: true | |
| - name: Sign image with cosign | |
| env: | |
| VAULT_ADDR: ${{ secrets.VAULT_ADDR }} | |
| VAULT_TOKEN: ${{ env.VAULT_TOKEN }} | |
| TRANSIT_SECRET_ENGINE_PATH: operator-transit | |
| run: | | |
| IMAGE_REF="${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_IMAGE }}@${{ steps.digest.outputs.digest }}" | |
| echo "Signing image: ${IMAGE_REF}" | |
| cosign sign --yes \ | |
| --key hashivault://cosign-key \ | |
| --registry-username "${{ secrets.HARBOR_USERNAME }}" \ | |
| --registry-password "${{ secrets.HARBOR_PASSWORD }}" \ | |
| "${IMAGE_REF}" | |
| echo "Image signed successfully" | |
| - name: Summary | |
| run: | | |
| echo "## Build Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Harbor (Primary)" >> $GITHUB_STEP_SUMMARY | |
| echo "- \`${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_IMAGE }}:${{ steps.meta.outputs.harbor_primary_tag }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "- \`${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_IMAGE }}:${{ steps.meta.outputs.sha_short }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Signing" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Digest:** \`${{ steps.digest.outputs.digest }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Signed with:** Vault Transit (cosign-key)" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY |