Security improvements #1
Some checks are pending
CI/CD Pipeline with Secure Ephemeral PiP / test-backend (push) Waiting to run
CI/CD Pipeline with Secure Ephemeral PiP / test-frontend (push) Blocked by required conditions
CI/CD Pipeline with Secure Ephemeral PiP / build-backend (push) Blocked by required conditions
CI/CD Pipeline with Secure Ephemeral PiP / build-frontend (push) Blocked by required conditions
CI/CD Pipeline with Secure Ephemeral PiP / cleanup (push) Blocked by required conditions

This commit is contained in:
continuist 2025-09-04 22:15:49 -04:00
parent 4270c036f6
commit 6624c2a340
3 changed files with 58 additions and 71 deletions

View file

@ -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
podman rm -f ci-pip-$RUN_ID 2>/dev/null || true
rm -f ${XDG_RUNTIME_DIR}/podman-host/podman.sock 2>/dev/null || true

View file

@ -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

View file

@ -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"
echo " Security: No network, no capabilities, read-only rootfs, client-only"