220 lines
6.7 KiB
YAML
220 lines
6.7 KiB
YAML
name: Flask CI/CD Pipeline
|
|
|
|
on:
|
|
push:
|
|
branches: [ main ]
|
|
|
|
env:
|
|
IMAGE_NAME: git.onlionel.com/lionel/arcade
|
|
|
|
jobs:
|
|
format-and-auto-fix:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.11"
|
|
|
|
- name: Install formatting dependencies
|
|
run: |
|
|
python -m venv venv
|
|
. venv/bin/activate
|
|
pip install --upgrade pip
|
|
pip install -r requirements.txt
|
|
pip install ruff
|
|
|
|
- name: Run Ruff auto-fix and formatting
|
|
run: |
|
|
. venv/bin/activate
|
|
python -m ruff check . --fix --exclude venv,.venv
|
|
python -m ruff format . --exclude venv,.venv
|
|
|
|
- name: Commit and push formatting changes
|
|
run: |
|
|
if [ -n "$(git status --porcelain)" ]; then
|
|
git config --global user.name "gitea-actions[bot]"
|
|
git config --global user.email "gitea-actions[bot]@local"
|
|
git add .
|
|
git commit -m "Auto-fix code style [skip ci]"
|
|
git push
|
|
else
|
|
echo "No formatting changes to commit"
|
|
fi
|
|
|
|
test-and-verify:
|
|
needs: format-and-auto-fix
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Scan repository and git history for leaked secrets with Gitleaks
|
|
run: |
|
|
docker run --rm \
|
|
-v "$PWD":/repo \
|
|
ghcr.io/gitleaks/gitleaks:latest \
|
|
git /repo --verbose
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.11"
|
|
|
|
- name: Install test and security dependencies
|
|
run: |
|
|
python -m venv venv
|
|
. venv/bin/activate
|
|
pip install --upgrade pip
|
|
pip install -r requirements.txt
|
|
pip install pytest pytest-cov pip-audit
|
|
|
|
- name: Run pytest with coverage
|
|
run: |
|
|
. venv/bin/activate
|
|
PYTHONPATH=. pytest --cov=. --cov-report=term-missing
|
|
|
|
- name: Run lightweight input fuzzing against Flask test client
|
|
run: |
|
|
. venv/bin/activate
|
|
python - <<'PY'
|
|
import random
|
|
import string
|
|
from app import app
|
|
|
|
client = app.test_client()
|
|
|
|
samples = [
|
|
"",
|
|
"abc",
|
|
"🔥",
|
|
"één test",
|
|
"<script>alert(1)</script>",
|
|
"\x00",
|
|
" " * 100,
|
|
"a" * 10000,
|
|
"🙂🙃😅",
|
|
"漢字かなカナ",
|
|
"\n\t\r",
|
|
]
|
|
|
|
alphabet = string.ascii_letters + string.digits + string.punctuation + " 🙂🔥éäöü漢字\n\t"
|
|
|
|
for _ in range(100):
|
|
size = random.randint(0, 512)
|
|
samples.append("".join(random.choice(alphabet) for _ in range(size)))
|
|
|
|
for value in samples:
|
|
response = client.post("/", data={"tekst": value})
|
|
assert response.status_code == 200, f"Unexpected status code for input length {len(value)}"
|
|
body = response.get_data(as_text=True)
|
|
assert "Spiegelbeeld:" in body, "Expected response marker not found"
|
|
|
|
print(f"Lightweight fuzzing completed successfully for {len(samples)} inputs.")
|
|
PY
|
|
|
|
- name: Scan Python dependencies with pip-audit
|
|
run: |
|
|
. venv/bin/activate
|
|
pip-audit
|
|
|
|
- name: Start Flask app and run smoke test
|
|
run: |
|
|
. venv/bin/activate
|
|
python app.py &
|
|
APP_PID=$!
|
|
sleep 5
|
|
curl -f http://127.0.0.1:5000
|
|
kill $APP_PID
|
|
|
|
build-scan-and-push-image:
|
|
needs: test-and-verify
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
image_tag: ${{ steps.meta.outputs.image_tag }}
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set image tag
|
|
id: meta
|
|
run: |
|
|
SHORT_SHA=$(echo "${{ gitea.sha }}" | cut -c1-7)
|
|
echo "image_tag=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Log in to Gitea container registry
|
|
run: |
|
|
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login git.onlionel.com \
|
|
-u "${{ secrets.REGISTRY_USERNAME }}" \
|
|
--password-stdin
|
|
|
|
- name: Build Docker images
|
|
run: |
|
|
docker build --pull -t $IMAGE_NAME:latest .
|
|
docker build --pull -t $IMAGE_NAME:${{ steps.meta.outputs.image_tag }} .
|
|
|
|
- name: Verify Flask is available in container image
|
|
run: |
|
|
docker run --rm $IMAGE_NAME:latest python -c "import flask"
|
|
|
|
- name: Clean up old test containers
|
|
run: |
|
|
docker stop arcade-test || true
|
|
docker rm -f arcade-test || true
|
|
|
|
- name: Install Trivy
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y wget gnupg lsb-release apt-transport-https
|
|
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
|
|
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list
|
|
sudo apt-get update
|
|
sudo apt-get install -y trivy
|
|
|
|
- name: Scan container image with Trivy
|
|
run: |
|
|
trivy image --exit-code 0 --severity HIGH,CRITICAL $IMAGE_NAME:latest
|
|
|
|
- name: Push container images
|
|
run: |
|
|
docker push $IMAGE_NAME:latest
|
|
docker push $IMAGE_NAME:${{ steps.meta.outputs.image_tag }}
|
|
|
|
deploy-to-k3s:
|
|
needs: build-scan-and-push-image
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install kubectl
|
|
run: |
|
|
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
|
|
chmod +x kubectl
|
|
sudo mv kubectl /usr/local/bin/kubectl
|
|
kubectl version --client
|
|
|
|
- name: Configure kubeconfig
|
|
run: |
|
|
mkdir -p ~/.kube
|
|
echo "${{ secrets.KUBECONFIG_B64 }}" | base64 -d > ~/.kube/config
|
|
chmod 600 ~/.kube/config
|
|
|
|
- name: Render manifest with image tag
|
|
run: |
|
|
sed "s|image: .*|image: ${IMAGE_NAME}:${{ needs.build-scan-and-push-image.outputs.image_tag }}|g" arcade.yaml > rendered-arcade.yaml
|
|
|
|
- name: Apply Kubernetes manifest
|
|
run: |
|
|
kubectl apply -f rendered-arcade.yaml
|
|
|
|
- name: Wait for rollout
|
|
run: |
|
|
kubectl -n devsecops rollout status deployment/arcade --timeout=180s |