⏱️ وقت القراءة المقدر: 15 دقيقة

أمان الحاويات في منصات الذكاء الاصطناعي السحابية يكتسب أهمية متزايدة. مع تزايد اعتماد الفرق على صور Docker لنشر نماذج الذكاء الاصطناعي وخدمات الاستدلال، أصبح فحص الثغرات الأمنية في هذه الصور خطوة لا غنى عنها في دورة التطوير.

Grype من Anchore هو أداة مفتوحة المصدر لفحص الثغرات في صور الحاويات، تحظى بأكثر من 10,100 نجمة على GitHub. تتميز بسرعتها وقاعدة بياناتها الشاملة وسهولة دمجها في خطوط CI/CD.


نظرة عامة على Grype

الميزات الرئيسية

دعم واسع: تفحص صور Docker وملفات Dockerfile وتوزيعات Linux المختلفة (Alpine, Ubuntu, Debian, CentOS, RHEL, Amazon Linux).

دعم مديري الحزم المتعددين: pip (Python), npm (Node.js), gem (Ruby), jar/war (Java), go modules, cargo (Rust).

قاعدة بيانات ثغرات محدّثة: تتزامن مع NVD وGitHub Advisory وRed Hat Security وDebian Security.

مخرجات مرنة: يدعم تنسيقات JSON, table, cyclonedx, sarif.

دمج CI/CD: يتكامل بسلاسة مع GitHub Actions وGitLab CI وJenkins وغيرها.

بيئة الاختبار

# المواصفات المستخدمة في هذا الدليل
النظام: macOS 15 / Ubuntu 22.04
Docker: 24.0.5
Grype: 0.74.x
Syft: 0.90.x (اختياري، لإنشاء SBOM)

التثبيت

على macOS

# عبر Homebrew (موصى به)
brew install anchore/grype/grype

# التحقق من الإصدار
grype version

على Linux

# باستخدام سكريبت التثبيت الرسمي
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin

# أو عبر APT (Debian/Ubuntu)
# تثبيت المتطلبات أولًا
sudo apt-get install -y wget apt-transport-https gnupg

# إضافة مستودع Anchore
wget -qO - https://artifacts.anchore.io/apt/gpg.key | sudo apt-key add -
echo "deb https://artifacts.anchore.io/apt stable main" | sudo tee /etc/apt/sources.list.d/anchore.list

sudo apt-get update && sudo apt-get install -y grype

عبر Docker

# الفحص بدون تثبيت
docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  anchore/grype:latest \
  your-image:latest

عبر Go

# تثبيت من المصدر
go install github.com/anchore/grype@latest

الاستخدام الأساسي

فحص صورة Docker

# فحص بسيط
grype nginx:latest

# فحص مع مخرجات تفصيلية
grype python:3.11-slim -v

# فحص مع تصفية حسب الخطورة
grype ubuntu:22.04 --fail-on critical

# فحص بمخرجات JSON
grype alpine:3.18 -o json > vulnerabilities.json

تنسيقات المخرجات

# تنسيق الجدول (الافتراضي)
grype nginx:latest -o table

# تنسيق JSON للمعالجة البرمجية
grype nginx:latest -o json

# تنسيق CycloneDX (SBOM)
grype nginx:latest -o cyclonedx-json

# تنسيق SARIF (لـ GitHub Code Scanning)
grype nginx:latest -o sarif

تصفية حسب مستوى الخطورة

# إخفاء الثغرات منخفضة الخطورة
grype nginx:latest --ignore-states = won't-fix

# الفشل فقط عند وجود ثغرات حرجة أو عالية
grype nginx:latest --fail-on high

# فحص مع تعيين مستوى الحد الأدنى
grype nginx:latest --fail-on critical --fail-on high

فحص مجلد محلي

# فحص مجلد المشروع
grype dir:./myproject

# فحص بيئة Python المحلية
grype dir:./venv

# فحص مع تحديد النوع
grype dir:./app --scope all-layers

حالات الاستخدام في منصة الذكاء الاصطناعي السحابية

دمج مع GitHub Actions

# .github/workflows/security-scan.yml
# سير عمل فحص الأمان

name: أمان الحاويات - Grype

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    # فحص يومي في منتصف الليل
    - cron: '0 0 * * *'

jobs:
  scan-images:
    name: فحص الثغرات الأمنية
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        image:
          - name: "واجهة API"
            path: "./services/api"
            tag: "api-service"
          - name: "خدمة الاستدلال"
            path: "./services/inference"
            tag: "inference-service"
          - name: "خدمة النماذج"
            path: "./services/models"
            tag: "model-service"
    
    steps:
      - name: استنساخ المستودع
        uses: actions/checkout@v4
      
      - name: إعداد Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      - name: بناء صورة Docker
        uses: docker/build-push-action@v5
        with:
          context: $
          tags: $:test
          load: true
          cache-from: type=gha
          cache-to: type=gha,mode=max
      
      - name: تثبيت Grype
        uses: anchore/scan-action/download-grype@v3
      
      - name: فحص الثغرات الأمنية
        id: security-scan
        uses: anchore/scan-action@v3
        with:
          image: "$:test"
          fail-build: true
          severity-cutoff: high
          output-format: sarif
        continue-on-error: true
      
      - name: رفع نتائج SARIF إلى GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: $
          category: "container-security-$"
      
      - name: إنشاء تقرير مفصل
        if: failure()
        run: |
          echo "## تقرير الأمان: $" >> $GITHUB_STEP_SUMMARY
          grype $:test \
            --fail-on high \
            -o table | head -50 >> $GITHUB_STEP_SUMMARY
      
      - name: فشل في حالة ثغرات حرجة
        if: steps.security-scan.outcome == 'failure'
        run: exit 1

دمج مع GitLab CI

# .gitlab-ci.yml (قسم الأمان)
# دمج Grype في GitLab CI

variables:
  GRYPE_VERSION: "0.74.0"
  DOCKER_TLS_CERTDIR: ""

stages:
  - build
  - security
  - deploy

# قالب مشترك لفحص الأمان
.security-scan-template: &security-scan
  stage: security
  image: docker:24
  services:
    - docker:24-dind
  before_script:
    - curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin v${GRYPE_VERSION}
  script:
    - grype ${TARGET_IMAGE} --fail-on high -o json > grype-report.json
    - grype ${TARGET_IMAGE} --fail-on high -o table
  artifacts:
    when: always
    reports:
      # SARIF لـ GitLab Security Dashboard
      sast: grype-report.json
    paths:
      - grype-report.json
    expire_in: 30 days

# فحص صورة الإنتاج
scan-production-image:
  <<: *security-scan
  variables:
    TARGET_IMAGE: "${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}"
  needs: ["build-production"]
  only:
    - main
    - tags

وحدة تحكم قبول Kubernetes

# k8s-admission-controller/main.py
# وحدة تحكم قبول Kubernetes مع Grype

from flask import Flask, request, jsonify
import subprocess
import json
import logging
import base64

app = Flask(__name__)
logging.basicConfig(level=logging.INFO)

def scan_image_with_grype(image: str) -> dict:
    """فحص صورة Docker باستخدام Grype"""
    try:
        result = subprocess.run(
            ["grype", image, "-o", "json", "--quiet"],
            capture_output=True,
            text=True,
            timeout=300
        )
        
        if result.returncode != 0 and not result.stdout:
            logging.error(f"خطأ في فحص {image}: {result.stderr}")
            return {"error": result.stderr, "matches": []}
        
        return json.loads(result.stdout)
    except subprocess.TimeoutExpired:
        logging.error(f"انتهت مهلة فحص {image}")
        return {"error": "timeout", "matches": []}
    except Exception as e:
        logging.error(f"استثناء أثناء فحص {image}: {e}")
        return {"error": str(e), "matches": []}

def check_vulnerabilities(scan_result: dict, max_severity: str = "high") -> tuple:
    """التحقق من وجود ثغرات فوق الحد المسموح"""
    severity_levels = {"low": 1, "medium": 2, "high": 3, "critical": 4}
    max_level = severity_levels.get(max_severity.lower(), 3)
    
    violations = []
    for match in scan_result.get("matches", []):
        vuln = match.get("vulnerability", {})
        severity = vuln.get("severity", "unknown").lower()
        
        if severity in severity_levels and severity_levels[severity] >= max_level:
            violations.append({
                "id": vuln.get("id"),
                "severity": severity,
                "package": match.get("artifact", {}).get("name"),
                "fix_version": vuln.get("fix", {}).get("versions", [])
            })
    
    return len(violations) == 0, violations

@app.route('/validate', methods=['POST'])
def validate_pod():
    """نقطة نهاية التحقق من Pod"""
    admission_review = request.get_json()
    
    pod_spec = admission_review.get('request', {}).get('object', {}).get('spec', {})
    containers = pod_spec.get('containers', []) + pod_spec.get('initContainers', [])
    
    all_allowed = True
    denial_reasons = []
    
    for container in containers:
        image = container.get('image', '')
        logging.info(f"فحص صورة: {image}")
        
        scan_result = scan_image_with_grype(image)
        
        if "error" in scan_result:
            logging.warning(f"فشل الفحص للصورة {image}: {scan_result['error']}")
            continue
        
        is_safe, violations = check_vulnerabilities(scan_result, max_severity="high")
        
        if not is_safe:
            all_allowed = False
            denial_reasons.append({
                "image": image,
                "violations": violations[:5]  # أول 5 ثغرات فقط في الرسالة
            })
    
    # بناء الاستجابة
    uid = admission_review.get('request', {}).get('uid', '')
    
    if all_allowed:
        response = {
            "apiVersion": "admission.k8s.io/v1",
            "kind": "AdmissionReview",
            "response": {
                "uid": uid,
                "allowed": True
            }
        }
    else:
        message = f"رُفض: وُجدت ثغرات عالية الخطورة في {len(denial_reasons)} صورة(صور)"
        response = {
            "apiVersion": "admission.k8s.io/v1",
            "kind": "AdmissionReview",
            "response": {
                "uid": uid,
                "allowed": False,
                "status": {
                    "code": 403,
                    "message": message
                }
            }
        }
    
    return jsonify(response)

@app.route('/health', methods=['GET'])
def health():
    """فحص صحة الخدمة"""
    return jsonify({"status": "healthy"}), 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8443, ssl_context=('cert.pem', 'key.pem'))

سياسة أمان Pod في Kubernetes

# pod-security-policy.yaml
# سياسة أمان للـ Pods في Kubernetes

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: grype-scanner-policy
  namespace: security
spec:
  podSelector:
    matchLabels:
      app: grype-scanner
  policyTypes:
    - Egress
  egress:
    # السماح بالوصول إلى قاعدة بيانات Grype
    - to: []
      ports:
        - port: 443
---
# ValidatingWebhookConfiguration للتحقق من الصور
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: grype-image-validator
webhooks:
  - name: validate-images.security.thakicloud.io
    rules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["pods"]
    clientConfig:
      service:
        name: grype-admission-service
        namespace: security
        path: /validate
      caBundle: LS0t...  # شهادة CA مشفرة بـ base64
    admissionReviewVersions: ["v1"]
    sideEffects: None
    failurePolicy: Fail  # رفض الـ Pods في حالة فشل الفحص

سكريبت مراقبة تلقائي

#!/bin/bash
# scripts/grype-monitor.sh
# مراقبة دورية لأمان الصور في الإنتاج

set -euo pipefail

# المتغيرات
REGISTRY="your-registry.azurecr.io"
NAMESPACE="production"
SLACK_WEBHOOK="${SLACK_WEBHOOK_URL:-}"
MAX_SEVERITY="high"
REPORT_DIR="/tmp/grype-reports"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)

# إنشاء مجلد التقارير
mkdir -p "${REPORT_DIR}"

# دالة إرسال تنبيه Slack
send_slack_alert() {
    local message="$1"
    if [[ -n "${SLACK_WEBHOOK}" ]]; then
        curl -s -X POST -H 'Content-type: application/json' \
            --data "{\"text\": \"${message}\"}" \
            "${SLACK_WEBHOOK}"
    fi
}

# دالة فحص صورة واحدة
scan_image() {
    local image="$1"
    local report_file="${REPORT_DIR}/$(echo ${image} | tr '/:' '-')-${TIMESTAMP}.json"
    
    echo "فحص: ${image}"
    
    # تنفيذ الفحص
    grype "${image}" -o json --quiet > "${report_file}" 2>/dev/null || true
    
    # عد الثغرات حسب الخطورة
    local critical_count=$(jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length' "${report_file}" 2>/dev/null || echo 0)
    local high_count=$(jq '[.matches[] | select(.vulnerability.severity == "High")] | length' "${report_file}" 2>/dev/null || echo 0)
    
    echo "  الثغرات الحرجة: ${critical_count}"
    echo "  الثغرات العالية: ${high_count}"
    
    # إرسال تنبيه إذا وُجدت ثغرات خطيرة
    if [[ "${critical_count}" -gt 0 ]] || [[ "${high_count}" -gt 5 ]]; then
        local alert_msg="تنبيه أمان الحاويات: ${image} - ${critical_count} حرجة، ${high_count} عالية"
        send_slack_alert "${alert_msg}"
    fi
    
    echo "${report_file}"
}

# الحصول على قائمة الصور من Kubernetes
echo "جلب قائمة الصور من Kubernetes..."
IMAGES=$(kubectl get pods -n "${NAMESPACE}" \
    -o jsonpath='{range .items[*]}{range .spec.containers[*]}{.image}{"\n"}{end}{end}' \
    | sort -u)

# فحص جميع الصور
TOTAL_VULNS=0
declare -a FAILED_IMAGES=()

while IFS= read -r image; do
    [[ -z "${image}" ]] && continue
    
    report=$(scan_image "${image}")
    
    # إضافة إلى قائمة الفاشلة إذا لزم
    high_count=$(jq '[.matches[] | select(.vulnerability.severity == "High" or .vulnerability.severity == "Critical")] | length' "${report}" 2>/dev/null || echo 0)
    TOTAL_VULNS=$((TOTAL_VULNS + high_count))
    
    if [[ "${high_count}" -gt 0 ]]; then
        FAILED_IMAGES+=("${image}")
    fi
done <<< "${IMAGES}"

# ملخص نهائي
echo ""
echo "=== ملخص الفحص الأمني ==="
echo "الصور الفاشلة: ${#FAILED_IMAGES[@]}"
echo "إجمالي الثغرات العالية والحرجة: ${TOTAL_VULNS}"

if [[ "${#FAILED_IMAGES[@]}" -gt 0 ]]; then
    echo ""
    echo "الصور التي تحتاج معالجة فورية:"
    for img in "${FAILED_IMAGES[@]}"; do
        echo "  - ${img}"
    done
    
    send_slack_alert "تقرير الأمان اليومي: ${#FAILED_IMAGES[@]} صورة تحتاج معالجة فورية (${TOTAL_VULNS} ثغرة)"
    exit 1
fi

echo "جميع الصور آمنة."

Tekton Pipeline للفحص الأمني

# tekton-security-pipeline.yaml
# خط أنابيب Tekton لفحص الأمان

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: grype-security-scan
  namespace: ci-cd
spec:
  params:
    - name: IMAGE
      description: صورة Docker للفحص
    - name: SEVERITY_THRESHOLD
      description: الحد الأدنى للخطورة
      default: "high"
    - name: OUTPUT_FORMAT
      description: تنسيق مخرجات التقرير
      default: "json"
  
  results:
    - name: scan-result
      description: نتيجة الفحص (passed/failed)
    - name: vulnerability-count
      description: عدد الثغرات المكتشفة
  
  steps:
    - name: install-grype
      image: curlimages/curl:latest
      script: |
        curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh \
          | sh -s -- -b /usr/local/bin
    
    - name: scan-image
      image: alpine:3.18
      script: |
        set -e
        
        echo "فحص الصورة: $(params.IMAGE)"
        
        # تنفيذ الفحص
        grype "$(params.IMAGE)" \
          -o "$(params.OUTPUT_FORMAT)" \
          --fail-on "$(params.SEVERITY_THRESHOLD)" \
          > /workspace/scan-results.json 2>&1 || SCAN_FAILED=true
        
        # حفظ نتيجة الفحص
        if [[ "${SCAN_FAILED:-false}" == "true" ]]; then
          echo "failed" > $(results.scan-result.path)
        else
          echo "passed" > $(results.scan-result.path)
        fi
        
        # حفظ عدد الثغرات
        VULN_COUNT=$(jq '.matches | length' /workspace/scan-results.json 2>/dev/null || echo 0)
        echo "${VULN_COUNT}" > $(results.vulnerability-count.path)
      
      volumeMounts:
        - name: workspace
          mountPath: /workspace
  
  volumes:
    - name: workspace
      emptyDir: {}

تصدير مقاييس Prometheus

# scripts/grype_metrics_exporter.py
# تصدير مقاييس فحص Grype إلى Prometheus

from prometheus_client import start_http_server, Gauge, Counter
import subprocess
import json
import time
import logging
from typing import Optional

# تعريف المقاييس
vulnerability_gauge = Gauge(
    'grype_vulnerabilities_total',
    'إجمالي الثغرات المكتشفة',
    ['image', 'severity', 'package_type']
)

scan_duration_gauge = Gauge(
    'grype_scan_duration_seconds',
    'مدة فحص الصورة بالثواني',
    ['image']
)

scan_success_counter = Counter(
    'grype_scans_successful_total',
    'عدد الفحوصات الناجحة',
    ['image']
)

scan_failure_counter = Counter(
    'grype_scans_failed_total',
    'عدد الفحوصات الفاشلة',
    ['image', 'reason']
)

def scan_image(image: str) -> Optional[dict]:
    """فحص صورة وإرجاع نتائج JSON"""
    start_time = time.time()
    
    try:
        result = subprocess.run(
            ["grype", image, "-o", "json", "--quiet"],
            capture_output=True,
            text=True,
            timeout=600
        )
        
        duration = time.time() - start_time
        scan_duration_gauge.labels(image=image).set(duration)
        
        if result.stdout:
            scan_data = json.loads(result.stdout)
            scan_success_counter.labels(image=image).inc()
            return scan_data
        else:
            logging.warning(f"لا توجد مخرجات للصورة {image}: {result.stderr}")
            scan_failure_counter.labels(image=image, reason="no_output").inc()
            return None
            
    except subprocess.TimeoutExpired:
        scan_failure_counter.labels(image=image, reason="timeout").inc()
        logging.error(f"انتهت مهلة فحص {image}")
        return None
    except Exception as e:
        scan_failure_counter.labels(image=image, reason="exception").inc()
        logging.error(f"خطأ في فحص {image}: {e}")
        return None

def update_metrics(images: list):
    """تحديث مقاييس Prometheus"""
    for image in images:
        logging.info(f"جارٍ فحص: {image}")
        scan_result = scan_image(image)
        
        if not scan_result:
            continue
        
        # إعادة تعيين المقياس للصورة
        for severity in ["critical", "high", "medium", "low", "negligible"]:
            vulnerability_gauge.labels(
                image=image,
                severity=severity,
                package_type="all"
            ).set(0)
        
        # حساب الثغرات
        for match in scan_result.get("matches", []):
            vuln = match.get("vulnerability", {})
            severity = vuln.get("severity", "unknown").lower()
            artifact = match.get("artifact", {})
            pkg_type = artifact.get("type", "unknown")
            
            vulnerability_gauge.labels(
                image=image,
                severity=severity,
                package_type=pkg_type
            ).inc()

def main():
    """النقطة الرئيسية لتصدير المقاييس"""
    # قائمة الصور للمراقبة
    images_to_monitor = [
        "your-registry.io/api-service:latest",
        "your-registry.io/inference-service:latest",
        "your-registry.io/model-service:latest",
    ]
    
    # بدء خادم Prometheus
    start_http_server(8080)
    logging.info("تصدير مقاييس Grype على المنفذ 8080")
    
    # حلقة المراقبة الدورية
    while True:
        update_metrics(images_to_monitor)
        time.sleep(3600)  # كل ساعة

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    main()

لوحة Grafana

// grafana-dashboard.json (مقتطف)
// لوحة Grafana لمراقبة Grype
{
  "title": "أمان الحاويات - Grype",
  "panels": [
    {
      "title": "إجمالي الثغرات الحرجة",
      "type": "stat",
      "targets": [
        {
          "expr": "sum(grype_vulnerabilities_total{severity='critical'})",
          "legendFormat": "ثغرات حرجة"
        }
      ],
      "thresholds": {
        "steps": [
          {"color": "green", "value": 0},
          {"color": "red", "value": 1}
        ]
      }
    },
    {
      "title": "توزيع الثغرات حسب الخطورة",
      "type": "piechart",
      "targets": [
        {
          "expr": "sum by (severity) (grype_vulnerabilities_total)",
          "legendFormat": ""
        }
      ]
    },
    {
      "title": "اتجاه الثغرات عبر الزمن",
      "type": "timeseries",
      "targets": [
        {
          "expr": "sum by (severity) (grype_vulnerabilities_total)",
          "legendFormat": ""
        }
      ]
    }
  ]
}

الإعدادات المتقدمة

ملف قواعد التجاهل

# .grype.yaml
# قواعد تجاهل الثغرات المقبولة

ignore:
  # تجاهل ثغرات بعيدة الاستغلال
  - vulnerability: CVE-2021-44228  # Log4Shell - محمي بجدار الحماية
    reason: "محمي بجدار الحماية الداخلي وعدم تعرض الخدمة للإنترنت مباشرة"
    expiration: "2025-12-31T00:00:00Z"
  
  # تجاهل ثغرات في حزم محددة
  - package:
      name: openssl
      version: "1.1.1t"
    vulnerability: CVE-2023-0286
    reason: "نسخة معدّلة مع تطبيق إصلاح جزئي"
  
  # تجاهل بناءً على الخطورة لبيئات معينة
  - fix-state: wont-fix
    reason: "المشروع المصدري لن يُصلح هذه الثغرة"

# إعدادات قاعدة البيانات
db:
  # تحديث قاعدة البيانات تلقائيًا
  auto-update: true
  # مصدر قاعدة البيانات
  update-url: "https://toolbox-data.anchore.io/grype/databases/listing.json"
  # الحد الأقصى لعمر قاعدة البيانات (بالساعات)
  max-allowed-built-age: 120h

# إعدادات الفحص
check-for-app-update: false
fail-on-severity: "high"
output: "table"
quiet: false

مطابقة مخصصة

# .grype-custom-matchers.yaml
# مطابقة مخصصة للحزم الداخلية

matchers:
  # تعطيل مطابقة معينة
  - type: java
    use-cpes: false
  
  # إضافة مصادر بيانات إضافية
additional-vulnerability-sources:
  - name: "قاعدة بيانات الشركة الداخلية"
    url: "https://vuln-db.internal.thakicloud.io/api/v1"
    headers:
      Authorization: "Bearer ${INTERNAL_VULN_DB_TOKEN}"

اختصارات Zsh

# إضافة هذه الاختصارات إلى ~/.zshrc

# اختصارات Grype الأساسية
alias gs='grype'                                           # فحص سريع
alias gsc='grype --fail-on critical'                       # فحص مع فشل عند ثغرات حرجة
alias gsh='grype --fail-on high'                           # فحص مع فشل عند ثغرات عالية
alias gsjson='grype -o json'                               # مخرجات JSON
alias gslocal='grype dir:.'                                # فحص المجلد الحالي

# دالة فحص سريع مع ملخص
grype-summary() {
    local image="${1:-alpine:latest}"
    echo "فحص: ${image}"
    grype "${image}" -o json --quiet 2>/dev/null | \
        jq -r '.matches | group_by(.vulnerability.severity) | 
        map({(.[0].vulnerability.severity): length}) | 
        add | 
        to_entries | 
        sort_by(.key) | 
        .[] | "\(.key): \(.value) ثغرة"'
}

# دالة فحص وتصفية JSON
grype-critical() {
    local image="${1}"
    grype "${image}" -o json --quiet 2>/dev/null | \
        jq '.matches[] | select(.vulnerability.severity == "Critical") | 
        {cve: .vulnerability.id, package: .artifact.name, fix: .vulnerability.fix.versions}'
}

تحسين الأداء

التخزين المؤقت لقاعدة البيانات

# تحديث قاعدة البيانات مرة واحدة وتخزينها مؤقتًا
grype db update

# مشاركة قاعدة البيانات عبر الحاويات
docker run -v grype-db:/tmp/grype-db \
  -e GRYPE_DB_CACHE_DIR=/tmp/grype-db \
  anchore/grype:latest \
  your-image:latest

الفحص المتوازي

#!/bin/bash
# فحص متوازٍ لصور متعددة

IMAGES=(
    "nginx:latest"
    "python:3.11-slim"
    "node:20-alpine"
    "redis:7-alpine"
)

# استخدام GNU parallel إذا كان متاحًا
if command -v parallel &>/dev/null; then
    parallel grype {} -o json --quiet '>' /tmp/grype-{}.json ::: "${IMAGES[@]}"
else
    # بديل باستخدام &
    for image in "${IMAGES[@]}"; do
        safe_name=$(echo "${image}" | tr '/:' '-')
        grype "${image}" -o json --quiet > "/tmp/grype-${safe_name}.json" &
    done
    wait
fi

echo "اكتمل الفحص المتوازٍ"

تجميع النتائج

# scripts/aggregate_grype_results.py
# تجميع نتائج الفحص من ملفات JSON متعددة

import json
import glob
from pathlib import Path
from collections import defaultdict

def aggregate_scan_results(results_dir: str = "/tmp") -> dict:
    """تجميع نتائج فحوصات Grype المتعددة"""
    
    all_results = {
        "images_scanned": 0,
        "total_vulnerabilities": 0,
        "by_severity": defaultdict(int),
        "top_packages": defaultdict(int),
        "cve_frequency": defaultdict(int),
        "images_with_critical": []
    }
    
    # قراءة جميع ملفات النتائج
    for result_file in glob.glob(f"{results_dir}/grype-*.json"):
        image_name = Path(result_file).stem.replace("grype-", "")
        
        try:
            with open(result_file) as f:
                data = json.load(f)
        except Exception as e:
            print(f"خطأ في قراءة {result_file}: {e}")
            continue
        
        all_results["images_scanned"] += 1
        matches = data.get("matches", [])
        all_results["total_vulnerabilities"] += len(matches)
        
        has_critical = False
        
        for match in matches:
            vuln = match.get("vulnerability", {})
            severity = vuln.get("severity", "unknown").lower()
            pkg_name = match.get("artifact", {}).get("name", "unknown")
            cve_id = vuln.get("id", "unknown")
            
            all_results["by_severity"][severity] += 1
            all_results["top_packages"][pkg_name] += 1
            all_results["cve_frequency"][cve_id] += 1
            
            if severity == "critical":
                has_critical = True
        
        if has_critical:
            all_results["images_with_critical"].append(image_name)
    
    # ترتيب النتائج
    all_results["top_packages"] = dict(
        sorted(all_results["top_packages"].items(), key=lambda x: x[1], reverse=True)[:10]
    )
    all_results["cve_frequency"] = dict(
        sorted(all_results["cve_frequency"].items(), key=lambda x: x[1], reverse=True)[:10]
    )
    all_results["by_severity"] = dict(all_results["by_severity"])
    
    return all_results

# تنفيذ التجميع
results = aggregate_scan_results()
print(json.dumps(results, ensure_ascii=False, indent=2))

نتائج الاختبار الفعلية

# مثال على مخرجات فحص nginx:latest
$ grype nginx:latest

 ✔ Vulnerability DB        [no update available]
 ✔ Indexed image
 ✔ Cataloged packages      [97 packages]
 ✔ Scanning for vulnerabilities

NAME              INSTALLED   FIXED-IN    TYPE       VULNERABILITY        SEVERITY
curl              7.88.1-10   7.88.1-10+deb12u5  deb  CVE-2023-38545  High
libssl1.1         1.1.1w-0    (لا يوجد إصلاح)  deb  CVE-2023-5678   Medium
openssl           3.0.11-1    3.0.13-1+deb12u1   deb  CVE-2024-0727   Medium
...

الملخص:
  الحرجة: 0
  العالية: 1
  المتوسطة: 8
  المنخفضة: 12
  إجمالي: 21

استكشاف الأخطاء وإصلاحها

مشكلة: قاعدة البيانات قديمة

# تحديث قاعدة البيانات يدويًا
grype db update

# أو حذفها وإعادة التنزيل
grype db delete
grype db update

# التحقق من حالة قاعدة البيانات
grype db status

مشكلة: بطء الفحص

# استخدام وضع الهدوء لتسريع المخرجات
grype nginx:latest --quiet

# تقليل نطاق الفحص
grype nginx:latest --scope squashed  # بدلًا من all-layers (الافتراضي)

# تجاهل الطبقات المؤقتة
grype nginx:latest --platform linux/amd64

مشكلة: نتائج إيجابية كاذبة

# إضافة قاعدة تجاهل في .grype.yaml
ignore:
  - vulnerability: CVE-XXXX-XXXX
    reason: "إيجابي كاذب - تأكيد من الفريق الأمني بتاريخ 2025-06-01"
    expiration: "2025-12-31T00:00:00Z"

الخلاصة

Grype هو الأداة المثالية لتعزيز DevSecOps في منصات الذكاء الاصطناعي السحابية بفضل:

  1. السرعة والدقة: نتائج دقيقة خلال ثوانٍ حتى مع الصور الكبيرة
  2. التكامل السلس: يدخل في CI/CD دون تغيير جوهري في سير العمل
  3. المرونة: يدعم بيئات متعددة من المحلية إلى Kubernetes
  4. قاعدة بيانات شاملة: تغطية واسعة لثغرات Linux والحزم
  5. المصدر المفتوح: مجتمع نشط ومستمر في التطوير

ابدأ بدمج Grype في خطوط CI/CD الخاصة بك اليوم، وانتقل تدريجيًا إلى المراقبة المستمرة والإبلاغ التلقائي لبناء منصة ذكاء اصطناعي آمنة.