Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions .github/workflows/pr-checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ env:
# renovate: datasource=github-releases depName=terraform-linters/tflint
TFLINT_VERSION: "v0.63.1"
ARTIFACT_RETENTION_DAYS: "15"
# renovate: datasource=github-releases depName=aquasecurity/trivy
TRIVY_VERSION: "v0.70.0"

permissions:
contents: read
Expand Down Expand Up @@ -426,3 +428,136 @@ jobs:
name: terraform-validation-${{ github.run_attempt }}-${{ github.sha }}-${{ github.run_number }}
retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }}
path: ${{ env.REPORT_DIR }}/terraform


extract-images:
needs: run-kubara-init-and-generate
runs-on: ubuntu-latest
outputs:
images: ${{ steps.matrix.outputs.images }}
has_images: ${{ steps.matrix.outputs.has_images }}
defaults:
run:
shell: bash
steps:
- name: Checkout CI scripts
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
sparse-checkout: |
.scripts
.github/helm-profiles

- name: Download generated kubara artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: kubara-generated-files-${{ github.run_attempt }}-${{ github.sha }}-${{ github.run_number }}

- name: Verify Helm
run: helm version --short

- name: Extract container images
run: |
mkdir -p reports
OUTPUT_FILE="$PWD/reports/images.txt" .scripts/image-version.sh

- name: Write image summary
if: always()
run: |
[[ -f reports/images.txt ]] || exit 0
echo "### Container Images" >> "$GITHUB_STEP_SUMMARY"
while read -r img; do
echo "- $img" >> "$GITHUB_STEP_SUMMARY"
done < reports/images.txt

- name: Upload image list
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
with:
name: container-images-${{ github.run_attempt }}-${{ github.sha }}-${{ github.run_number }}
retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }}
path: reports/images.txt

- name: Build image matrix
id: matrix
run: |
set -euo pipefail
images=$(jq -R -s -c 'split("\n") | map(select(length > 0))' reports/images.txt)
echo "images=$images" >> "$GITHUB_OUTPUT"
if [[ "$images" == "[]" ]]; then
echo "has_images=false" >> "$GITHUB_OUTPUT"
else
echo "has_images=true" >> "$GITHUB_OUTPUT"
fi

trivy-db:
needs: extract-images
if: needs.extract-images.outputs.has_images == 'true'
runs-on: ubuntu-latest
steps:
- name: Cache Trivy DB
id: cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ github.workspace }}/.cache/trivy
key: trivy-db-${{ github.run_id }}
restore-keys: trivy-db-

- name: Setup Trivy
if: steps.cache.outputs.cache-hit != 'true'
uses: aquasecurity/setup-trivy@81e514348e19b6112ce2a7e3ecbafe19c1e1f567 # v0.3.1
with:
version: ${{ env.TRIVY_VERSION }}

- name: Download DB only
if: steps.cache.outputs.cache-hit != 'true'
run: trivy image --download-db-only --cache-dir "${{ github.workspace }}"/.cache/trivy

trivy-scan:
needs: [extract-images, trivy-db]
if: needs.extract-images.outputs.has_images == 'true'
runs-on: ubuntu-latest
env:
TRIVY_CACHE_DIR: ${{ github.workspace }}/.cache/trivy
TRIVY_SKIP_DB_UPDATE: true # assumes db is present, can't silently redownload db
TRIVY_SKIP_JAVA_DB_UPDATE: true

strategy:
fail-fast: false
max-parallel: 10
matrix:
image: ${{ fromJSON(needs.extract-images.outputs.images) }}
steps:
- name: Checkout # for fetching .git to upload sarif
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
sparse-checkout: .github
fetch-depth: 1

- name: Restore Trivy DB
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ github.workspace }}/.cache/trivy
key: trivy-db-${{ github.run_id }}
restore-keys: trivy-db-


- name: Scan ${{ matrix.image }}
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
cache: false # uses restored db
image-ref: ${{ matrix.image }}
version: ${{ env.TRIVY_VERSION }}
scanners: vuln
severity: HIGH

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

severity: HIGH only reports HIGH β€” CRITICAL findings are excluded from the SARIF. Did you mean severity: HIGH,CRITICAL? (Combined with exit-code: '0' the scan is report-only, which is totally fine if that's intended.)

limit-severities-for-sarif: true
ignore-unfixed: true
format: sarif
output: trivy-results.sarif
exit-code: '0'

- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
sarif_file: trivy-results.sarif
category: trivy-${{ matrix.image }}
46 changes: 46 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,52 @@ jobs:
- name: Install go-licenses
run: go install github.com/google/go-licenses/v2@3e084b0caf710f7bfead967567539214f598c0a2 #v2.0.1

- name: Verify preinstalled Helm
run: |
set -euo pipefail
helm version --short

- name: Run kubara generate
env:
KUBARA_PROJECT_NAME: kubara
KUBARA_PROJECT_STAGE: tst
KUBARA_DOCKERCONFIG_BASE64: "000000"
KUBARA_ARGOCD_WIZARD_ACCOUNT_PASSWORD: "000000"
KUBARA_ARGOCD_HELM_REPO_USERNAME: git
KUBARA_ARGOCD_HELM_REPO_PASSWORD: "000000"
KUBARA_ARGOCD_HELM_REPO_URL: "https://kubara.io/kubara.git"
KUBARA_ARGOCD_GIT_HTTPS_URL: "https://kubara.io/kubara.git"
KUBARA_ARGOCD_GIT_PAT_OR_PASSWORD: "000000"
KUBARA_ARGOCD_GIT_USERNAME: git
KUBARA_DNS_NAME: kubara-tst.stackit.run
KUBARA_STACKIT_PROJECT_ID: "00000000-0000-0000-0000-000000000000"
KUBARA_TERRAFORM_PROVIDER: stackit
KUBARA_CLUSTER_TYPE: hub
KUBARA_SSO_ORG: Kubara
KUBARA_SSO_TEAM: Test
KUBARA_KUBERNETES_TYPE: ske
KUBARA_KUBERNETES_VERSION: "1.35.0"
run: |
set -euo pipefail
mkdir -p /tmp/gen
(cd src && go run main.go --work-dir /tmp/gen init --prep)
.scripts/kubara-env-update.sh /tmp/gen/.env
(cd src && go run main.go --work-dir /tmp/gen init)
.scripts/kubara-config-update.sh /tmp/gen/config.yaml
(cd src && go run main.go --work-dir /tmp/gen generate)

- name: Extract images and set goreleaser header
run: |
MANAGED=/tmp/gen/managed-service-catalog/helm \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Small path mismatch here: the generated files live under /tmp/gen, but only MANAGED is overridden β€” and it points to managed-service-catalog/helm, while generate now produces platform-components/helm. CONFIG_FILE/CONFIGS also stay relative to the repo root, where there's no config.yaml.

As written the script would exit before GoReleaser, so the header wouldn't be filled. Something like:

MANAGED=/tmp/gen/platform-components/helm \
CONFIG_FILE=/tmp/gen/config.yaml \
CONFIGS=/tmp/gen/platform-configs/<cluster>/helm \
  "$GITHUB_WORKSPACE"/.scripts/image-version.sh

should fix it. (Not caught by PR CI since release only runs on tag push.)

OUTPUT_FILE=/tmp/images.txt \
"$GITHUB_WORKSPACE"/.scripts/image-version.sh
{
echo "IMAGE_LIST<<EOF"
sed 's/^/- `/' /tmp/images.txt | sed 's/$/ `/'
echo "EOF"
} >> "$GITHUB_ENV"


- name: Run GoReleaser
if: github.event_name == 'push'
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
Expand Down
84 changes: 84 additions & 0 deletions .scripts/image-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bash


# pipefail that pipes break
set -euo pipefail

export PATH="$HOME/.local/bin/:$PATH"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

MANAGED="${MANAGED:-${PWD}/platform-components/helm}"
CONFIG_FILE="${CONFIG_FILE:-config.yaml}"
CLUSTER_NAME="$(yq -r '.clusters[0].name' "$CONFIG_FILE")"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tiny thing: yq reads $CONFIG_FILE here before the [[ -f … ]] / command -v checks below, so a missing file/tool produces a cryptic command-substitution error instead of your nice diagnostic. Moving the checks above this line would surface the friendly message.

CONFIGS="${CONFIGS:-platform-configs/${CLUSTER_NAME}/helm}"
OUTPUT_FILE="${OUTPUT_FILE:-}"
Comment thread
tuunit marked this conversation as resolved.

[[ -f "$CONFIG_FILE" ]] || { echo "::error::Missing $CONFIG_FILE β€” run 'kubara generate' first (or cd into its output)"; exit 1; }
[[ -d "$MANAGED" ]] || { echo "::error::Missing $MANAGED β€” run 'kubara generate' first"; exit 1; }
command -v helm >/dev/null 2>&1 || { echo "::error::helm not found on PATH"; exit 1; }
command -v yq >/dev/null 2>&1 || { echo "::error::yq not found on PATH"; exit 1; }

KUBE_VERSION=$(yq -r '.clusters[0].terraform.kubernetesVersion' "$CONFIG_FILE")

PROMETHEUS_STATUS="$(yq -r '.clusters[0].services."kube-prometheus-stack".status // "disabled"' "$CONFIG_FILE")"

# helm template flags advertise the monitoring API only when
# kube-prometheus-stack is enabled, since some charts (eg. traefik) render
# ServiceMonitors guarded by a `fail` on monitoring.coreos.com/v1.
HELM_TEMPLATE_ARGS=(--kube-version "$KUBE_VERSION" --include-crds)
if [[ "$PROMETHEUS_STATUS" == enabled ]]; then
HELM_TEMPLATE_ARGS+=(--api-versions "monitoring.coreos.com/v1")
fi


echo "Rendering charts from $MANAGED (kube-version=$KUBE_VERSION)" >&2


render_dir="$(mktemp -d)"; trap 'rm -rf "$render_dir"' EXIT
FAILED=()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice that failures get collected in FAILED β€” but the array is never checked afterwards, so a chart that fails dependency update/template is silently skipped and the job still passes. That means its images quietly disappear from the scan (this is exactly what happens to reloader).

Could we fail (or at least emit a visible ::warning::) when FAILED is non-empty?

Minor: on the helm dependency update line below, dep_out=$(… >/dev/null 2>&1) discards all output, so the later echo "$dep_out" never prints anything.


for chart_path in "$MANAGED"/*/; do
chart=$(basename "$chart_path")
[[ -f "$chart_path/Chart.yaml" ]] || continue

# Don't render library charts
[[ "$(yq '.type // "application"' "$chart_path/Chart.yaml")" == library ]] && continue

echo "Updating dependency for ${chart_path}" >&2

if ! dep_out=$(helm dependency update "$chart_path" >/dev/null 2>&1); then
echo "::error::helm dependency update failed for '$chart_path'"; echo "$dep_out" >&2
FAILED+=("$chart:dependency-update"); continue
fi

values_file="$CONFIGS/$chart/values.generated.yaml"
base_values=(); [[ -f "$values_file" ]] && base_values=(-f "$values_file")

if ! helm template "${HELM_TEMPLATE_ARGS[@]}" \
"$chart" "$chart_path" "${base_values[@]}" \
> "$render_dir/$chart.yaml" 2> "$render_dir/$chart.err"; then
echo "::error::helm template for for '$chart':"
sed 's/^/ /' "$render_dir/$chart.err" >&2
FAILED+=("$chart:template"); continue
fi
done

IMAGES="$(
cat "$render_dir"/*.yaml |
grep -E '^[[:space:]]*image:' |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Quick note on coverage: since extraction is a grep image: over the helm template output, it catches all standard container / init-container / sidecar images (good!), but by design it misses:

  • runtime-injected images (mutating webhooks, Kyverno, operator-set images via flags),
  • image refs passed via args/env rather than an image: field,
  • - image: list-item lines β€” the regex only matches image: after whitespace, not after - .

Not a blocker, but might be worth a comment noting this is best-effort, so "scan passed" isn't read as "every running image was scanned."

sed -E "s/^[[:space:]]*image:[[:space:]]*//; s/[\"']//g" |
grep -vE '[*!]' | # drop kyverno wildcard/negation entries
grep -vE '^[[:space:]]*$' |
sort -u
)"

echo "Done Rendering!"

[[ -n "$IMAGES" ]] || { echo "::warning::No image references found"; exit 0; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

With set -euo pipefail, if grep matches nothing the pipeline exits 1 and the script dies in the IMAGES="$( … )" assignment above β€” before ever reaching this friendly ::warning::No image references found / exit 0. OUTPUT_FILE then never gets written, and the workflow reads reports/images.txt unconditionally, so the has_images=false branch can't actually be reached.

A || true on the grep (or writing the file first) would restore the intended empty-case handling.


echo "$IMAGES"

if [[ -n "$OUTPUT_FILE" ]]; then
echo "$IMAGES" > "$OUTPUT_FILE"
echo "::notice::Image list written to $OUTPUT_FILE"
fi
10 changes: 10 additions & 0 deletions .scripts/kubara-config-update.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,14 @@ apply_yaml_if_set KUBARA_KUBERNETES_VERSION ".clusters[0].terraform.kubernetesV
apply_yaml_if_set KUBARA_DNS_NAME ".clusters[0].dnsName"
apply_yaml_if_set KUBARA_DNS_NAME ".clusters[0].terraform.dns.name"

# Enable every catalog service to the image/vuln report and the release header cover all charts
yq eval '(.clusters[0].services[] | .status) = "enabled"' -i "$CFG"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the intent is to enable everything so all charts get rendered for the scan β€” makes sense πŸ‘

The catch: this same generated config is also consumed by the existing helm-checks job, which now renders reloader (disabled by default) and fails on its template-library dependency β†’ the whole job goes red (that's the currently failing check).

Could we scope the "enable all" to a separate config used only for the extract/scan step, so helm-checks keeps its current service set? (Alternatively: make reloader renderable, or only enable services that render cleanly.)


# metalb, loki and velero need custom configs
yq eval '.clusters[0].services.metallb.config.publicLoadBalancerIPs = "203.0.113.10"' -i "$CFG"
yq eval '.clusters[0].services.metallb.config.loadBalancerAddressPool = ["203.0.113.0/24"]' -i "$CFG"
yq eval '.clusters[0].storage.bucketNames.chunks = "loki"' -i "$CFG"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This writes .clusters[0].storage.bucketNames.chunks, but I couldn't find a matching field in the config schema (the Loki chunks bucket comes from the static platform-components/helm/loki/values.yaml). It may be silently ignored β€” worth double-checking whether this actually has the intended effect.


yq eval '.clusters[0].services.velero.config.backupStorage.s3Url = "https://bucket.example.com"' -i "$CFG"
Comment thread
tuunit marked this conversation as resolved.

log "βœ… config.yaml updated"
3 changes: 3 additions & 0 deletions src/.goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ release:
replace_existing_artifacts: true
extra_files:
- glob: licenses.csv
header: |
## Container Images
{{ .Env.IMAGE_LIST }}
changelog:
use: git
sort: asc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ name = "{{ .cluster.name }}"
### Velero Object Storage
velero_bucket_region = "{{ index .cluster.services "velero" "config" "backupStorage" "region" }}"
velero_bucket_name = "bucket-velero-{{ .cluster.name }}-{{ .cluster.stage }}"
{{/* Terraform has complained that the credentials group name might be too long, this trims it to 32 characters */}}
{{- /* Terraform has complained that the credentials group name might be too long, this trims it to 32 characters */}}
velero_credentials_group_name = "{{ printf "cg-velero-%s-%s" .cluster.name .cluster.stage | trunc 32 | trimSuffix "-" }}"

{{ end -}}
Expand Down
Loading