From 6624c2a34088e419c8e62c6fb5a00a243cd6c493 Mon Sep 17 00:00:00 2001 From: continuist Date: Thu, 4 Sep 2025 22:15:49 -0400 Subject: [PATCH] Security improvements #1 --- .forgejo/workflows/ci.yml | 65 +++++++++++++++++++-------------------- pip_ready.sh | 32 +++++++++---------- secure_pip_setup.sh | 32 +++++++------------ 3 files changed, 58 insertions(+), 71 deletions(-) diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 36a2667..1e8badc 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI/CD Pipeline with Ephemeral PiP +name: CI/CD Pipeline with Secure Ephemeral PiP on: push: @@ -10,6 +10,7 @@ env: REGISTRY: ${{ secrets.REGISTRY_HOST }} APP_NAME: ${{ secrets.APP_NAME }} IMAGE_TAG: ${{ github.sha }} + RUN_ID: ${{ github.run_id }} jobs: test-backend: @@ -28,57 +29,55 @@ jobs: chmod +x ./pip_ready.sh ./pip_ready.sh - - name: Setup SSH for production deployment + - name: Setup SSH with pinned known_hosts run: | mkdir -p ~/.ssh echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 - ssh-keyscan -H ${{ secrets.PRODUCTION_IP }} >> ~/.ssh/known_hosts + echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts + chmod 644 ~/.ssh/known_hosts - - name: Login to Forgejo Container Registry + - name: Create integration test network run: | - podman exec ci-pip podman login ${{ secrets.REGISTRY_HOST }} \ - -u ${{ secrets.REGISTRY_USERNAME }} \ - -p ${{ secrets.REGISTRY_TOKEN }} + podman exec ci-pip-$RUN_ID podman network create integ-$RUN_ID - - name: Start PostgreSQL for integration tests + - name: Start PostgreSQL on internal network run: | - podman exec ci-pip podman run -d \ + podman exec ci-pip-$RUN_ID podman run -d \ --name test-postgres \ + --network integ-$RUN_ID \ -e POSTGRES_PASSWORD=testpassword \ -e POSTGRES_USER=testuser \ -e POSTGRES_DB=testdb \ - -p 5432:5432 \ postgres:15-alpine - name: Wait for PostgreSQL to be ready run: | - podman exec ci-pip timeout 60 bash -c 'until podman exec test-postgres pg_isready -h localhost -p 5432 -U testuser; do sleep 1; done' + podman exec ci-pip-$RUN_ID timeout 60 bash -c 'until podman exec test-postgres pg_isready -h test-postgres -p 5432 -U testuser; do sleep 1; done' - name: Run backend unit tests run: | - podman exec ci-pip podman run --rm \ + podman exec ci-pip-$RUN_ID podman run --rm \ -v $(pwd):/workspace \ -w /workspace \ rust:latest \ sh -c "cargo test --lib -- --test-threads=1" - name: Run backend integration tests - env: - DATABASE_URL: postgres://testuser:testpassword@localhost:5432/testdb run: | - podman exec ci-pip podman run --rm \ + podman exec ci-pip-$RUN_ID podman run --rm \ -v $(pwd):/workspace \ -w /workspace \ - -e DATABASE_URL="$DATABASE_URL" \ + -e DATABASE_URL=postgres://testuser:testpassword@test-postgres:5432/testdb \ rust:latest \ sh -c "cargo test --test '*' -- --test-threads=1" - - name: Cleanup test database + - name: Cleanup test resources if: always() run: | - podman exec ci-pip podman stop test-postgres 2>/dev/null || true - podman exec ci-pip podman rm test-postgres 2>/dev/null || true + podman exec ci-pip-$RUN_ID podman stop test-postgres 2>/dev/null || true + podman exec ci-pip-$RUN_ID podman rm test-postgres 2>/dev/null || true + podman exec ci-pip-$RUN_ID podman network rm integ-$RUN_ID 2>/dev/null || true test-frontend: runs-on: [self-hosted, ci] @@ -99,7 +98,7 @@ jobs: - name: Run frontend tests in PiP run: | - podman exec ci-pip podman run --rm \ + podman exec ci-pip-$RUN_ID podman run --rm \ -v $(pwd):/workspace \ -w /workspace \ node:20 \ @@ -122,21 +121,21 @@ jobs: chmod +x ./pip_ready.sh ./pip_ready.sh - - name: Login to Forgejo Container Registry + - name: Login to Forgejo Container Registry securely run: | - podman exec ci-pip podman login ${{ secrets.REGISTRY_HOST }} \ + echo "${{ secrets.REGISTRY_TOKEN }}" | podman exec -i ci-pip-$RUN_ID podman login ${{ secrets.REGISTRY_HOST }} \ -u ${{ secrets.REGISTRY_USERNAME }} \ - -p ${{ secrets.REGISTRY_TOKEN }} + --password-stdin - name: Build backend image run: | - podman exec ci-pip podman build \ + podman exec ci-pip-$RUN_ID podman build \ -t ${{ secrets.REGISTRY_HOST }}/${{ secrets.APP_NAME }}/backend:${{ github.sha }} \ -f Dockerfile.backend . - name: Push backend image run: | - podman exec ci-pip podman push \ + podman exec ci-pip-$RUN_ID podman push \ ${{ secrets.REGISTRY_HOST }}/${{ secrets.APP_NAME }}/backend:${{ github.sha }} build-frontend: @@ -156,21 +155,21 @@ jobs: chmod +x ./pip_ready.sh ./pip_ready.sh - - name: Login to Forgejo Container Registry + - name: Login to Forgejo Container Registry securely run: | - podman exec ci-pip podman login ${{ secrets.REGISTRY_HOST }} \ + echo "${{ secrets.REGISTRY_TOKEN }}" | podman exec -i ci-pip-$RUN_ID podman login ${{ secrets.REGISTRY_HOST }} \ -u ${{ secrets.REGISTRY_USERNAME }} \ - -p ${{ secrets.REGISTRY_TOKEN }} + --password-stdin - name: Build frontend image run: | - podman exec ci-pip podman build \ + podman exec ci-pip-$RUN_ID podman build \ -t ${{ secrets.REGISTRY_HOST }}/${{ secrets.APP_NAME }}/frontend:${{ github.sha }} \ -f Dockerfile.frontend . - name: Push frontend image run: | - podman exec ci-pip podman push \ + podman exec ci-pip-$RUN_ID podman push \ ${{ secrets.REGISTRY_HOST }}/${{ secrets.APP_NAME }}/frontend:${{ github.sha }} cleanup: @@ -178,7 +177,7 @@ jobs: needs: [build-backend, build-frontend] if: always() steps: - - name: Cleanup PiP container + - name: Cleanup PiP container and socket run: | - podman rm -f ci-pip 2>/dev/null || true - rm -f /tmp/podman.sock 2>/dev/null || true \ No newline at end of file + podman rm -f ci-pip-$RUN_ID 2>/dev/null || true + rm -f ${XDG_RUNTIME_DIR}/podman-host/podman.sock 2>/dev/null || true \ No newline at end of file diff --git a/pip_ready.sh b/pip_ready.sh index a4883c2..56cd677 100644 --- a/pip_ready.sh +++ b/pip_ready.sh @@ -1,16 +1,17 @@ #!/bin/bash set -euo pipefail -# pip_ready.sh - Readiness probe for PiP container -# Checks if the Podman-in-Podman container is ready for CI operations +# pip_ready.sh - Socket-only readiness probe for PiP container +# Checks if PiP container can connect to host Podman via UNIX socket -PIP_CONTAINER_NAME="ci-pip" -MAX_RETRIES=30 +RUN_ID="${GITHUB_RUN_ID:-local}" +PIP_CONTAINER_NAME="ci-pip-${RUN_ID}" +MAX_RETRIES=15 RETRY_DELAY=2 -# Function to check PiP readiness +# Function to check PiP socket connectivity only check_pip_ready() { - echo "🔍 Checking PiP container readiness..." + echo "🔍 Checking PiP container socket connectivity..." # Check if container exists and is running if ! podman inspect "${PIP_CONTAINER_NAME}" --format '{{.State.Status}}' 2>/dev/null | grep -q running; then @@ -18,22 +19,19 @@ check_pip_ready() { return 1 fi - # Test basic Podman command inside PiP + # Test basic Podman command connectivity (socket only) if ! podman exec "${PIP_CONTAINER_NAME}" podman info --format json >/dev/null 2>&1; then - echo "⚠️ PiP container running but Podman not responsive" + echo "⚠️ PiP container running but socket connectivity failed" return 1 fi - # Test image pulling capability (network test) - if ! podman exec "${PIP_CONTAINER_NAME}" podman pull --quiet alpine:latest >/dev/null 2>&1; then - echo "⚠️ PiP container ready but network access test failed" + # Test version command (socket connectivity verification) + if ! podman exec "${PIP_CONTAINER_NAME}" podman version >/dev/null 2>&1; then + echo "⚠️ PiP container socket connection unstable" return 1 fi - # Clean up test image - podman exec "${PIP_CONTAINER_NAME}" podman rmi alpine:latest 2>/dev/null || true - - echo "✅ PiP container ready and fully operational" + echo "✅ PiP container ready with socket connectivity" return 0 } @@ -45,13 +43,13 @@ while [[ ${attempt} -le ${MAX_RETRIES} ]]; do exit 0 fi - echo "⏳ PiP not ready yet (attempt ${attempt}/${MAX_RETRIES}), retrying in ${RETRY_DELAY}s..." + echo "⏳ PiP socket connectivity not ready yet (attempt ${attempt}/${MAX_RETRIES}), retrying in ${RETRY_DELAY}s..." sleep ${RETRY_DELAY} attempt=$((attempt + 1)) done # If we reach here, all retries failed -echo "❌ ERROR: PiP container failed to become ready after ${MAX_RETRIES} attempts" +echo "❌ ERROR: PiP container failed to establish socket connectivity after ${MAX_RETRIES} attempts" echo "📋 Container status:" podman ps -a --filter "name=${PIP_CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" || true diff --git a/secure_pip_setup.sh b/secure_pip_setup.sh index 0f6f91e..29fbf0f 100644 --- a/secure_pip_setup.sh +++ b/secure_pip_setup.sh @@ -1,11 +1,12 @@ #!/bin/bash set -euo pipefail -# secure_pip_setup.sh - Idempotent setup for ephemeral Podman-in-Podman container -# This script creates a secure PiP container for CI operations with no network exposure +# secure_pip_setup.sh - Secure PiP client container setup +# Creates ephemeral PiP container that connects to host Podman via UNIX socket # Configuration -PIP_CONTAINER_NAME="ci-pip" +RUN_ID="${GITHUB_RUN_ID:-local}" +PIP_CONTAINER_NAME="ci-pip-${RUN_ID}" SOCKET_DIR="${XDG_RUNTIME_DIR}/podman-host" SOCKET_PATH="${SOCKET_DIR}/podman.sock" PODMAN_IMAGE="quay.io/podman/stable:latest" @@ -38,8 +39,8 @@ fi echo "🔒 Setting secure socket permissions..." chmod 660 "${SOCKET_PATH}" -# Create ephemeral PiP container with maximum security -echo "🐳 Creating secure PiP container..." +# Create ephemeral PiP container as client only (no inner daemon) +echo "🐳 Creating secure PiP client container..." podman run -d \ --name "${PIP_CONTAINER_NAME}" \ --security-opt=no-new-privileges \ @@ -49,12 +50,13 @@ podman run -d \ --tmpfs /run:rw,size=64M \ --tmpfs /tmp:rw,size=256M \ -v "${SOCKET_PATH}:/var/run/podman.sock" \ + -e CONTAINER_HOST="unix:///var/run/podman.sock" \ "${PODMAN_IMAGE}" \ - podman system service --time=0 unix:///var/run/podman.sock + sleep infinity # Wait for container to start echo "⏳ Waiting for PiP container to start..." -sleep 5 +sleep 3 # Verify container is running if ! podman inspect "${PIP_CONTAINER_NAME}" --format '{{.State.Status}}' | grep -q running; then @@ -64,19 +66,7 @@ if ! podman inspect "${PIP_CONTAINER_NAME}" --format '{{.State.Status}}' | grep exit 1 fi -# Kill the background host service (PiP container now handles requests) -echo "🔄 Switching to PiP container for Podman operations..." -kill ${HOST_PODMAN_PID} 2>/dev/null || true - -# Test PiP connectivity -echo "✅ Testing PiP connectivity..." -if ! podman exec "${PIP_CONTAINER_NAME}" podman version >/dev/null 2>&1; then - echo "❌ ERROR: PiP container not responding to Podman commands" - podman logs "${PIP_CONTAINER_NAME}" || true - exit 1 -fi - -echo "🎉 Secure PiP container setup complete!" +echo "🎉 Secure PiP client container setup complete!" echo " Container: ${PIP_CONTAINER_NAME}" echo " Socket: ${SOCKET_PATH}" -echo " Security: No network, no capabilities, read-only rootfs" \ No newline at end of file +echo " Security: No network, no capabilities, read-only rootfs, client-only" \ No newline at end of file