Files
magistr/scripts/check-artifact-pinning.sh
2026-07-19 20:16:12 +03:00

153 lines
5.9 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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'