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: | diff --git a/Dockerfile b/Dockerfile index 59acccf..5142e74 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 -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 + && rm -f kustomize /tmp/install_kustomize.sh # Install GitHub Runner WORKDIR /runner 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 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..1f4e13d 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,9 +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" - curl -s -X POST "$METADATA_ENDPOINT" \ + # 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' \ - -d "{\"StartRequests\": [{\"EventId\": \"${EventId}\"}]}" + -H 'Content-Type: application/json' \ + -d "$JSON_PAYLOAD" echo "Event acknowledged successfully" fi else