name: Deploy on: push: branches: - main env: GIT_HOST: git.jakach.ch GIT_REPO: jakach/jakach-login GIT_BRANCH: main APP_NAME: auth APP_DOMAIN: auth.jakach.ch APP_PORT: 447 SECURITY_SCAN_ENABLED: ${{ vars.SECURITY_SCAN_ENABLED }} CODE_SCAN_ENABLED: ${{ vars.CODE_SCAN_ENABLED }} TRIVY_SEVERITY: HIGH,CRITICAL TRIVY_IMAGE_SCANNERS: vuln TRIVY_VEX: repo TRIVY_FS_SCANNERS: vuln,misconfig,secret SEMGREP_CONFIG: p/default jobs: security_scan: runs-on: ubuntu-latest env: GIT_USER: ${{ vars.GIT_USER }} GIT_TOKEN: ${{ secrets.GIT_TOKEN }} TRIVY_REGISTRY_USER: ${{ vars.TRIVY_REGISTRY_USER }} TRIVY_REGISTRY_PASSWORD: ${{ secrets.TRIVY_REGISTRY_PASSWORD }} steps: - name: Scan Docker images for vulnerabilities run: | set -Eeuo pipefail case "${SECURITY_SCAN_ENABLED:-true}" in false|False|FALSE|0|no|No|NO|off|Off|OFF) echo "Security scan disabled by SECURITY_SCAN_ENABLED=${SECURITY_SCAN_ENABLED}" exit 0 ;; esac : "${GIT_USER:?GIT_USER is required}" : "${GIT_TOKEN:?GIT_TOKEN is required}" if command -v apk >/dev/null 2>&1; then apk add --no-cache ca-certificates curl git elif command -v apt-get >/dev/null 2>&1; then apt-get update apt-get install -y ca-certificates curl git elif command -v dnf >/dev/null 2>&1; then dnf install -y ca-certificates curl git elif command -v yum >/dev/null 2>&1; then yum install -y ca-certificates curl git else echo "Unsupported package manager" exit 1 fi if ! command -v trivy >/dev/null 2>&1; then mkdir -p "$HOME/.local/bin" curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \ | sh -s -- -b "$HOME/.local/bin" export PATH="$HOME/.local/bin:$PATH" fi REPO_URL="https://${GIT_USER}:${GIT_TOKEN}@${GIT_HOST}/${GIT_REPO}" git clone \ --branch "$GIT_BRANCH" \ "$REPO_URL" \ source cd source COMPOSE_FILE="" for file in docker-compose.yml compose.yml compose.yaml; do if [ -f "$file" ]; then COMPOSE_FILE="$file" break fi done if [ -z "$COMPOSE_FILE" ]; then echo "No docker compose file found" exit 0 fi if docker compose version >/dev/null 2>&1; then docker compose -f "$COMPOSE_FILE" config --images > images.txt else awk ' /^[[:space:]]*image:[[:space:]]*/ { sub(/^[[:space:]]*image:[[:space:]]*/, "") gsub(/["\047]/, "") print } ' "$COMPOSE_FILE" > images.txt fi sort -u images.txt -o images.txt if [ ! -s images.txt ]; then echo "No Docker images found to scan" exit 0 fi if [ -n "${TRIVY_REGISTRY_USER:-}" ] || [ -n "${TRIVY_REGISTRY_PASSWORD:-}" ]; then : "${TRIVY_REGISTRY_USER:?TRIVY_REGISTRY_USER is required when TRIVY_REGISTRY_PASSWORD is set}" : "${TRIVY_REGISTRY_PASSWORD:?TRIVY_REGISTRY_PASSWORD is required when TRIVY_REGISTRY_USER is set}" echo "Using registry credentials from TRIVY_REGISTRY_USER/TRIVY_REGISTRY_PASSWORD" fi TRIVY_IGNORE_ARGS="" if [ -f cve_blacklist.txt ]; then awk ' /^[[:space:]]*($|#)/ { next } { print $1 } ' cve_blacklist.txt > .trivyignore if [ -s .trivyignore ]; then TRIVY_IGNORE_ARGS="--ignorefile .trivyignore" echo "Using CVE blacklist from cve_blacklist.txt" fi fi echo "Scanning Docker images for ${TRIVY_SEVERITY} vulnerabilities:" cat images.txt failed=0 while IFS= read -r image; do [ -n "$image" ] || continue echo "" echo "Scanning ${image}" if [ -n "${TRIVY_REGISTRY_USER:-}" ]; then if ! trivy \ image \ --username "${TRIVY_REGISTRY_USER}" \ --password "${TRIVY_REGISTRY_PASSWORD}" \ --scanners "${TRIVY_IMAGE_SCANNERS}" \ --vex "${TRIVY_VEX}" \ --exit-code 1 \ --severity "${TRIVY_SEVERITY}" \ --ignore-unfixed \ ${TRIVY_IGNORE_ARGS} \ --no-progress \ "${image}"; then failed=1 fi elif ! trivy \ image \ --scanners "${TRIVY_IMAGE_SCANNERS}" \ --vex "${TRIVY_VEX}" \ --exit-code 1 \ --severity "${TRIVY_SEVERITY}" \ --ignore-unfixed \ ${TRIVY_IGNORE_ARGS} \ --no-progress \ "${image}"; then failed=1 fi done < images.txt if [ "$failed" -ne 0 ]; then echo "WARNING: One or more Docker image scans failed or found ${TRIVY_SEVERITY} vulnerabilities. Deployment stopped." exit 1 fi echo "No high or critical vulnerabilities found" code_scan: runs-on: ubuntu-latest env: GIT_USER: ${{ vars.GIT_USER }} GIT_TOKEN: ${{ secrets.GIT_TOKEN }} steps: - name: Scan source code run: | set -Eeuo pipefail case "${CODE_SCAN_ENABLED:-true}" in false|False|FALSE|0|no|No|NO|off|Off|OFF) echo "Code scan disabled by CODE_SCAN_ENABLED=${CODE_SCAN_ENABLED}" exit 0 ;; esac : "${GIT_USER:?GIT_USER is required}" : "${GIT_TOKEN:?GIT_TOKEN is required}" if command -v apk >/dev/null 2>&1; then apk add --no-cache ca-certificates curl git python3 py3-pip elif command -v apt-get >/dev/null 2>&1; then apt-get update apt-get install -y ca-certificates curl git python3 python3-pip python3-venv elif command -v dnf >/dev/null 2>&1; then dnf install -y ca-certificates curl git python3 python3-pip elif command -v yum >/dev/null 2>&1; then yum install -y ca-certificates curl git python3 python3-pip else echo "Unsupported package manager" exit 1 fi if ! command -v semgrep >/dev/null 2>&1; then python3 -m venv "$HOME/.semgrep-venv" . "$HOME/.semgrep-venv/bin/activate" python3 -m pip install --upgrade pip python3 -m pip install semgrep fi if ! command -v trivy >/dev/null 2>&1; then mkdir -p "$HOME/.local/bin" curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \ | sh -s -- -b "$HOME/.local/bin" export PATH="$HOME/.local/bin:$PATH" fi REPO_URL="https://${GIT_USER}:${GIT_TOKEN}@${GIT_HOST}/${GIT_REPO}" git clone \ --branch "$GIT_BRANCH" \ "$REPO_URL" \ source cd source semgrep scan \ --config "${SEMGREP_CONFIG}" \ --error \ --metrics=off trivy fs \ --scanners "${TRIVY_FS_SCANNERS}" \ --exit-code 1 \ --severity "${TRIVY_SEVERITY}" \ --ignore-unfixed \ --no-progress \ . deploy: runs-on: ubuntu-latest needs: - security_scan - code_scan steps: - name: Install dependencies run: | set -e if command -v apk >/dev/null 2>&1; then apk add --no-cache openssh-client git bash elif command -v apt-get >/dev/null 2>&1; then apt-get update apt-get install -y openssh-client git bash elif command -v dnf >/dev/null 2>&1; then dnf install -y openssh-clients git bash elif command -v yum >/dev/null 2>&1; then yum install -y openssh-clients git bash else echo "Unsupported package manager" exit 1 fi - name: Deploy application env: SSH_KEY: ${{ secrets.SSH_KEY }} SSH_USER: ${{ vars.SSH_USER }} SSH_IP: ${{ vars.SSH_IP }} GIT_USER: ${{ vars.GIT_USER }} GIT_TOKEN: ${{ secrets.GIT_TOKEN }} run: | cat > deploy.sh <<'OUTER_EOF' #!/usr/bin/env bash set -Eeuo pipefail : "${SSH_KEY:?SSH_KEY is required}" : "${SSH_USER:?SSH_USER is required}" : "${SSH_IP:?SSH_IP is required}" : "${GIT_USER:?GIT_USER is required}" : "${GIT_TOKEN:?GIT_TOKEN is required}" REPO_NAME="$(basename "$GIT_REPO")" APP_DIR="/srv/systems/${REPO_NAME}" mkdir -p ~/.ssh chmod 700 ~/.ssh printf '%s\n' "$SSH_KEY" | tr -d '\r' > ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key ssh-keyscan -H "$SSH_IP" >> ~/.ssh/known_hosts 2>/dev/null || true ssh \ -i ~/.ssh/deploy_key \ -o StrictHostKeyChecking=yes \ -o IdentitiesOnly=yes \ "$SSH_USER@$SSH_IP" \ " export \ APP_NAME='${APP_NAME}' \ APP_DOMAIN='${APP_DOMAIN}' \ APP_PORT='${APP_PORT}' \ APP_DIR='${APP_DIR}' \ GIT_HOST='${GIT_HOST}' \ GIT_REPO='${GIT_REPO}' \ GIT_BRANCH='${GIT_BRANCH}' \ GIT_USER='${GIT_USER}' \ GIT_TOKEN='${GIT_TOKEN}'; bash -s " <<'REMOTE_EOF' set -Eeuo pipefail REPO_URL="https://${GIT_USER}:${GIT_TOKEN}@${GIT_HOST}/${GIT_REPO}" PROXY_DIR="/srv/systems/proxy" NGINX_CONF="${PROXY_DIR}/nginx_conf/nginx.conf" GET_CERT_SCRIPT="${PROXY_DIR}/get_cert.sh" RENEW_SCRIPT="${PROXY_DIR}/renew_cert.sh" FIRST_DEPLOY=0 PROXY_RESTART_REQUIRED=0 # -------------------------------------------------- # Clone repository if missing # -------------------------------------------------- if [ ! -d "$APP_DIR/.git" ]; then echo "Repository missing, cloning..." FIRST_DEPLOY=1 mkdir -p "$(dirname "$APP_DIR")" git clone \ --branch "$GIT_BRANCH" \ "$REPO_URL" \ "$APP_DIR" fi # -------------------------------------------------- # Update repository # -------------------------------------------------- cd "$APP_DIR" git remote set-url origin "$REPO_URL" git fetch origin "$GIT_BRANCH" git checkout "$GIT_BRANCH" git pull origin "$GIT_BRANCH" # -------------------------------------------------- # Create nginx reverse proxy entry # -------------------------------------------------- if [ "$FIRST_DEPLOY" -eq 1 ]; then if ! grep -q "proxy_pass http://192.168.1.109:${APP_PORT}/;" "$NGINX_CONF"; then echo "Creating nginx entry..." PROXY_RESTART_REQUIRED=1 cat >> "$NGINX_CONF" <> "$RENEW_SCRIPT" <