153 lines
5.9 KiB
Bash
Executable File
153 lines
5.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
|
||
set -euo pipefail
|
||
|
||
PROJECT_ROOT=${PROJECT_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}
|
||
WORKFLOW="$PROJECT_ROOT/.gitea/workflows/docker-build.yaml"
|
||
|
||
failures=0
|
||
|
||
fail() {
|
||
printf '[АРТЕФАКТЫ] Ошибка: %s\n' "$1" >&2
|
||
failures=$((failures + 1))
|
||
}
|
||
|
||
require_file() {
|
||
local file=$1
|
||
if [[ ! -f "$file" ]]; then
|
||
fail "не найден обязательный файл ${file#"$PROJECT_ROOT"/}"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
check_dockerfile() {
|
||
local file=$1
|
||
local line
|
||
|
||
require_file "$file" || return
|
||
while IFS= read -r line; do
|
||
if [[ ! "$line" =~ ^FROM[[:space:]]+[^[:space:]]+@sha256:[0-9a-f]{64}([[:space:]]+AS[[:space:]]+[A-Za-z0-9._-]+)?$ ]]; then
|
||
fail "базовый образ в ${file#"$PROJECT_ROOT"/} должен содержать полный sha256 digest: $line"
|
||
fi
|
||
done < <(grep -E '^FROM[[:space:]]+' "$file")
|
||
}
|
||
|
||
check_compose_images() {
|
||
local line
|
||
|
||
require_file "$PROJECT_ROOT/compose.yaml" || return
|
||
while IFS= read -r line; do
|
||
if [[ ! "$line" =~ image:[[:space:]]+[^[:space:]]+@sha256:[0-9a-f]{64}[[:space:]]*$ ]]; then
|
||
fail "образ Compose должен содержать полный sha256 digest: ${line#*image: }"
|
||
fi
|
||
done < <(grep -E '^[[:space:]]+image:[[:space:]]+' "$PROJECT_ROOT/compose.yaml")
|
||
}
|
||
|
||
check_actions() {
|
||
local action_ref
|
||
|
||
require_file "$WORKFLOW" || return
|
||
while IFS= read -r action_ref; do
|
||
if [[ ! "$action_ref" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+@[0-9a-f]{40}$ ]]; then
|
||
fail "Action должен быть закреплён полным commit SHA: $action_ref"
|
||
fi
|
||
done < <(sed -nE 's/^[[:space:]]+uses:[[:space:]]+([^[:space:]#]+).*/\1/p' "$WORKFLOW")
|
||
}
|
||
|
||
require_count() {
|
||
local pattern=$1
|
||
local expected=$2
|
||
local description=$3
|
||
local actual
|
||
|
||
actual=$(grep -Ec "$pattern" "$WORKFLOW" || true)
|
||
if (( actual < expected )); then
|
||
fail "$description: ожидается не менее $expected, найдено $actual"
|
||
fi
|
||
}
|
||
|
||
check_workflow_gates() {
|
||
require_file "$WORKFLOW" || return
|
||
|
||
require_count '^[[:space:]]+sbom:[[:space:]]+true$' 2 \
|
||
"для обоих образов должна публиковаться SBOM attestation"
|
||
require_count '^[[:space:]]+provenance:[[:space:]]+mode=max$' 2 \
|
||
"для обоих образов должна публиковаться provenance attestation"
|
||
require_count 'image --scanners vuln --exit-code 1 --severity HIGH,CRITICAL' 2 \
|
||
"оба образа должны проходить блокирующее HIGH/CRITICAL-сканирование"
|
||
require_count 'TRIVY_IMAGE: [^[:space:]]+@sha256:[0-9a-f]{64}$' 1 \
|
||
"образ Trivy должен быть закреплён digest"
|
||
require_count 'MAVEN_IMAGE: [^[:space:]]+@sha256:[0-9a-f]{64}$' 1 \
|
||
"образ Maven для CI должен быть закреплён digest"
|
||
require_count 'NODE_IMAGE: [^[:space:]]+@sha256:[0-9a-f]{64}$' 1 \
|
||
"образ Node.js для CI должен быть закреплён digest"
|
||
|
||
if ! grep -Eq '^ needs: \[build-and-push-backend, build-and-push-frontend, security-scan\]$' "$WORKFLOW"; then
|
||
fail "deploy обязан зависеть от успешного security-scan"
|
||
fi
|
||
|
||
if ! grep -Eq '^ KUBECTL_VERSION: v[0-9]+\.[0-9]+\.[0-9]+$' "$WORKFLOW" \
|
||
|| ! grep -Eq '^ KUBECTL_SHA256: [0-9a-f]{64}$' "$WORKFLOW" \
|
||
|| ! grep -Fq 'sha256sum --check --strict' "$WORKFLOW"; then
|
||
fail "kubectl должен иметь точную версию и обязательную SHA-256 проверку"
|
||
fi
|
||
}
|
||
|
||
check_downloaded_backend_artifact() {
|
||
local dockerfile="$PROJECT_ROOT/backend/Dockerfile"
|
||
|
||
require_file "$dockerfile" || return
|
||
if ! grep -Eq '^ARG OTEL_JAVAAGENT_VERSION=[0-9]+\.[0-9]+\.[0-9]+$' "$dockerfile" \
|
||
|| ! grep -Eq '^ARG OTEL_JAVAAGENT_SHA256=[0-9a-f]{64}$' "$dockerfile" \
|
||
|| ! grep -Fq 'sha256sum -c -' "$dockerfile"; then
|
||
fail "OpenTelemetry Java Agent должен иметь точную версию и SHA-256 проверку"
|
||
fi
|
||
}
|
||
|
||
check_testcontainer_images() {
|
||
local image_catalog="$PROJECT_ROOT/backend/src/test/java/com/magistr/app/testing/TestContainerImages.java"
|
||
|
||
[[ -d "$PROJECT_ROOT/backend/src/test/java" ]] || return 0
|
||
require_file "$image_catalog" || return
|
||
if ! grep -Eq 'postgres:[^"[:space:]]+@sha256:[0-9a-f]{64}' "$image_catalog" \
|
||
|| ! grep -Fq 'asCompatibleSubstituteFor("postgres")' "$image_catalog"; then
|
||
fail "образ PostgreSQL Testcontainers должен содержать digest и явную совместимость"
|
||
fi
|
||
}
|
||
|
||
check_optional_kubernetes_images() {
|
||
local k8s_dir=${K8S_DIR:-}
|
||
local line
|
||
|
||
[[ -n "$k8s_dir" ]] || return 0
|
||
if [[ ! -d "$k8s_dir" ]]; then
|
||
fail "каталог Kubernetes не найден: $k8s_dir"
|
||
return
|
||
fi
|
||
|
||
while IFS= read -r line; do
|
||
if [[ "$line" =~ image:[[:space:]]+([^[:space:]]+) ]]; then
|
||
local image_ref=${BASH_REMATCH[1]}
|
||
if [[ ! "$image_ref" =~ @sha256:[0-9a-f]{64}$ ]]; then
|
||
fail "production-образ Kubernetes должен содержать digest: $image_ref"
|
||
fi
|
||
fi
|
||
done < <(grep -R -h -E '^[[:space:]]+image:[[:space:]]+' "$k8s_dir" --include='*.yaml' --include='*.yml' || true)
|
||
}
|
||
|
||
check_dockerfile "$PROJECT_ROOT/backend/Dockerfile"
|
||
check_dockerfile "$PROJECT_ROOT/frontend/Dockerfile"
|
||
check_compose_images
|
||
check_actions
|
||
check_workflow_gates
|
||
check_downloaded_backend_artifact
|
||
check_testcontainer_images
|
||
check_optional_kubernetes_images
|
||
|
||
if (( failures > 0 )); then
|
||
printf '[АРТЕФАКТЫ] Проверка завершена с ошибками: %d\n' "$failures" >&2
|
||
exit 1
|
||
fi
|
||
|
||
printf '[АРТЕФАКТЫ] Все версии, digest, checksum, SBOM и scan gates проверены\n'
|