Skip to content

ci: publish log4j-chain images to ghcr (replace ttl.sh)#139

Merged
entlein merged 1 commit into
mainfrom
ci/log4j-chain-images
Jun 9, 2026
Merged

ci: publish log4j-chain images to ghcr (replace ttl.sh)#139
entlein merged 1 commit into
mainfrom
ci/log4j-chain-images

Conversation

@entlein

@entlein entlein commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

The four log4j-chain demo images
(backend-{vulnerable,contained,patched} + attacker) currently rot on ttl.sh after 24h. That keeps breaking fresh-PG provisioning when the makefile / cluster-deploy pulls them; the manifests in example/log4j-chain/{backend-b,backend-c,log4j-chain,log4j-attacks}.yaml already reference ghcr.io/k8sstormcenter/log4j-chain-:latest, they just had nothing publishing them there.

This workflow mirrors ci-chain-images.yaml shape: matrix per component, SHA + branch + latest tags, pinned GitHub Actions SHAs per repo policy. Triggers only on changes under example/log4j-chain/backend/ or example/log4j-chain/attacker/ + the workflow file itself.

amd64-only — the demo cluster is amd64 and the maven builds + JDK base images take significantly longer multi-arch. arm64 can be added if a consumer ever needs it.

No smoke-test step: the existing chain workflow's /healthz check doesn't generalise to the attacker (marshalsec LDAP + python HTTP serving Payload.class — no health endpoint). Push success is the green signal; the demo runbooks exercise the images end-to-end.

Flagged by dx-agent in k8sstormcenter/pixie#47 — needed for the #7 pgsql re-test and the M6 combined run.

The four log4j-chain demo images
(backend-{vulnerable,contained,patched} + attacker) currently rot on
ttl.sh after 24h. That keeps breaking fresh-PG provisioning when the
makefile / cluster-deploy pulls them; the manifests in
example/log4j-chain/{backend-b,backend-c,log4j-chain,log4j-attacks}.yaml
already reference ghcr.io/k8sstormcenter/log4j-chain-<component>:latest,
they just had nothing publishing them there.

This workflow mirrors ci-chain-images.yaml shape: matrix per component,
SHA + branch + latest tags, pinned GitHub Actions SHAs per repo policy.
Triggers only on changes under example/log4j-chain/backend/ or
example/log4j-chain/attacker/ + the workflow file itself.

amd64-only — the demo cluster is amd64 and the maven builds + JDK base
images take significantly longer multi-arch. arm64 can be added if
a consumer ever needs it.

No smoke-test step: the existing chain workflow's /healthz check
doesn't generalise to the attacker (marshalsec LDAP + python HTTP
serving Payload.class — no health endpoint). Push success is the
green signal; the demo runbooks exercise the images end-to-end.

Flagged by dx-agent in k8sstormcenter/pixie#47 — needed for the #7
pgsql re-test and the M6 combined run.
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

A new GitHub Actions workflow file adds automated building and publishing of four log4j-chain demo container images to GHCR. The workflow runs on main branch pushes (filtered to relevant paths) and manual dispatch, computes image tags from commit SHA and branch name (with latest tag on main), and uses Docker Buildx to build and push each component.

Changes

log4j-chain Container Image Publishing

Layer / File(s) Summary
Workflow configuration and triggers
.github/workflows/ci-log4j-chain-images.yaml
Workflow document describes tagging conventions for four demo images. Push trigger is limited to main branch with path filters for example/log4j-chain/** and the workflow file itself. Manual dispatch is also enabled. Permissions grant contents: read and packages: write for GHCR access.
Build matrix and component definitions
.github/workflows/ci-log4j-chain-images.yaml
Job defines a four-component matrix for vulnerable, contained, and patched backend variants plus attacker, each with its corresponding build context directory and Dockerfile path.
Image tagging strategy and build execution
.github/workflows/ci-log4j-chain-images.yaml
Tag computation uses short commit SHA and sanitized branch name, with conditional latest tag appended when ref is main. Steps perform GHCR login, Docker Buildx setup, and build-push for linux/amd64, pushing the computed tag list to GHCR for each matrix component.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • k8sstormcenter/bob#137: Both PRs add the same log4j-chain container image CI workflow with matrix builds, SHA/branch tagging, and latest-tag promotion on main.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a CI workflow to publish log4j-chain images to GHCR instead of relying on ttl.sh.
Description check ✅ Passed The description is directly related to the changeset, explaining the motivation for the workflow, its structure, and design decisions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/log4j-chain-images

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci-log4j-chain-images.yaml:
- Around line 53-54: The Checkout step (named "Checkout" and using
actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683) is missing
persist-credentials: false; update that step to add persist-credentials: false
so the GitHub token is not written into .git/config and cannot be copied into
Docker build contexts or accessed by later steps.
- Line 67: Remove the unused output by deleting the line that writes the
`primary` output to GitHub Actions outputs; specifically remove the echo that
appends "primary=${IMG}:${SHORT_SHA}" to the file referenced by `GITHUB_OUTPUT`
so that `primary` is no longer declared (leave other outputs/uses of IMG and
SHORT_SHA intact).
- Around line 28-31: Add a short explanatory comment above the permissions block
that clarifies why packages: write is required (e.g., to publish or update build
artifacts/packages from CI), so update the permissions section containing
"permissions:", "contents: read", and "packages: write" to include that comment;
keep the comment concise and specific to the CI workflow's need for package
write access.
- Around line 18-27: Add a concurrency policy to the GitHub Actions workflow to
prevent parallel runs from racing to push the same "latest" tag: inside the
workflow root (near the existing on: push / workflow_dispatch) add a concurrency
section that defines a stable group (e.g., use the branch ref like github.ref or
a combination such as "ci-log4j-chain-images-${{ github.ref }}") and set
cancel-in-progress: true so only the most recent run for a given branch
proceeds; update the workflow that currently contains the on: push block to
include this concurrency configuration to serialize pushes that could overwrite
the latest image.
- Line 61: The IMG variable currently uses ghcr.io/${{ github.repository_owner
}}/${{ matrix.component }} which causes forked PRs to target the fork owner’s
GHCR; to clarify intent either (A) keep the dynamic value to publish to the
workflow runner's owner (no change), or (B) explicitly target the upstream org
by replacing IMG with ghcr.io/k8sstormcenter/${{ matrix.component }} (only do
this if you intend CI to push into the k8sstormcenter namespace), or (preferred
for safety) add a conditional that sets IMG to k8sstormcenter only when
github.repository_owner == 'k8sstormcenter' and otherwise uses
github.repository_owner, updating the IMG assignment in the CI job where IMG is
defined.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 70128ee8-9289-4b2a-9c75-28e85d724a07

📥 Commits

Reviewing files that changed from the base of the PR and between 5c9170b and caa1304.

📒 Files selected for processing (1)
  • .github/workflows/ci-log4j-chain-images.yaml

Comment on lines +18 to +27
on:
push:
branches:
- main
paths:
- 'example/log4j-chain/backend/**'
- 'example/log4j-chain/attacker/**'
- '.github/workflows/ci-log4j-chain-images.yaml'
workflow_dispatch:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

Add concurrency control to prevent race conditions on the latest tag.

Without concurrency limits, parallel pushes to main will spawn multiple workflow runs that race to push the latest tag. If two commits land in quick succession, the older commit's workflow could finish second and overwrite latest with a stale image.

🔒 Recommended fix to add concurrency group
 on:
   push:
     branches:
       - main
     paths:
       - 'example/log4j-chain/backend/**'
       - 'example/log4j-chain/attacker/**'
       - '.github/workflows/ci-log4j-chain-images.yaml'
   workflow_dispatch:

+concurrency:
+  group: log4j-chain-images-${{ github.ref }}
+  cancel-in-progress: true
+
 permissions:
   contents: read
   packages: write
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
on:
push:
branches:
- main
paths:
- 'example/log4j-chain/backend/**'
- 'example/log4j-chain/attacker/**'
- '.github/workflows/ci-log4j-chain-images.yaml'
workflow_dispatch:
on:
push:
branches:
- main
paths:
- 'example/log4j-chain/backend/**'
- 'example/log4j-chain/attacker/**'
- '.github/workflows/ci-log4j-chain-images.yaml'
workflow_dispatch:
concurrency:
group: log4j-chain-images-${{ github.ref }}
cancel-in-progress: true
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 18-26: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting

(concurrency-limits)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci-log4j-chain-images.yaml around lines 18 - 27, Add a
concurrency policy to the GitHub Actions workflow to prevent parallel runs from
racing to push the same "latest" tag: inside the workflow root (near the
existing on: push / workflow_dispatch) add a concurrency section that defines a
stable group (e.g., use the branch ref like github.ref or a combination such as
"ci-log4j-chain-images-${{ github.ref }}") and set cancel-in-progress: true so
only the most recent run for a given branch proceeds; update the workflow that
currently contains the on: push block to include this concurrency configuration
to serialize pushes that could overwrite the latest image.

Source: Linters/SAST tools

Comment on lines +28 to +31
permissions:
contents: read
packages: write

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider adding an explanatory comment for the permissions block.

Adding a brief comment explaining why packages: write is required improves clarity for future maintainers.

📝 Suggested improvement
+# Read repo contents for checkout; write packages to push images to GHCR.
 permissions:
   contents: read
   packages: write
🧰 Tools
🪛 zizmor (1.25.2)

[error] 30-30: overly broad permissions (excessive-permissions): packages: write is overly broad at the workflow level

(excessive-permissions)


[warning] 30-30: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci-log4j-chain-images.yaml around lines 28 - 31, Add a
short explanatory comment above the permissions block that clarifies why
packages: write is required (e.g., to publish or update build artifacts/packages
from CI), so update the permissions section containing "permissions:",
"contents: read", and "packages: write" to include that comment; keep the
comment concise and specific to the CI workflow's need for package write access.

Source: Linters/SAST tools

Comment on lines +53 to +54
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Set persist-credentials: false to prevent credential leakage.

The checkout action defaults to persisting the GitHub token in .git/config. In a Docker build workflow, this credential could leak into the build context (via COPY .git patterns) or remain accessible to later steps, creating a security risk.

🛡️ Recommended fix
       - name: Checkout
         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+        with:
+          persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 53-54: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci-log4j-chain-images.yaml around lines 53 - 54, The
Checkout step (named "Checkout" and using
actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683) is missing
persist-credentials: false; update that step to add persist-credentials: false
so the GitHub token is not written into .git/config and cannot be copied into
Docker build contexts or accessed by later steps.

Source: Linters/SAST tools

run: |
SHORT_SHA=$(git rev-parse --short HEAD)
BRANCH_SAFE="${GITHUB_REF_NAME//\//-}"
IMG=ghcr.io/${{ github.repository_owner }}/${{ matrix.component }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if repository_owner is hardcoded in similar workflows
rg -n 'ghcr\.io/k8sstormcenter' --type yaml -g '.github/workflows/*.yaml'

Repository: k8sstormcenter/bob

Length of output: 211


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Show the relevant section of the workflow around the IMG definition
FILE=".github/workflows/ci-log4j-chain-images.yaml"
echo "== File: $FILE =="
# Print lines 45-90 if the file is reasonably small
nl -ba "$FILE" | sed -n '45,90p'

# 2) Find all uses of github.repository_owner in workflow YAMLs
echo
echo "== Uses of github.repository_owner in workflows =="
rg -n 'github\.repository_owner' --type yaml .github/workflows || true

# 3) Re-run search for the hardcoded ghcr.io/k8sstormcenter pattern with correct globs
echo
echo "== Uses of ghcr.io/k8sstormcenter in workflows =="
rg -n 'ghcr\.io/k8sstormcenter' --type yaml .github/workflows || true

Repository: k8sstormcenter/bob

Length of output: 180


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/ci-log4j-chain-images.yaml"

echo "== File: $FILE (context around IMG) =="
# Show approximate line window (adjust if needed)
sed -n '50,80p' "$FILE" | cat -n

echo
echo "== Uses of github.repository_owner in workflows =="
rg -n 'github\.repository_owner' --type yaml .github/workflows || true

echo
echo "== Uses of ghcr.io in workflows =="
rg -n 'ghcr\.io/' --type yaml .github/workflows || true

echo
echo "== Uses of hardcoded ghcr.io/k8sstormcenter in workflows =="
rg -n 'ghcr\.io/k8sstormcenter' --type yaml .github/workflows || true

Repository: k8sstormcenter/bob

Length of output: 6236


Clarify GHCR publish target for fork runs in CI.

.github/workflows/ci-log4j-chain-images.yaml sets IMG to ghcr.io/${{ github.repository_owner }}/${{ matrix.component }}, so runs from forks will push to the fork owner’s GHCR namespace. This pattern matches .github/workflows/ci-chain-images.yaml; only hardcode k8sstormcenter if publishing to the upstream org is required.

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 61-61: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[warning] 61-61: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci-log4j-chain-images.yaml at line 61, The IMG variable
currently uses ghcr.io/${{ github.repository_owner }}/${{ matrix.component }}
which causes forked PRs to target the fork owner’s GHCR; to clarify intent
either (A) keep the dynamic value to publish to the workflow runner's owner (no
change), or (B) explicitly target the upstream org by replacing IMG with
ghcr.io/k8sstormcenter/${{ matrix.component }} (only do this if you intend CI to
push into the k8sstormcenter namespace), or (preferred for safety) add a
conditional that sets IMG to k8sstormcenter only when github.repository_owner ==
'k8sstormcenter' and otherwise uses github.repository_owner, updating the IMG
assignment in the CI job where IMG is defined.

Source: Linters/SAST tools

TAGS="${TAGS},${IMG}:latest"
fi
echo "tags=${TAGS}" >> "$GITHUB_OUTPUT"
echo "primary=${IMG}:${SHORT_SHA}" >> "$GITHUB_OUTPUT"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Remove unused primary output.

The primary output is defined but never consumed by any subsequent step. Remove it to reduce confusion.

🧹 Suggested cleanup
           echo "tags=${TAGS}" >> "$GITHUB_OUTPUT"
-          echo "primary=${IMG}:${SHORT_SHA}" >> "$GITHUB_OUTPUT"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
echo "primary=${IMG}:${SHORT_SHA}" >> "$GITHUB_OUTPUT"
echo "tags=${TAGS}" >> "$GITHUB_OUTPUT"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci-log4j-chain-images.yaml at line 67, Remove the unused
output by deleting the line that writes the `primary` output to GitHub Actions
outputs; specifically remove the echo that appends "primary=${IMG}:${SHORT_SHA}"
to the file referenced by `GITHUB_OUTPUT` so that `primary` is no longer
declared (leave other outputs/uses of IMG and SHORT_SHA intact).

@entlein

entlein commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

Review — ci-log4j-chain-images.yaml

Reviewed the workflow against the sibling ci-chain-images.yaml and the actual example/log4j-chain/ image consumers. Content is correct and image coverage is complete — recommend merging with one consistency fix.

✅ Correct

  • Matrix covers all custom images: log4j-chain-backend-{vulnerable,contained,patched} (right Dockerfiles) + log4j-chain-attacker. Verified against the manifests — log4j-chain.yaml/backend-b.yaml/backend-c.yaml reference exactly these.
  • No missing image: the demo's frontend is stock nginx:1.27-alpine, postgres is postgres:15/16, observer/attack-pod are curlimages/curl — nothing else custom. Correctly omitted.
  • Tags (<sha>, <branch>, latest main-only), permissions: packages: write, GHCR login via GITHUB_TOKEN, and SHA-pinned actions all match repo convention.
  • amd64-only is reasonable and documented (demo cluster is amd64; multi-arch maven/JDK is slow). Fine to defer arm64.

🟡 One gap vs the sibling workflow — add a smoke test before tagging

ci-chain-images.yaml gates publication with a TDD step and a "Smoke-test pushed image" step. #139 builds and pushes with no validation — a successful docker build doesn't mean the backend actually starts, so a broken image can land on :latest. The Java backend can't reuse the Go go test gate, but a minimal container smoke test gives parity, e.g. after build (load locally, run, hit the health/app port):

      - name: Smoke-test image
        run: |
          docker run -d --name t -p 8080:8080 ${{ steps.tags.outputs.primary }}
          for i in $(seq 1 30); do curl -fsS localhost:8080/ >/dev/null 2>&1 && break; sleep 2; done
          docker logs t | tail -20
          docker rm -f t

(Adjust port/path to the backend's actual listener; for the attacker image a --version/entrypoint check is enough.) Build the image once with load: true, smoke it, then push.

Minor (optional)

  • paths: triggers on backend/** + attacker/**; fine since frontend is stock, but a change to the demo manifests won't re-trigger (acceptable — they reference :latest).
  • No concurrency: block — rapid pushes to main could race; low risk.

Note for context

The four log4j-chain-*:latest images are already live on GHCR (the earlier merge ran the build before it was reverted), so merging this restores CI freshness, not first publication. Consumers can pin by digest in the interim (posted on dx#42).

LGTM to merge once the smoke-test step is added (or as-is if you want freshness restored now and the smoke gate as a fast-follow).

@entlein
entlein merged commit e0db77d into main Jun 9, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant