استرجاع معرّف نسخة EC2 عبر خدمة البيانات الوصفية IMDSv2
في كثير من السيناريوهات التشغيلية، يحتاج السكريبت الذي يعمل داخل نسخة EC2 إلى معرفة هويتها — سواء لتسجيل الأحداث، أو لاتخاذ قرارات تلقائية بناءً على الـ instance-id. المشكلة أن الطريقة القديمة (IMDSv1) تركت ثغرة حقيقية استُغلّت في هجمات SSRF على بيئات إنتاجية، وأدّت إلى تسريب بيانات اعتماد IAM بالكامل.
ملخص سريع (TL;DR)
| النقطة | التفاصيل |
|---|---|
| الهدف | استرجاع instance-id من داخل النسخة |
| الأداة | Instance Metadata Service (IMDS) على العنوان 169.254.169.254 |
| الإصدار المُوصى به | IMDSv2 — يستخدم token ذو عمر محدود |
| سبب تجنّب IMDSv1 | عرضة لهجمات SSRF — لا يتطلب أي مصادقة مسبقة |
| الأمر الأساسي | طلب token ثم استخدامه في استعلام البيانات الوصفية |
كيف تعمل خدمة البيانات الوصفية IMDS
كل نسخة EC2 تستطيع الوصول إلى عنوان IP خاص غير قابل للتوجيه خارجياً: 169.254.169.254. هذا العنوان يوفّر بيانات وصفية عن النسخة نفسها — معرّفها، نوعها، منطقتها، وبيانات اعتماد IAM المؤقتة المرتبطة بها إن وُجد دور مُعيَّن.
الفارق الجوهري بين الإصدارين:
- IMDSv1: طلب HTTP مباشر بدون أي مصادقة. أي كود يعمل على الجهاز — أو أي طلب يصل عبر ثغرة SSRF — يستطيع استرجاع هذه البيانات فوراً.
- IMDSv2: يتطلب أولاً الحصول على token مؤقت عبر طلب PUT، ثم تضمين هذا الـ token في كل طلب لاحق. الـ token يُحدَّد له عمر زمني (TTL) بالثواني.
الفكرة الجوهرية: هجمات SSRF تُرسل طلبات GET من الخادم نيابةً عن المهاجم، لكنها لا تستطيع تنفيذ طلب PUT أولاً لاستخراج الـ token — وهذا بالضبط ما يكسر سلسلة الهجوم.
X-aws-ec2-metadata-token-ttl-seconds: 21600 IMDS-->>S: token مؤقت (نص) S->>IMDS: GET /latest/meta-data/instance-id
X-aws-ec2-metadata-token: [token] IMDS-->>S: i-1234567890abcdef0
- طلب PUT للـ token: السكريبت يرسل طلب PUT إلى نقطة نهاية IMDS مع تحديد مدة صلاحية الـ token.
- استلام الـ token: IMDS يُعيد token نصياً مؤقتاً.
- طلب GET مع الـ token: السكريبت يستخدم الـ token كـ header في طلب GET للحصول على البيانات الوصفية.
- استلام instance-id: IMDS يُعيد معرّف النسخة نصاً.
استرجاع instance-id باستخدام IMDSv2 — الطريقة الصحيحة
الخطوة الأولى دائماً هي طلب الـ token. بدونه، أي استعلام لاحق سيفشل إن كانت النسخة مُهيَّأة لرفض IMDSv1.
باستخدام curl من سطر الأوامر
# الخطوة 1: طلب token مؤقت بصلاحية 21600 ثانية (6 ساعات)
TOKEN=$(curl -s -X PUT \
'http://169.254.169.254/latest/api/token' \
-H 'X-aws-ec2-metadata-token-ttl-seconds: 21600')
# الخطوة 2: استخدام الـ token لاسترجاع instance-id
INSTANCE_ID=$(curl -s \
'http://169.254.169.254/latest/meta-data/instance-id' \
-H "X-aws-ec2-metadata-token: $TOKEN")
echo "Instance ID: $INSTANCE_ID"
لاحظ أن الـ token يُمرَّر كـ header وليس كجزء من الـ URL — هذا التصميم المقصود هو ما يجعل SSRF عاجزاً عن استغلاله.
داخل سكريبت Python
🔽 اضغط لعرض كود Python الكامل
import urllib.request
def get_instance_id():
# الخطوة 1: طلب token من IMDSv2
token_url = 'http://169.254.169.254/latest/api/token'
token_request = urllib.request.Request(
token_url,
method='PUT',
headers={'X-aws-ec2-metadata-token-ttl-seconds': '21600'}
)
with urllib.request.urlopen(token_request, timeout=2) as response:
token = response.read().decode('utf-8')
# الخطوة 2: استرجاع instance-id باستخدام الـ token
metadata_url = 'http://169.254.169.254/latest/meta-data/instance-id'
metadata_request = urllib.request.Request(
metadata_url,
headers={'X-aws-ec2-metadata-token': token}
)
with urllib.request.urlopen(metadata_request, timeout=2) as response:
instance_id = response.read().decode('utf-8')
return instance_id
if __name__ == '__main__':
print(f'Instance ID: {get_instance_id()}')
قيمة الـ timeout مهمة — إن لم تكن النسخة تعمل على EC2 فعلياً، الطلب سيتعلق إلى الأبد بدون timeout محدد.
إجبار النسخة على استخدام IMDSv2 فقط — وكيف تتحقق من ذلك
تفعيل IMDSv2 على مستوى السكريبت لا يكفي وحده. إن كانت النسخة لا تزال تقبل IMDSv1، فأي كود قديم أو مكتبة غير مُحدَّثة قد تتجاوز الحماية تلقائياً. الحل الصحيح هو إجبار النسخة على رفض IMDSv1 تماماً.
التحقق من الإعداد الحالي للنسخة
aws ec2 describe-instances \
--instance-ids i-1234567890abcdef0 \
--query 'Reservations[*].Instances[*].MetadataOptions' \
--region us-east-1 \
--output json
ابحث في الناتج عن HttpTokens. إن كانت القيمة optional فهذا يعني أن IMDSv1 لا يزال مقبولاً. القيمة المطلوبة هي required.
إجبار النسخة على IMDSv2 فقط
aws ec2 modify-instance-metadata-options \
--instance-id i-1234567890abcdef0 \
--http-tokens required \
--http-endpoint enabled \
--region us-east-1
هذا الأمر لا يتطلب إعادة تشغيل النسخة — يسري فوراً. لكن تأكد أن كل السكريبتات والمكتبات المستخدمة على النسخة تدعم IMDSv2 قبل تطبيقه.
تطبيق الإعداد على نسخ جديدة عبر Launch Template
aws ec2 create-launch-template \
--launch-template-name MySecureLaunchTemplate \
--version-description 'IMDSv2 required' \
--launch-template-data '{
"MetadataOptions": {
"HttpTokens": "required",
"HttpEndpoint": "enabled"
}
}' \
--region us-east-1
تجربة حقيقية: التشخيص الخاطئ الشائع
المشكلة ظهرت في بيئة إنتاجية: سكريبت Python يستخدم مكتبة boto3 لاسترجاع بيانات النسخة بدأ يُعيد خطأ ConnectionError بعد تطبيق HttpTokens: required. التشخيص الأول كان أن المشكلة في Security Group أو في إعدادات الشبكة.
الأمر الذي كشف الحقيقة:
# اختبار مباشر لـ IMDSv1 — إن أعاد 401 فهذا يعني أن IMDSv2 مُفعَّل بشكل صحيح
curl -s -o /dev/null -w "%{http_code}" \
'http://169.254.169.254/latest/meta-data/instance-id'
الناتج كان 401 — وهذا يعني أن IMDS يعمل، لكن النسخة تطلب token. المشكلة الفعلية كانت أن إصدار boto3 المستخدم كان قديماً ولا يدعم IMDSv2 بشكل كامل. الحل كان تحديث المكتبة وليس تغيير إعدادات الشبكة.
الدرس: عندما يفشل الاتصال بـ IMDS بعد تفعيل required، ابدأ بالتحقق من دعم المكتبة لـ IMDSv2 قبل التحقيق في طبقة الشبكة.
صلاحيات IAM المطلوبة
الوصول إلى IMDS على العنوان 169.254.169.254 لا يتطلب صلاحيات IAM — هو اتصال شبكي محلي داخل النسخة. لكن إن كنت تستخدم AWS CLI أو SDK من خارج النسخة لاستعلام بيانات النسخة، فستحتاج إلى الصلاحية التالية:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2:DescribeInstances",
"Resource": "*"
}
]
}
ملاحظة: ec2:DescribeInstances لا تدعم تقييد الصلاحية على مستوى نسخة بعينها — يجب استخدام "Resource": "*" مع إمكانية إضافة شروط Condition للتضييق.
استرجاع بيانات وصفية أخرى بنفس الأسلوب
بعد الحصول على الـ token، يمكن استرجاع أي بيانات وصفية أخرى بنفس الطريقة:
# استرجاع نوع النسخة
curl -s \
'http://169.254.169.254/latest/meta-data/instance-type' \
-H "X-aws-ec2-metadata-token: $TOKEN"
# استرجاع المنطقة الجغرافية
curl -s \
'http://169.254.169.254/latest/meta-data/placement/region' \
-H "X-aws-ec2-metadata-token: $TOKEN"
# استرجاع عنوان IP الخاص
curl -s \
'http://169.254.169.254/latest/meta-data/local-ipv4' \
-H "X-aws-ec2-metadata-token: $TOKEN"
الخلاصة والخطوات التالية لاسترجاع instance-id بأمان
استخدام IMDSv2 لاسترجاع instance-id ليس مجرد توصية أمنية — هو الحد الأدنى المقبول في أي بيئة إنتاجية. الخطوات الثلاث الأساسية: تطبيق IMDSv2 في السكريبت، التحقق من أن النسخة مُهيَّأة على HttpTokens: required، وتضمين هذا الإعداد في Launch Template لكل نسخة جديدة.
- راجع توثيق AWS الرسمي لـ IMDS للاطلاع على كامل قائمة البيانات الوصفية المتاحة.
- إن كنت تعمل مع Auto Scaling Groups، تأكد من تطبيق إعداد IMDSv2 على مستوى Launch Template وليس فقط على النسخ الموجودة.
مسرد المصطلحات
| المصطلح | التعريف |
|---|---|
| IMDS | Instance Metadata Service — خدمة تتيح للنسخة الوصول إلى بياناتها الوصفية عبر عنوان IP محلي |
| IMDSv2 | الإصدار الثاني من IMDS — يستخدم token مؤقت للمصادقة قبل كل طلب |
| SSRF | Server-Side Request Forgery — هجوم يجعل الخادم يُرسل طلبات HTTP نيابةً عن المهاجم |
| instance-id | معرّف فريد لكل نسخة EC2، يبدأ بـ i- ويتبعه سلسلة هيكساديسيمال |
| HttpTokens | إعداد على مستوى النسخة يتحكم في ما إذا كان IMDSv2 اختيارياً أو إلزامياً |
تعليقات
إرسال تعليق