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
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:
parent
4270c036f6
commit
6624c2a340
3 changed files with 58 additions and 71 deletions
|
@ -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
|
32
pip_ready.sh
32
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
|
||||
|
||||
|
|
|
@ -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"
|
Loading…
Add table
Reference in a new issue