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:
|
on:
|
||||||
push:
|
push:
|
||||||
|
@ -10,6 +10,7 @@ env:
|
||||||
REGISTRY: ${{ secrets.REGISTRY_HOST }}
|
REGISTRY: ${{ secrets.REGISTRY_HOST }}
|
||||||
APP_NAME: ${{ secrets.APP_NAME }}
|
APP_NAME: ${{ secrets.APP_NAME }}
|
||||||
IMAGE_TAG: ${{ github.sha }}
|
IMAGE_TAG: ${{ github.sha }}
|
||||||
|
RUN_ID: ${{ github.run_id }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test-backend:
|
test-backend:
|
||||||
|
@ -28,57 +29,55 @@ jobs:
|
||||||
chmod +x ./pip_ready.sh
|
chmod +x ./pip_ready.sh
|
||||||
./pip_ready.sh
|
./pip_ready.sh
|
||||||
|
|
||||||
- name: Setup SSH for production deployment
|
- name: Setup SSH with pinned known_hosts
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
||||||
chmod 600 ~/.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: |
|
run: |
|
||||||
podman exec ci-pip podman login ${{ secrets.REGISTRY_HOST }} \
|
podman exec ci-pip-$RUN_ID podman network create integ-$RUN_ID
|
||||||
-u ${{ secrets.REGISTRY_USERNAME }} \
|
|
||||||
-p ${{ secrets.REGISTRY_TOKEN }}
|
|
||||||
|
|
||||||
- name: Start PostgreSQL for integration tests
|
- name: Start PostgreSQL on internal network
|
||||||
run: |
|
run: |
|
||||||
podman exec ci-pip podman run -d \
|
podman exec ci-pip-$RUN_ID podman run -d \
|
||||||
--name test-postgres \
|
--name test-postgres \
|
||||||
|
--network integ-$RUN_ID \
|
||||||
-e POSTGRES_PASSWORD=testpassword \
|
-e POSTGRES_PASSWORD=testpassword \
|
||||||
-e POSTGRES_USER=testuser \
|
-e POSTGRES_USER=testuser \
|
||||||
-e POSTGRES_DB=testdb \
|
-e POSTGRES_DB=testdb \
|
||||||
-p 5432:5432 \
|
|
||||||
postgres:15-alpine
|
postgres:15-alpine
|
||||||
|
|
||||||
- name: Wait for PostgreSQL to be ready
|
- name: Wait for PostgreSQL to be ready
|
||||||
run: |
|
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
|
- name: Run backend unit tests
|
||||||
run: |
|
run: |
|
||||||
podman exec ci-pip podman run --rm \
|
podman exec ci-pip-$RUN_ID podman run --rm \
|
||||||
-v $(pwd):/workspace \
|
-v $(pwd):/workspace \
|
||||||
-w /workspace \
|
-w /workspace \
|
||||||
rust:latest \
|
rust:latest \
|
||||||
sh -c "cargo test --lib -- --test-threads=1"
|
sh -c "cargo test --lib -- --test-threads=1"
|
||||||
|
|
||||||
- name: Run backend integration tests
|
- name: Run backend integration tests
|
||||||
env:
|
|
||||||
DATABASE_URL: postgres://testuser:testpassword@localhost:5432/testdb
|
|
||||||
run: |
|
run: |
|
||||||
podman exec ci-pip podman run --rm \
|
podman exec ci-pip-$RUN_ID podman run --rm \
|
||||||
-v $(pwd):/workspace \
|
-v $(pwd):/workspace \
|
||||||
-w /workspace \
|
-w /workspace \
|
||||||
-e DATABASE_URL="$DATABASE_URL" \
|
-e DATABASE_URL=postgres://testuser:testpassword@test-postgres:5432/testdb \
|
||||||
rust:latest \
|
rust:latest \
|
||||||
sh -c "cargo test --test '*' -- --test-threads=1"
|
sh -c "cargo test --test '*' -- --test-threads=1"
|
||||||
|
|
||||||
- name: Cleanup test database
|
- name: Cleanup test resources
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
podman exec ci-pip podman stop test-postgres 2>/dev/null || true
|
podman exec ci-pip-$RUN_ID 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 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:
|
test-frontend:
|
||||||
runs-on: [self-hosted, ci]
|
runs-on: [self-hosted, ci]
|
||||||
|
@ -99,7 +98,7 @@ jobs:
|
||||||
|
|
||||||
- name: Run frontend tests in PiP
|
- name: Run frontend tests in PiP
|
||||||
run: |
|
run: |
|
||||||
podman exec ci-pip podman run --rm \
|
podman exec ci-pip-$RUN_ID podman run --rm \
|
||||||
-v $(pwd):/workspace \
|
-v $(pwd):/workspace \
|
||||||
-w /workspace \
|
-w /workspace \
|
||||||
node:20 \
|
node:20 \
|
||||||
|
@ -122,21 +121,21 @@ jobs:
|
||||||
chmod +x ./pip_ready.sh
|
chmod +x ./pip_ready.sh
|
||||||
./pip_ready.sh
|
./pip_ready.sh
|
||||||
|
|
||||||
- name: Login to Forgejo Container Registry
|
- name: Login to Forgejo Container Registry securely
|
||||||
run: |
|
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 }} \
|
-u ${{ secrets.REGISTRY_USERNAME }} \
|
||||||
-p ${{ secrets.REGISTRY_TOKEN }}
|
--password-stdin
|
||||||
|
|
||||||
- name: Build backend image
|
- name: Build backend image
|
||||||
run: |
|
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 }} \
|
-t ${{ secrets.REGISTRY_HOST }}/${{ secrets.APP_NAME }}/backend:${{ github.sha }} \
|
||||||
-f Dockerfile.backend .
|
-f Dockerfile.backend .
|
||||||
|
|
||||||
- name: Push backend image
|
- name: Push backend image
|
||||||
run: |
|
run: |
|
||||||
podman exec ci-pip podman push \
|
podman exec ci-pip-$RUN_ID podman push \
|
||||||
${{ secrets.REGISTRY_HOST }}/${{ secrets.APP_NAME }}/backend:${{ github.sha }}
|
${{ secrets.REGISTRY_HOST }}/${{ secrets.APP_NAME }}/backend:${{ github.sha }}
|
||||||
|
|
||||||
build-frontend:
|
build-frontend:
|
||||||
|
@ -156,21 +155,21 @@ jobs:
|
||||||
chmod +x ./pip_ready.sh
|
chmod +x ./pip_ready.sh
|
||||||
./pip_ready.sh
|
./pip_ready.sh
|
||||||
|
|
||||||
- name: Login to Forgejo Container Registry
|
- name: Login to Forgejo Container Registry securely
|
||||||
run: |
|
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 }} \
|
-u ${{ secrets.REGISTRY_USERNAME }} \
|
||||||
-p ${{ secrets.REGISTRY_TOKEN }}
|
--password-stdin
|
||||||
|
|
||||||
- name: Build frontend image
|
- name: Build frontend image
|
||||||
run: |
|
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 }} \
|
-t ${{ secrets.REGISTRY_HOST }}/${{ secrets.APP_NAME }}/frontend:${{ github.sha }} \
|
||||||
-f Dockerfile.frontend .
|
-f Dockerfile.frontend .
|
||||||
|
|
||||||
- name: Push frontend image
|
- name: Push frontend image
|
||||||
run: |
|
run: |
|
||||||
podman exec ci-pip podman push \
|
podman exec ci-pip-$RUN_ID podman push \
|
||||||
${{ secrets.REGISTRY_HOST }}/${{ secrets.APP_NAME }}/frontend:${{ github.sha }}
|
${{ secrets.REGISTRY_HOST }}/${{ secrets.APP_NAME }}/frontend:${{ github.sha }}
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
|
@ -178,7 +177,7 @@ jobs:
|
||||||
needs: [build-backend, build-frontend]
|
needs: [build-backend, build-frontend]
|
||||||
if: always()
|
if: always()
|
||||||
steps:
|
steps:
|
||||||
- name: Cleanup PiP container
|
- name: Cleanup PiP container and socket
|
||||||
run: |
|
run: |
|
||||||
podman rm -f ci-pip 2>/dev/null || true
|
podman rm -f ci-pip-$RUN_ID 2>/dev/null || true
|
||||||
rm -f /tmp/podman.sock 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
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# pip_ready.sh - Readiness probe for PiP container
|
# pip_ready.sh - Socket-only readiness probe for PiP container
|
||||||
# Checks if the Podman-in-Podman container is ready for CI operations
|
# Checks if PiP container can connect to host Podman via UNIX socket
|
||||||
|
|
||||||
PIP_CONTAINER_NAME="ci-pip"
|
RUN_ID="${GITHUB_RUN_ID:-local}"
|
||||||
MAX_RETRIES=30
|
PIP_CONTAINER_NAME="ci-pip-${RUN_ID}"
|
||||||
|
MAX_RETRIES=15
|
||||||
RETRY_DELAY=2
|
RETRY_DELAY=2
|
||||||
|
|
||||||
# Function to check PiP readiness
|
# Function to check PiP socket connectivity only
|
||||||
check_pip_ready() {
|
check_pip_ready() {
|
||||||
echo "🔍 Checking PiP container readiness..."
|
echo "🔍 Checking PiP container socket connectivity..."
|
||||||
|
|
||||||
# Check if container exists and is running
|
# Check if container exists and is running
|
||||||
if ! podman inspect "${PIP_CONTAINER_NAME}" --format '{{.State.Status}}' 2>/dev/null | grep -q running; then
|
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
|
return 1
|
||||||
fi
|
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
|
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
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Test image pulling capability (network test)
|
# Test version command (socket connectivity verification)
|
||||||
if ! podman exec "${PIP_CONTAINER_NAME}" podman pull --quiet alpine:latest >/dev/null 2>&1; then
|
if ! podman exec "${PIP_CONTAINER_NAME}" podman version >/dev/null 2>&1; then
|
||||||
echo "⚠️ PiP container ready but network access test failed"
|
echo "⚠️ PiP container socket connection unstable"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Clean up test image
|
echo "✅ PiP container ready with socket connectivity"
|
||||||
podman exec "${PIP_CONTAINER_NAME}" podman rmi alpine:latest 2>/dev/null || true
|
|
||||||
|
|
||||||
echo "✅ PiP container ready and fully operational"
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,13 +43,13 @@ while [[ ${attempt} -le ${MAX_RETRIES} ]]; do
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
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}
|
sleep ${RETRY_DELAY}
|
||||||
attempt=$((attempt + 1))
|
attempt=$((attempt + 1))
|
||||||
done
|
done
|
||||||
|
|
||||||
# If we reach here, all retries failed
|
# 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:"
|
echo "📋 Container status:"
|
||||||
podman ps -a --filter "name=${PIP_CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" || true
|
podman ps -a --filter "name=${PIP_CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" || true
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# secure_pip_setup.sh - Idempotent setup for ephemeral Podman-in-Podman container
|
# secure_pip_setup.sh - Secure PiP client container setup
|
||||||
# This script creates a secure PiP container for CI operations with no network exposure
|
# Creates ephemeral PiP container that connects to host Podman via UNIX socket
|
||||||
|
|
||||||
# Configuration
|
# 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_DIR="${XDG_RUNTIME_DIR}/podman-host"
|
||||||
SOCKET_PATH="${SOCKET_DIR}/podman.sock"
|
SOCKET_PATH="${SOCKET_DIR}/podman.sock"
|
||||||
PODMAN_IMAGE="quay.io/podman/stable:latest"
|
PODMAN_IMAGE="quay.io/podman/stable:latest"
|
||||||
|
@ -38,8 +39,8 @@ fi
|
||||||
echo "🔒 Setting secure socket permissions..."
|
echo "🔒 Setting secure socket permissions..."
|
||||||
chmod 660 "${SOCKET_PATH}"
|
chmod 660 "${SOCKET_PATH}"
|
||||||
|
|
||||||
# Create ephemeral PiP container with maximum security
|
# Create ephemeral PiP container as client only (no inner daemon)
|
||||||
echo "🐳 Creating secure PiP container..."
|
echo "🐳 Creating secure PiP client container..."
|
||||||
podman run -d \
|
podman run -d \
|
||||||
--name "${PIP_CONTAINER_NAME}" \
|
--name "${PIP_CONTAINER_NAME}" \
|
||||||
--security-opt=no-new-privileges \
|
--security-opt=no-new-privileges \
|
||||||
|
@ -49,12 +50,13 @@ podman run -d \
|
||||||
--tmpfs /run:rw,size=64M \
|
--tmpfs /run:rw,size=64M \
|
||||||
--tmpfs /tmp:rw,size=256M \
|
--tmpfs /tmp:rw,size=256M \
|
||||||
-v "${SOCKET_PATH}:/var/run/podman.sock" \
|
-v "${SOCKET_PATH}:/var/run/podman.sock" \
|
||||||
|
-e CONTAINER_HOST="unix:///var/run/podman.sock" \
|
||||||
"${PODMAN_IMAGE}" \
|
"${PODMAN_IMAGE}" \
|
||||||
podman system service --time=0 unix:///var/run/podman.sock
|
sleep infinity
|
||||||
|
|
||||||
# Wait for container to start
|
# Wait for container to start
|
||||||
echo "⏳ Waiting for PiP container to start..."
|
echo "⏳ Waiting for PiP container to start..."
|
||||||
sleep 5
|
sleep 3
|
||||||
|
|
||||||
# Verify container is running
|
# Verify container is running
|
||||||
if ! podman inspect "${PIP_CONTAINER_NAME}" --format '{{.State.Status}}' | grep -q running; then
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Kill the background host service (PiP container now handles requests)
|
echo "🎉 Secure PiP client container setup complete!"
|
||||||
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 " Container: ${PIP_CONTAINER_NAME}"
|
echo " Container: ${PIP_CONTAINER_NAME}"
|
||||||
echo " Socket: ${SOCKET_PATH}"
|
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