ورقة مرجعية لأمان GitLab CI: المتغيرات، المُنفّذات، البيئات، وOIDC

لماذا يُعدّ أمان GitLab CI مهمًّا

تُعدّ خطوط أنابيب GitLab CI/CD أدوات قوية — لكن القوة تأتي مع المخاطر. متغيّر واحد خاطئ التكوين قد يسرّب أسرارًا حساسة. مُنفّذ غير محدّد النطاق قد ينفّذ شيفرة خبيثة. بيئة غير محمية قد تسمح لمطوّر مبتدئ بالنشر مباشرة في الإنتاج. تمنحك هذه الورقة المرجعية أكواد YAML جاهزة للنسخ واللصق لكل ضوابط الأمان الحرجة في GitLab CI، من المتغيرات المحمية إلى اتحاد OIDC.

احفظ هذه الصفحة في مفضلتك. استخدمها كمرجع أساسي في كل مرة تُهيّئ فيها مشروعًا جديدًا أو تُدقّق مشروعًا قائمًا.

1. المتغيرات المحمية، المُقنّعة، والمخفية

تتحكّم متغيرات GitLab CI/CD في كيفية تدفّق الأسرار إلى خطوط الأنابيب الخاصة بك. الخطأ في ضبطها هو السبب الأول لتسريب بيانات الاعتماد في CI/CD. يجب أن تكون كل قيمة حساسة محمية (متاحة فقط على الفروع/العلامات المحمية)، ومُقنّعة (مخفية من سجلات المهام)، وحيث يكون ذلك مدعومًا، مخفية (غير مرئية في واجهة المستخدم بعد الإنشاء).

ضبط المتغيرات عبر واجهة برمجة التطبيقات (API)

# Create a protected + masked variable via the GitLab API
curl --request POST \
  --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/variables" \
  --form "key=AWS_SECRET_ACCESS_KEY" \
  --form "value=$MY_SECRET" \
  --form "protected=true" \
  --form "masked=true" \
  --form "variable_type=env_var"

استخدام المتغيرات في .gitlab-ci.yml

variables:
  # Group or project-level variables are injected automatically.
  # File-type variables are written to a temp path.
  DEPLOY_TOKEN:
    description: "Token for deploying to production"
    value: ""  # Intentionally blank — set in CI/CD Settings

deploy_production:
  stage: deploy
  script:
    - echo "Deploying with masked token..."
    - ./deploy.sh --token "$DEPLOY_TOKEN"
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  environment:
    name: production

القواعد الأساسية:

  • لا تضع الأسرار مباشرة في .gitlab-ci.yml أبدًا — استخدم دائمًا إعدادات متغيرات CI/CD.
  • اضبط protected=true حتى تكون الأسرار متاحة فقط على الفروع المحمية.
  • اضبط masked=true حتى يتم إخفاء القيم من سجلات المهام.
  • استخدم متغيرات مستوى المجموعة للأسرار المشتركة عبر المشاريع (مثل بيانات اعتماد السحابة).

2. أنواع المُنفّذات وتحديد النطاق

تنفّذ المُنفّذات (Runners) مهام CI/CD الخاصة بك. إذا كان أي مُنفّذ يمكنه تنفيذ أي مهمة، فقد يستغلّ طلب دمج خبيث ذلك لسرقة الأسرار من مُنفّذ الإنتاج. تحديد النطاق بشكل صحيح أمر ضروري.

تسجيل المُنفّذ مع العلامات والحماية

# Register a runner scoped to protected branches only
gitlab-runner register \
  --non-interactive \
  --url "https://gitlab.example.com" \
  --token "$RUNNER_REG_TOKEN" \
  --executor docker \
  --docker-image alpine:latest \
  --tag-list "production,protected" \
  --access-level ref_protected

تحديد نطاق المهام لمُنفّذات محددة

# .gitlab-ci.yml — ensure production jobs only run on protected runners
deploy_production:
  stage: deploy
  tags:
    - production
    - protected
  script:
    - kubectl apply -f k8s/production/
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  environment:
    name: production

# Development jobs use a separate, unprivileged runner
test:
  stage: test
  tags:
    - shared
    - development
  script:
    - pytest tests/

أفضل الممارسات:

  • استخدم --access-level ref_protected لتقييد المُنفّذات بالفروع والعلامات المحمية.
  • استخدم مُنفّذات خاصة بالمشروع لأحمال العمل الحساسة — لا تشارك مُنفّذات الإنتاج أبدًا عبر مشاريع غير مرتبطة.
  • فضّل المُنفّذات المؤقتة (مُنفّذات Docker أو Kubernetes) حتى يتم تدمير البيئة بعد كل مهمة.
  • عطّل المُنفّذات المشتركة في المشاريع التي تتعامل مع عمليات نشر حساسة.

3. البيئات المحمية مع الموافقات

تضيف البيئات المحمية بوابة بشرية قبل عمليات النشر. هذا هو خط دفاعك الأخير ضد التغييرات غير المصرّح بها في الإنتاج.

تهيئة البيئة في .gitlab-ci.yml

# .gitlab-ci.yml — deployment with environment protection
deploy_staging:
  stage: deploy
  script:
    - ./deploy.sh staging
  environment:
    name: staging
    url: https://staging.example.com
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

deploy_production:
  stage: deploy
  script:
    - ./deploy.sh production
  environment:
    name: production
    url: https://example.com
    deployment_tier: production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual
      allow_failure: false

إعداد قواعد الموافقة عبر واجهة برمجة التطبيقات (API)

# Protect the production environment with required approvals
curl --request POST \
  --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/protected_environments" \
  --data '{"name": "production", "deploy_access_levels": [{"group_id": 9899826}], "required_approval_count": 2, "approval_rules": [{"group_id": 9899826, "required_approvals": 2}]}'

قم بتهيئة ذلك ضمن Settings > CI/CD > Protected Environments في واجهة مستخدم GitLab. اطلب موافقتين على الأقل لعمليات نشر الإنتاج. قيّد وصول النشر لمجموعات أو مستخدمين محددين — لا تستخدم أبدًا “All maintainers”.

4. تحديد نطاق CI_JOB_TOKEN

CI_JOB_TOKEN هو رمز مميز تلقائي يحقنه GitLab في كل مهمة. افتراضيًا، يمكنه الوصول إلى مشاريع أخرى في مجموعتك — وهذا خطر جدّي للتحرّك الجانبي. منذ GitLab 16.0، يجب عليك تقييد نطاقه.

تقييد وصول الرمز المميز

# Check current CI_JOB_TOKEN access scope
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/job_token_scope"

# Limit CI_JOB_TOKEN to only access specific projects
curl --request PATCH \
  --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/job_token_scope" \
  --data '{"enabled": true}'

# Add an allowlisted project
curl --request POST \
  --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/job_token_scope/allowlist" \
  --data '{"target_project_id": 12345}'

استخدام CI_JOB_TOKEN بأمان في خطوط الأنابيب

# .gitlab-ci.yml — using CI_JOB_TOKEN for cross-project triggers
trigger_deploy:
  stage: deploy
  trigger:
    project: my-group/deploy-project
    branch: main
    strategy: depend
  # CI_JOB_TOKEN is used automatically for the trigger.
  # The target project must allowlist this project's token.

القواعد الأساسية: فعّل حدّ نطاق رمز مهمة CI/CD في كل مشروع. أضف فقط المشاريع المحددة التي تحتاج فعلًا للوصول عبر المشاريع إلى القائمة المسموحة. راجع القوائم المسموحة كل ثلاثة أشهر.

5. رموز OIDC id_tokens لـ AWS وGCP

يُلغي اتحاد OIDC الحاجة إلى بيانات اعتماد السحابة طويلة الأمد في متغيرات CI/CD الخاصة بك نهائيًّا. يُصدر GitLab رمز JWT قصير الأمد، ويستبدله مزوّد السحابة الخاص بك ببيانات اعتماد مؤقتة. هذا هو المعيار الذهبي لمصادقة السحابة في خطوط الأنابيب.

اتحاد OIDC مع AWS

# .gitlab-ci.yml — OIDC authentication with AWS
deploy_aws:
  stage: deploy
  image: amazon/aws-cli:latest
  id_tokens:
    GITLAB_OIDC_TOKEN:
      aud: https://gitlab.example.com
  variables:
    ROLE_ARN: arn:aws:iam::123456789012:role/gitlab-ci-deploy
  script:
    - >
      export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s"
      $(aws sts assume-role-with-web-identity
      --role-arn $ROLE_ARN
      --role-session-name "GitLabCI-${CI_PROJECT_ID}-${CI_PIPELINE_ID}"
      --web-identity-token "$GITLAB_OIDC_TOKEN"
      --duration-seconds 3600
      --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]'
      --output text))
    - aws s3 sync ./build s3://my-production-bucket/
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  environment:
    name: production

اتحاد هوية أحمال العمل مع GCP

# .gitlab-ci.yml — OIDC authentication with GCP
deploy_gcp:
  stage: deploy
  image: google/cloud-sdk:latest
  id_tokens:
    GITLAB_OIDC_TOKEN:
      aud: https://gitlab.example.com
  script:
    - echo "$GITLAB_OIDC_TOKEN" > /tmp/gitlab_token.txt
    - >
      gcloud iam workload-identity-pools create-cred-config
      projects/$GCP_PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$PROVIDER_ID
      --service-account="$GCP_SERVICE_ACCOUNT"
      --output-file=/tmp/gcp_creds.json
      --credential-source-file=/tmp/gitlab_token.txt
    - export GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp_creds.json
    - gcloud config set project $GCP_PROJECT_ID
    - gcloud run deploy my-service --image gcr.io/$GCP_PROJECT_ID/my-app:$CI_COMMIT_SHA
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  environment:
    name: production

على جانب السحابة، قم بتهيئة سياسة الثقة للتحقق من المطالبات مثل project_path وref وref_protected بحيث يمكن فقط لمشاريع وفروع محددة تولّي الدور.

6. أمان خط أنابيب طلبات الدمج

تعمل خطوط أنابيب طلبات الدمج (Merge Request) على شيفرة لم تتم مراجعتها بعد. عاملها على أنها غير موثوقة. لا تكشف أبدًا أسرار الإنتاج لخطوط أنابيب طلبات الدمج.

# .gitlab-ci.yml — separate rules for MR vs. branch pipelines
test:
  stage: test
  script:
    - pytest tests/
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

deploy_production:
  stage: deploy
  script:
    - ./deploy.sh production
  rules:
    # NEVER run on merge request pipelines
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      when: never
    - if: $CI_COMMIT_BRANCH == "main"
  environment:
    name: production

الضوابط الحرجة:

  • استخدم rules: لضمان عدم تشغيل مهام النشر أبدًا على خطوط أنابيب merge_request_event.
  • اطلب نجاح خط الأنابيب قبل الدمج (Settings > Merge Requests).
  • فعّل “Pipelines must succeed” و“All discussions must be resolved.”
  • فكّر في تفعيل merged results pipelines لاختبار الحالة بعد الدمج.

7. قالب كشف الأسرار

يلتقط ماسح كشف الأسرار المدمج في GitLab بيانات الاعتماد المُرسلة عن طريق الخطأ قبل وصولها إلى الفرع الافتراضي.

# .gitlab-ci.yml — include the secret detection template
include:
  - template: Jobs/Secret-Detection.gitlab-ci.yml

# Override to make the pipeline fail if secrets are found
secret_detection:
  variables:
    SECRET_DETECTION_HISTORIC_SCAN: "true"  # Scan full git history
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  allow_failure: false  # Block the pipeline on detection

لمسح أكثر شمولًا، أضف قوالب SAST وفحص التبعيات (dependency scanning) أيضًا:

include:
  - template: Jobs/Secret-Detection.gitlab-ci.yml
  - template: Jobs/SAST.gitlab-ci.yml
  - template: Jobs/Dependency-Scanning.gitlab-ci.yml

راجع النتائج في Security Dashboard (المتاح على GitLab Ultimate) أو حلّل ملفات JSON الناتجة في المستويات الأدنى.

8. قواعد الدفع (Push Rules)

تفرض قواعد الدفع سياسات على مستوى Git — قبل أن تدخل الشيفرة حتى في خط الأنابيب. استخدمها لمنع دفع الأسرار، وفرض معايير رسائل الإيداع، وتقييد أنواع الملفات.

# Set push rules via the API
curl --request PUT \
  --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.example.com/api/v4/projects/$PROJECT_ID/push_rule" \
  --data '{"deny_delete_tag": true, "prevent_secrets": true, "commit_message_regex": "^(feat|fix|chore|docs|refactor|test|ci)\\(.*\\):.*", "max_file_size": 50, "member_check": true, "reject_unsigned_commits": true}'

قواعد الدفع الموصى بها:

  • prevent_secrets: true — يرفض عمليات الدفع التي تحتوي على ملفات تبدو كأسرار (مفاتيح، رموز مميزة، شهادات).
  • reject_unsigned_commits: true — يتطلب إيداعات موقّعة بـ GPG (GitLab Premium وأعلى).
  • commit_message_regex — يفرض رسائل إيداع تقليدية لمسارات تدقيق نظيفة.
  • max_file_size — يمنع إرسال ملفات ثنائية كبيرة عن طريق الخطأ.
  • member_check: true — يرفض الإيداعات من غير أعضاء المشروع.

9. مهلة المهام وinterruptible

تهدر المهام الخارجة عن السيطرة الموارد ويمكن استغلالها في التعدين الخفي للعملات الرقمية. اضبط مهلات زمنية صريحة وعلّم المهام غير الحرجة على أنها قابلة للمقاطعة حتى يتم إلغاؤها عند بدء خط أنابيب جديد على نفس الفرع.

# .gitlab-ci.yml — timeouts and interruptible
default:
  timeout: 30m          # Global default timeout for all jobs
  interruptible: true   # Cancel running jobs when a new commit is pushed
  retry:
    max: 1
    when:
      - runner_system_failure
      - stuck_or_timeout_failure

test:
  stage: test
  timeout: 15m
  interruptible: true
  script:
    - pytest tests/ --timeout=600

deploy_production:
  stage: deploy
  timeout: 20m
  interruptible: false  # NEVER cancel a running production deployment
  script:
    - ./deploy.sh production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual
  environment:
    name: production

إرشادات:

  • اضبط مهلة زمنية على مستوى المشروع في Settings > CI/CD > General Pipelines (الموصى به: 60 دقيقة كحد أقصى).
  • اضبط مهلات زمنية على مستوى المهمة أكثر صرامة من الافتراضي على مستوى المشروع.
  • علّم مهام الاختبار والتنسيق على أنها interruptible: true لتوفير سعة المُنفّذات.
  • علّم مهام النشر على أنها interruptible: false لمنع عمليات النشر الجزئية.
  • استخدم retry لأعطال البنية التحتية المؤقتة فقط — ليس أبدًا لفشل الاختبارات.

جدول مرجعي سريع

الضابط مكان التهيئة الحد الأدنى للمستوى
المتغيرات المحمية/المُقنّعة Settings > CI/CD > Variables Free
تحديد نطاق المُنفّذات Settings > CI/CD > Runners Free
البيئات المحمية Settings > CI/CD > Protected Environments Premium
نطاق CI_JOB_TOKEN Settings > CI/CD > Token Access Free
OIDC id_tokens .gitlab-ci.yml Free
كشف الأسرار include: template Free (Ultimate للوحة المعلومات)
قواعد الدفع Settings > Repository > Push Rules Premium
مهلة المهام Settings > CI/CD + .gitlab-ci.yml Free

قراءات إضافية ومختبرات عملية

واصل تعزيز أمان خطوط أنابيب GitLab CI/CD الخاصة بك مع هذه الموارد ذات الصلة:

الأمان ليس عملية تهيئة تتم مرة واحدة — بل هو ممارسة مستمرة. راجع إعدادات أمان خطوط الأنابيب كل ثلاثة أشهر، وقم بتدوير بيانات الاعتماد، وتدقيق وصول المُنفّذات، وحافظ على تحديث نسخة GitLab الخاصة بك. تمنحك هذه الورقة المرجعية اللبنات الأساسية. الآن اذهب وأمّن خطوط أنابيبك.