баг-фикс завершён

This commit is contained in:
Zuev
2026-07-19 20:16:12 +03:00
parent bc0e1ab1b4
commit ee876f1acd
43 changed files with 1228 additions and 258 deletions

152
scripts/check-artifact-pinning.sh Executable file
View File

@@ -0,0 +1,152 @@
#!/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'

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
CHECK_SCRIPT="$ROOT_DIR/scripts/check-artifact-pinning.sh"
FIXTURE_DIR=$(mktemp -d)
trap 'rm -rf "$FIXTURE_DIR"' EXIT
mkdir -p "$FIXTURE_DIR/backend" "$FIXTURE_DIR/frontend" "$FIXTURE_DIR/.gitea/workflows"
cp "$ROOT_DIR/backend/Dockerfile" "$FIXTURE_DIR/backend/Dockerfile"
cp "$ROOT_DIR/frontend/Dockerfile" "$FIXTURE_DIR/frontend/Dockerfile"
cp "$ROOT_DIR/compose.yaml" "$FIXTURE_DIR/compose.yaml"
cp "$ROOT_DIR/.gitea/workflows/docker-build.yaml" \
"$FIXTURE_DIR/.gitea/workflows/docker-build.yaml"
PROJECT_ROOT="$FIXTURE_DIR" bash "$CHECK_SCRIPT" >/dev/null
sed -i -E '0,/@sha256:[0-9a-f]{64}/s///' "$FIXTURE_DIR/backend/Dockerfile"
if PROJECT_ROOT="$FIXTURE_DIR" bash "$CHECK_SCRIPT" >/dev/null 2>&1; then
printf 'Ошибка теста: незакреплённый базовый образ не был отклонён\n' >&2
exit 1
fi
cp "$ROOT_DIR/backend/Dockerfile" "$FIXTURE_DIR/backend/Dockerfile"
sed -i '0,/sbom: true/{/sbom: true/d;}' "$FIXTURE_DIR/.gitea/workflows/docker-build.yaml"
if PROJECT_ROOT="$FIXTURE_DIR" bash "$CHECK_SCRIPT" >/dev/null 2>&1; then
printf 'Ошибка теста: отсутствие SBOM для одного образа не было отклонено\n' >&2
exit 1
fi
printf 'Проверка закрепления артефактов завершена успешно\n'