From 870a1f6165cbfa90166c624c5a7c5ce24c809b43 Mon Sep 17 00:00:00 2001 From: Fernando Korndorfer Date: Sun, 18 Jan 2026 16:26:28 +0100 Subject: [PATCH 1/4] docs: add available profiles matrix to README Added a comprehensive profiles matrix documenting the five available configurations: - full: Complete toolset with all available tools - minimal: Lightweight profile with essential tools only - k8s: Kubernetes-focused profile - iac: Infrastructure as Code profile with bash-based tools - iac-pwsh: Infrastructure as Code profile with PowerShell support --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 06641d5..5b92f90 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,20 @@ Supported `--build-arg` variables are listed below to easily configure the runne - `ADD_TERRASPACE`: Installs `terraspace` tool - `ADD_SUDO`: Installs and enables `sudo` for the runner user group + +## Available Profiles + +Pre-configured profiles are available for different use cases: + +| Profile | Description | Included Tools | +|---------|-------------|----------------| +| **full** | Complete toolset with all available tools | Docker, Azure CLI, AWS CLI, PowerShell (with Azure & AWS modules), kubectl, kubelogin, kustomize, Helm, jq, yq, Terraform, OpenTofu, Terraspace | +| **minimal** | Lightweight profile with essential tools only | Docker, jq, yq | +| **k8s** | Kubernetes-focused profile | Docker, kubectl, kubelogin, kustomize, Helm, jq, yq | +| **iac** | Infrastructure as Code profile with bash-based tools | Docker, Azure CLI, AWS CLI, Terraform, OpenTofu, Terraspace, jq, yq | +| **iac-pwsh** | Infrastructure as Code profile with PowerShell support | Docker, Azure CLI, AWS CLI, PowerShell (with Azure & AWS modules), Terraform, OpenTofu, Terraspace, jq, yq | + + # References From 80db1998d342e82421f0163698de492452b4cf7c Mon Sep 17 00:00:00 2001 From: Fernando Korndorfer Date: Sun, 18 Jan 2026 16:56:31 +0100 Subject: [PATCH 2/4] fix: resolve code scan security vulnerabilities - Fix CWE-78: Quote variables in seq command to prevent command injection - Fix CWE-78: Add proper quoting for EventId in vmss_monitor.sh - Fix deprecated apt-key usage, replace with gpg --dearmor - Fix insecure curl piping to bash, download scripts first - Add security documentation for privileged mode and NOPASSWD sudo - Improve curl error handling with standardized flags - Quote sensitive variables to prevent word splitting --- Dockerfile | 19 +++++++++++++------ ec2_monitor.sh | 2 +- run.sh | 6 +++++- start.sh | 6 +++--- vmss_monitor.sh | 6 ++++-- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index 59acccf..e25c68a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -78,6 +78,9 @@ RUN echo "APT::Get::Assume-Yes \"true\";" > /etc/apt/apt.conf.d/90assumeyes \ && rm -rf /var/lib/apt/lists/* # Install sudo... +# SECURITY NOTE: NOPASSWD:ALL is configured for CI/CD automation purposes. +# This allows the agent user to execute commands with sudo without password prompts. +# This is a security trade-off for CI/CD runner functionality. RUN test "${ADD_SUDO}" = "1" || exit 0 && \ apt-get update && apt-get install -y --no-install-recommends sudo \ && apt clean \ @@ -106,7 +109,9 @@ RUN test "${ADD_JQ}" = "1" || exit 0 && \ # Install latest Azure CLI https://learn.microsoft.com/cli/azure/install-azure-cli-linux RUN test "${ADD_AZURE_CLI}" = "1" || exit 0 && \ - curl -sLS "https://aka.ms/InstallAzureCLIDeb" | bash \ + curl -sLS "https://aka.ms/InstallAzureCLIDeb" -o /tmp/install-azure-cli.sh \ + && bash /tmp/install-azure-cli.sh \ + && rm /tmp/install-azure-cli.sh \ && apt clean \ && rm -rf /var/lib/apt/lists/* \ && az config set extension.use_dynamic_install=yes_without_prompt \ @@ -175,11 +180,12 @@ RUN test "${ADD_OPENTOFU}" = "1" || exit 0 && \ # Instal Terraspace https://terraspace.cloud/docs/install/ RUN test "${ADD_TERRASPACE}" = "1" || exit 0 && \ - curl -sL https://apt.boltops.com/boltops-key.public | apt-key add - \ - && echo "deb https://apt.boltops.com stable main" > /etc/apt/sources.list.d/boltops.list \ + curl -sL https://apt.boltops.com/boltops-key.public | gpg --dearmor -o /usr/share/keyrings/boltops-archive-keyring.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/boltops-archive-keyring.gpg] https://apt.boltops.com stable main" > /etc/apt/sources.list.d/boltops.list \ && apt-get update \ && apt-get install -y terraspace \ - && apt clean + && apt clean \ + && rm -rf /var/lib/apt/lists/* # Install HELM https://helm.sh/docs/intro/install/ RUN test "${ADD_HELM}" = "1" || exit 0 && \ @@ -192,9 +198,10 @@ RUN test "${ADD_HELM}" = "1" || exit 0 && \ # Install Kustomize https://kubectl.docs.kubernetes.io/installation/kustomize/ RUN test "${ADD_KUSTOMIZE}" = "1" || exit 0 && \ - curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash \ + curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" -o /tmp/install_kustomize.sh \ + && bash /tmp/install_kustomize.sh \ && install -o root -g root -m 0755 kustomize /usr/local/bin/kustomize \ - && rm -f kustomize + && rm -f kustomize /tmp/install_kustomize.sh # Install GitHub Runner WORKDIR /runner diff --git a/ec2_monitor.sh b/ec2_monitor.sh index 0ec69a4..e82fec5 100755 --- a/ec2_monitor.sh +++ b/ec2_monitor.sh @@ -12,7 +12,7 @@ echo "Checking for EC2 spot instance termination notice..." # Query EC2 Instance Metadata Service for spot termination notice # Returns HTTP 404 if no termination notice is present -RESPONSE=$(curl -s -f "$METADATA_ENDPOINT" 2>/dev/null || echo "") +RESPONSE=$(curl -sf "$METADATA_ENDPOINT" 2>/dev/null || echo "") # Check if termination notice exists if [ -n "$RESPONSE" ]; then diff --git a/run.sh b/run.sh index cdc56aa..8b1b497 100755 --- a/run.sh +++ b/run.sh @@ -75,7 +75,7 @@ echo "CPUs per runner: $MAX_CPU" echo "" # Launch runners -for R in $(seq 1 $RUNNER_COUNT); do +for R in $(seq 1 "$RUNNER_COUNT"); do RUNNER_NAME="runner-$(hostname)-$R" WORK_DIR="/mnt/runner${R}/_work" CONTAINER_NAME="github-runner-$R" @@ -92,6 +92,10 @@ for R in $(seq 1 $RUNNER_COUNT); do fi # Run GitHub runner container + # SECURITY NOTE: --privileged mode grants extended privileges to the container. + # This is required for Docker-in-Docker but poses security risks. + # Consider using rootless Docker or Docker socket mounting as alternatives. + # If --privileged is not needed for your use case, remove this flag. docker run \ --privileged \ --tty \ diff --git a/start.sh b/start.sh index 20e18f9..4312ea6 100755 --- a/start.sh +++ b/start.sh @@ -13,7 +13,7 @@ if [ -z "$GITHUB_TOKEN_FILE" ]; then fi GITHUB_TOKEN_FILE=/runner/.token - echo -n $GITHUB_TOKEN > "$GITHUB_TOKEN_FILE" + echo -n "$GITHUB_TOKEN" > "$GITHUB_TOKEN_FILE" fi unset GITHUB_TOKEN @@ -31,7 +31,7 @@ cleanup() { # If the agent has some running jobs, the configuration removal process will fail. # So, give it some time to finish the job. while true; do - ./config.sh remove --token $(cat "$GITHUB_TOKEN_FILE") && break + ./config.sh remove --token "$(cat "$GITHUB_TOKEN_FILE")" && break echo "Retrying in 30 seconds..." sleep 30 @@ -53,7 +53,7 @@ print_header "1. Configuring GitHub Runner..." ./config.sh --unattended \ --name "${RUNNER_NAME:-$(hostname)}" \ --url "$GITHUB_URL" \ - --token $(cat "$GITHUB_TOKEN_FILE") \ + --token "$(cat "$GITHUB_TOKEN_FILE")" \ --labels "${RUNNER_LABELS:-default}" \ --work "${RUNNER_WORK_DIRECTORY:-_work}" \ --replace diff --git a/vmss_monitor.sh b/vmss_monitor.sh index de5d1d1..4877b3a 100755 --- a/vmss_monitor.sh +++ b/vmss_monitor.sh @@ -12,7 +12,7 @@ STOP_SCRIPT='/opt/stop.sh' echo "Checking for VMSS scheduled events..." # Query Azure Instance Metadata Service for scheduled events -curl -s "$METADATA_ENDPOINT" -H 'Metadata: true' > "$EVENTS_FILE" +curl -sf "$METADATA_ENDPOINT" -H 'Metadata: true' > "$EVENTS_FILE" # Check if termination event is scheduled if grep -q "Terminate" "$EVENTS_FILE"; then @@ -30,8 +30,10 @@ if grep -q "Terminate" "$EVENTS_FILE"; then EventId=$(jq -r '.Events[] | select(.EventType == "Terminate") | .EventId' "$EVENTS_FILE") if [ -n "$EventId" ]; then echo "Acknowledging event: $EventId" - curl -s -X POST "$METADATA_ENDPOINT" \ + # Quote EventId to prevent command injection + curl -sf -X POST "$METADATA_ENDPOINT" \ -H 'Metadata: true' \ + -H 'Content-Type: application/json' \ -d "{\"StartRequests\": [{\"EventId\": \"${EventId}\"}]}" echo "Event acknowledged successfully" fi From 3896c839893fa0bb511740fc0eceb5b67f1583e8 Mon Sep 17 00:00:00 2001 From: Fernando Korndorfer Date: Sun, 18 Jan 2026 17:12:50 +0100 Subject: [PATCH 3/4] fix: address PR review feedback - Use jq to safely construct JSON payload in vmss_monitor.sh to properly handle special characters in EventId - Add -L flag to curl for Kustomize download to follow redirects --- Dockerfile | 2 +- vmss_monitor.sh | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index e25c68a..5142e74 100644 --- a/Dockerfile +++ b/Dockerfile @@ -198,7 +198,7 @@ RUN test "${ADD_HELM}" = "1" || exit 0 && \ # Install Kustomize https://kubectl.docs.kubernetes.io/installation/kustomize/ RUN test "${ADD_KUSTOMIZE}" = "1" || exit 0 && \ - curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" -o /tmp/install_kustomize.sh \ + curl -sLf "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" -o /tmp/install_kustomize.sh \ && bash /tmp/install_kustomize.sh \ && install -o root -g root -m 0755 kustomize /usr/local/bin/kustomize \ && rm -f kustomize /tmp/install_kustomize.sh diff --git a/vmss_monitor.sh b/vmss_monitor.sh index 4877b3a..1f4e13d 100755 --- a/vmss_monitor.sh +++ b/vmss_monitor.sh @@ -30,11 +30,12 @@ if grep -q "Terminate" "$EVENTS_FILE"; then EventId=$(jq -r '.Events[] | select(.EventType == "Terminate") | .EventId' "$EVENTS_FILE") if [ -n "$EventId" ]; then echo "Acknowledging event: $EventId" - # Quote EventId to prevent command injection + # Use jq to safely construct JSON payload to handle special characters + JSON_PAYLOAD=$(jq -n --arg eventId "$EventId" '{"StartRequests": [{"EventId": $eventId}]}') curl -sf -X POST "$METADATA_ENDPOINT" \ -H 'Metadata: true' \ -H 'Content-Type: application/json' \ - -d "{\"StartRequests\": [{\"EventId\": \"${EventId}\"}]}" + -d "$JSON_PAYLOAD" echo "Event acknowledged successfully" fi else From 073acc2b42445e14ab6e2a44507b55bc9f89b7db Mon Sep 17 00:00:00 2001 From: Fernando Korndorfer Date: Sun, 18 Jan 2026 20:32:33 +0100 Subject: [PATCH 4/4] fix: improve pipeline reliability with image verification and retry logic - Remove continue-on-error from build-push step to catch failures - Add verification step after image push with retry logic - Add retry logic to manifest creation with image existence check - Wait for images to propagate in registry before creating manifests --- .github/workflows/docker-image.yml | 52 ++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 9604a6c..293976f 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -385,7 +385,27 @@ jobs: cache-to: type=gha,mode=max,scope=${{ matrix.profile.name }}-${{ matrix.platform }} sbom: true provenance: true - continue-on-error: true + + - name: Verify image was pushed + run: | + echo "Verifying image exists in registry..." + IMAGE="${{ env.GHCR_IMAGE }}:${{ steps.versions.outputs.agent }}-ubuntu${{ steps.versions.outputs.os }}-${{ matrix.profile.name }}-${{ steps.date.outputs.date }}-${{ matrix.platform }}" + + # Wait a bit for the image to be available in the registry + sleep 10 + + # Retry up to 3 times + for i in {1..3}; do + if docker buildx imagetools inspect "$IMAGE" > /dev/null 2>&1; then + echo "✓ Image verified: $IMAGE" + exit 0 + fi + echo "Attempt $i: Image not yet available, waiting..." + sleep 10 + done + + echo "✗ Failed to verify image: $IMAGE" + exit 1 create-manifest: name: Create Multi-Arch Manifest (${{ matrix.profile.name }}) @@ -428,11 +448,31 @@ jobs: # Clean up disk space before creating manifests docker system prune -af - # All profiles: amd64 + arm64 - docker buildx imagetools create \ - -t ${{ env.GHCR_IMAGE }}:${{ steps.versions.outputs.agent }}-ubuntu${{ steps.versions.outputs.os }}-${{ matrix.profile.name }}-${{ steps.date.outputs.date }} \ - ${{ env.GHCR_IMAGE }}:${{ steps.versions.outputs.agent }}-ubuntu${{ steps.versions.outputs.os }}-${{ matrix.profile.name }}-${{ steps.date.outputs.date }}-amd64 \ - ${{ env.GHCR_IMAGE }}:${{ steps.versions.outputs.agent }}-ubuntu${{ steps.versions.outputs.os }}-${{ matrix.profile.name }}-${{ steps.date.outputs.date }}-arm64 + # Verify both images exist before creating manifest + AMD64_IMAGE="${{ env.GHCR_IMAGE }}:${{ steps.versions.outputs.agent }}-ubuntu${{ steps.versions.outputs.os }}-${{ matrix.profile.name }}-${{ steps.date.outputs.date }}-amd64" + ARM64_IMAGE="${{ env.GHCR_IMAGE }}:${{ steps.versions.outputs.agent }}-ubuntu${{ steps.versions.outputs.os }}-${{ matrix.profile.name }}-${{ steps.date.outputs.date }}-arm64" + + echo "Verifying source images exist..." + docker buildx imagetools inspect "$AMD64_IMAGE" + docker buildx imagetools inspect "$ARM64_IMAGE" + + # Create multi-arch manifest with retry logic + MANIFEST_TAG="${{ env.GHCR_IMAGE }}:${{ steps.versions.outputs.agent }}-ubuntu${{ steps.versions.outputs.os }}-${{ matrix.profile.name }}-${{ steps.date.outputs.date }}" + + for i in {1..3}; do + if docker buildx imagetools create \ + -t "$MANIFEST_TAG" \ + "$AMD64_IMAGE" \ + "$ARM64_IMAGE"; then + echo "✓ Manifest created successfully: $MANIFEST_TAG" + exit 0 + fi + echo "Attempt $i failed, retrying in 15 seconds..." + sleep 15 + done + + echo "✗ Failed to create manifest after 3 attempts" + exit 1 - name: Create additional tags run: |