-
Notifications
You must be signed in to change notification settings - Fork 25
Add manual workflows for Docker build and RC promotion #890
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
32f2d53
b7f6970
020da8d
35df28e
b8f4948
f7d7bf3
0ebebe1
6eeb08a
a3bcc4a
bc9ff33
f78daa7
3c9ed3d
d9066aa
68fcbfa
200f719
b7bc66c
7a6321d
6203236
f942210
d143e13
8a61870
b8d8a3a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,134 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: Build, Tag, and Push Docker Image | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workflow_dispatch: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inputs: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tag_version: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: 'Docker image tag version (e.g., 3.3.11)' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| required: true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| branch: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: 'Branch to build from (default: staging)' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| required: false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: 'staging' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DOCKER_IMAGE_NAME: elevate-user | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DOCKER_REGISTRY: docker.io | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DOCKER_NAMESPACE: shikshalokamqa | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| contents: write | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| docker-image-build-and-push: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Checkout code from target branch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ref: ${{ github.event.inputs.branch || 'staging' }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+27
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win Don't persist the workflow token in the build context.
Suggested change - name: Checkout code from target branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch || 'staging' }}
+ persist-credentials: false
...
- name: Create and push Git tag
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
run: |
VERSION="${{ steps.get-version.outputs.version }}"
git config user.name "github-actions"
git config user.email "github-actions@github.com"
git tag -a "$VERSION" -m "Release $VERSION"
- git push origin "$VERSION"
+ git push "https://x-access-token:${GITHUB_TOKEN}`@github.com/`${GITHUB_REPOSITORY}.git" "$VERSION"Also applies to: 109-120, 122-128 🧰 Tools🪛 zizmor (1.26.1)[warning] 27-30: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false (artipacked) [error] 28-28: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Set up Docker Buildx | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uses: docker/setup-buildx-action@v3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Login to Docker Hub | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uses: docker/login-action@v3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+28
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Pin every third-party action to a full commit SHA. These Also applies to: 103-111 🧰 Tools🪛 zizmor (1.26.1)[error] 28-28: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) [error] 33-33: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) [error] 36-36: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| username: ${{ secrets.DOCKERHUB_USERNAME }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| password: ${{ secrets.DOCKERHUB_TOKEN }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Get Docker tag version | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: get-version | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shell: bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VERSION="${{ github.event.inputs.tag_version }}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Strip leading "v" if present | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VERSION="${VERSION#v}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Enforce x.y.z or x.y.z-rc.1 format | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Error: tag_version must look like 3.4.0 or 3.4.0-rc.1 (no other suffixes allowed)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| printf 'version=%s\n' "$VERSION" >> "$GITHUB_OUTPUT" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+41
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Move Line 45 expands a user-controlled workflow input directly into bash, so the injection happens before your regex validation. Read the input from an environment variable instead of embedding Suggested change - name: Get Docker tag version
id: get-version
shell: bash
+ env:
+ INPUT_TAG_VERSION: ${{ github.event.inputs.tag_version }}
run: |
- VERSION="${{ github.event.inputs.tag_version }}"
+ VERSION="$INPUT_TAG_VERSION"
# Strip leading "v" if present
VERSION="${VERSION#v}"📝 Committable suggestion
Suggested change
🧰 Tools🪛 zizmor (1.26.1)[error] 45-45: code injection via template expansion (template-injection): may expand into attacker-controllable code (template-injection) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Check if Git tag already exists | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VERSION="${{ steps.get-version.outputs.version }}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| git fetch --tags | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if git rev-parse "$VERSION" >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Error: Git tag $VERSION already exists" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Check if version exists on Docker Hub | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VERSION="${{ steps.get-version.outputs.version }}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LOGIN_RESPONSE=$(curl -s -w "\n%{http_code}" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -X POST \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -H "Content-Type: application/json" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -d '{"username": "${{ secrets.DOCKERHUB_USERNAME }}", "password": "${{ secrets.DOCKERHUB_TOKEN }}"}' \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "https://hub.docker.com/v2/users/login") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+64
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Build the Docker Hub login JSON safely. This payload is assembled with raw string interpolation, so a valid Docker Hub username/token containing JSON-significant characters can make the login request malformed. 🧰 Tools🪛 zizmor (1.26.1)[info] 66-66: code injection via template expansion (template-injection): may expand into attacker-controllable code (template-injection) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LOGIN_HTTP=$(echo "$LOGIN_RESPONSE" | tail -n1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LOGIN_BODY=$(echo "$LOGIN_RESPONSE" | head -n-1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$LOGIN_HTTP" -ne 200 ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Error: Docker Hub login failed with HTTP $LOGIN_HTTP" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TOKEN=$(echo "$LOGIN_BODY" | jq -r .token) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Error: Docker Hub login succeeded but returned no token" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -H "Authorization: Bearer $TOKEN" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "https://hub.docker.com/v2/namespaces/${{ env.DOCKER_NAMESPACE }}/repositories/${{ env.DOCKER_IMAGE_NAME }}/tags/$VERSION") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$RESPONSE" -eq 200 ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Error: Tag $VERSION already exists on Docker Hub" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif [ "$RESPONSE" -eq 404 ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Tag $VERSION not found on Docker Hub — safe to push" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Error: Unexpected HTTP $RESPONSE from Docker Hub tag check; aborting to fail safe" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Extract metadata | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: meta | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uses: docker/metadata-action@v5 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_NAMESPACE }}/${{ env.DOCKER_IMAGE_NAME }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tags: | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type=raw,value=${{ steps.get-version.outputs.version }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Build and push Docker image | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: build | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uses: docker/build-push-action@v5 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context: . | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| file: ./Dockerfile | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| push: true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tags: ${{ steps.meta.outputs.tags }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| labels: ${{ steps.meta.outputs.labels }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| platforms: linux/amd64,linux/arm64 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cache-from: type=gha | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cache-to: type=gha,mode=max | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Create and push Git tag | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VERSION="${{ steps.get-version.outputs.version }}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| git config user.name "github-actions" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| git config user.email "github-actions@github.com" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| git tag -a "$VERSION" -m "Release $VERSION" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| git push origin "$VERSION" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Job summary | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "### Docker Image Published 🚀" >> $GITHUB_STEP_SUMMARY | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "**Tag:** ${{ steps.get-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "**Digest:** ${{ steps.build.outputs.digest }}" >> $GITHUB_STEP_SUMMARY | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,162 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| name: Promote RC to Production Release | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| workflow_dispatch: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| inputs: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| rc_tag: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| description: "Approved RC tag (example: 3.4.0-rc.2)" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| required: true | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| DOCKER_IMAGE_NAME: elevate-user | ||||||||||||||||||||||||||||||||||||||||||||||||||
| DOCKER_REGISTRY: docker.io | ||||||||||||||||||||||||||||||||||||||||||||||||||
| DOCKER_NAMESPACE: shikshalokamqa | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| contents: write | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| concurrency: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| group: promote | ||||||||||||||||||||||||||||||||||||||||||||||||||
| cancel-in-progress: false | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| promote: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Validate RC tag | ||||||||||||||||||||||||||||||||||||||||||||||||||
| id: version | ||||||||||||||||||||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RC_TAG: ${{ github.event.inputs.rc_tag }} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if ! [[ "$RC_TAG" =~ ^([0-9]+\.[0-9]+\.[0-9]+)-rc\.[0-9]+$ ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Invalid rc_tag format. Must look like 3.4.0-rc.2" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RELEASE_TAG=$(echo "$RC_TAG" | sed 's/-rc\.[0-9]*//') | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "rc=$RC_TAG" >> $GITHUB_OUTPUT | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "release=$RELEASE_TAG" >> $GITHUB_OUTPUT | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Checkout repository | ||||||||||||||||||||||||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
for ref in \
"actions/checkout v4" \
"docker/login-action v3" \
"docker/setup-buildx-action v3"
do
repo="${ref% *}"
tag="${ref#* }"
echo "== ${repo}@${tag} =="
git ls-remote --tags "https://github.com/${repo}.git" "refs/tags/${tag}" "refs/tags/${tag}^{}"
doneRepository: ELEVATE-Project/user Length of output: 411 Pin release workflow Actions to immutable SHAs. This workflow holds
Current Action References - uses: actions/checkout@v4
- uses: docker/login-action@v3
- uses: docker/setup-buildx-action@v3🧰 Tools🪛 zizmor (1.26.1)[error] 41-41: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fetch-depth: 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+40
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Avoid persisting the write token across all later steps.
🔒 Proposed credential scoping change - name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
+ persist-credentials: false
...
- name: Create production Git tag
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
run: |
VERSION="${{ steps.version.outputs.release }}"
git config user.name "github-actions"
git config user.email "github-actions@github.com"
git fetch --tags
if git rev-parse "$VERSION" >/dev/null 2>&1; then
echo "Error: Git tag $VERSION already exists"
exit 1
fi
git tag -a "$VERSION" "${{ steps.verify.outputs.rc_commit }}" -m "Release $VERSION"
- git push origin "$VERSION"
+ git -c http.https://github.com/.extraheader="AUTHORIZATION: bearer $GITHUB_TOKEN" push origin "$VERSION"Also applies to: 131-142 🧰 Tools🪛 zizmor (1.26.1)[warning] 40-43: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false (artipacked) [error] 41-41: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Verify RC commit exists in master | ||||||||||||||||||||||||||||||||||||||||||||||||||
| id: verify | ||||||||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RC_TAG="${{ steps.version.outputs.rc }}" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| git fetch origin master --tags | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RC_COMMIT=$(git rev-list -n 1 "$RC_TAG") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if git merge-base --is-ancestor "$RC_COMMIT" origin/master; then | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "RC commit is present in master — safe to promote." | ||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Error: RC tag commit is NOT in master." | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Merge staging → master before promoting." | ||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "rc_commit=$RC_COMMIT" >> $GITHUB_OUTPUT | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Login to Docker Hub | ||||||||||||||||||||||||||||||||||||||||||||||||||
| uses: docker/login-action@v3 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| username: ${{ secrets.DOCKERHUB_USERNAME }} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| password: ${{ secrets.DOCKERHUB_TOKEN }} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Check RC image exists and production tag is absent on Docker Hub | ||||||||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RC_TAG="${{ steps.version.outputs.rc }}" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RELEASE_TAG="${{ steps.version.outputs.release }}" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # FIX 1: Build JSON safely with jq --arg to avoid special-character injection | ||||||||||||||||||||||||||||||||||||||||||||||||||
| LOGIN_RESPONSE=$(jq -n \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| --arg username "${{ secrets.DOCKERHUB_USERNAME }}" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| --arg password "${{ secrets.DOCKERHUB_TOKEN }}" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| '{"username": $username, "password": $password}' \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| | curl -s -w "\n%{http_code}" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| -X POST \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| -H "Content-Type: application/json" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| -d @- \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "https://hub.docker.com/v2/users/login") | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+71
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Pass Docker Hub secrets through
🔐 Proposed safer secret handling - name: Check RC image exists and production tag is absent on Docker Hub
+ env:
+ DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
+ DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
...
LOGIN_RESPONSE=$(jq -n \
- --arg username "${{ secrets.DOCKERHUB_USERNAME }}" \
- --arg password "${{ secrets.DOCKERHUB_TOKEN }}" \
+ --arg username "$DOCKERHUB_USERNAME" \
+ --arg password "$DOCKERHUB_TOKEN" \
'{"username": $username, "password": $password}' \📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
| LOGIN_HTTP=$(echo "$LOGIN_RESPONSE" | tail -n1) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| LOGIN_BODY=$(echo "$LOGIN_RESPONSE" | head -n-1) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$LOGIN_HTTP" -ne 200 ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Error: Docker Hub login failed with HTTP $LOGIN_HTTP" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| TOKEN=$(echo "$LOGIN_BODY" | jq -r .token) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Error: Docker Hub login succeeded but returned no token" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Verify RC image exists | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RC_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| -H "Authorization: Bearer $TOKEN" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "https://hub.docker.com/v2/namespaces/${{ env.DOCKER_NAMESPACE }}/repositories/${{ env.DOCKER_IMAGE_NAME }}/tags/$RC_TAG") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$RC_RESPONSE" -eq 404 ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Error: RC image $RC_TAG not found on Docker Hub" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| elif [ "$RC_RESPONSE" -ne 200 ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Error: Unexpected HTTP $RC_RESPONSE checking RC image — aborting to fail safe" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "RC image $RC_TAG confirmed on Docker Hub." | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Verify production tag does not already exist | ||||||||||||||||||||||||||||||||||||||||||||||||||
| PROD_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| -H "Authorization: Bearer $TOKEN" \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "https://hub.docker.com/v2/namespaces/${{ env.DOCKER_NAMESPACE }}/repositories/${{ env.DOCKER_IMAGE_NAME }}/tags/$RELEASE_TAG") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if [ "$PROD_RESPONSE" -eq 200 ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Error: Production tag $RELEASE_TAG already exists on Docker Hub" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| elif [ "$PROD_RESPONSE" -ne 404 ]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Error: Unexpected HTTP $PROD_RESPONSE checking production tag — aborting to fail safe" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Production tag $RELEASE_TAG is absent — safe to promote." | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # FIX 2: Replace pull/tag/push with imagetools create to preserve multi-arch manifest | ||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Set up Docker Buildx | ||||||||||||||||||||||||||||||||||||||||||||||||||
| uses: docker/setup-buildx-action@v3 | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Retag RC image as production (multi-arch) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||||||||
| docker buildx imagetools create \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| --tag ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_NAMESPACE }}/${{ env.DOCKER_IMAGE_NAME }}:${{ steps.version.outputs.release }} \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_NAMESPACE }}/${{ env.DOCKER_IMAGE_NAME }}:${{ steps.version.outputs.rc }} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Create production Git tag | ||||||||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||||||||
| VERSION="${{ steps.version.outputs.release }}" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| git config user.name "github-actions" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| git config user.email "github-actions@github.com" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| git fetch --tags | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if git rev-parse "$VERSION" >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "Error: Git tag $VERSION already exists" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||
| git tag -a "$VERSION" "${{ steps.verify.outputs.rc_commit }}" -m "Release $VERSION" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| git push origin "$VERSION" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+125
to
+142
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Check the Git production tag before mutating Docker Hub. The workflow creates the production Docker tag before checking whether the Git tag already exists. If Line 137 fails, Docker Hub has already been changed and reruns will then fail at the Docker tag gate. Move the Git tag absence check before 🧰 Tools🪛 zizmor (1.26.1)[info] 128-128: code injection via template expansion (template-injection): may expand into attacker-controllable code (template-injection) [info] 129-129: code injection via template expansion (template-injection): may expand into attacker-controllable code (template-injection) [info] 133-133: code injection via template expansion (template-injection): may expand into attacker-controllable code (template-injection) [info] 141-141: code injection via template expansion (template-injection): may expand into attacker-controllable code (template-injection) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Job summary | ||||||||||||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||||||||||||
| DIGEST=$(docker buildx imagetools inspect \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_NAMESPACE }}/${{ env.DOCKER_IMAGE_NAME }}:${{ steps.version.outputs.release }} \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| --format '{{json .Manifest}}' \ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| | jq -r '.digest') | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "### Production Promotion Complete 🚀" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "| Field | Value |" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "| --- | --- |" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "| **RC tag** | \`${{ steps.version.outputs.rc }}\` |" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "| **Release tag** | \`${{ steps.version.outputs.release }}\` |" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "| **Validated commit** | \`${{ steps.verify.outputs.rc_commit }}\` |" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "| **Docker image** | \`${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_NAMESPACE }}/${{ env.DOCKER_IMAGE_NAME }}:${{ steps.version.outputs.release }}\` |" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "| **Digest** | \`$DIGEST\` |" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "| **Triggered by** | @${{ github.actor }} |" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| echo "| **Run** | [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) |" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } >> $GITHUB_STEP_SUMMARY | ||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Serialize runs per release tag.
The “tag absent” checks and the later push/tag steps are not atomic. Two dispatches for the same
tag_versioncan both pass validation, and the losing run can still overwrite the Docker tag before its Git tag push fails. Add aconcurrencygroup keyed by the normalized version so only one publish for a given tag can run at a time.Also applies to: 55-128
🤖 Prompt for AI Agents