{"id":"l-aws-2-2","courseId":"aws-cloud-foundations","lang":"ar","kind":"lesson","summary":"اقرأ أي سياسة IAM عبارةً عبارة، واكتب سياسات بمبدأ أقل الصلاحيات مع ARNs دقيقة، وأضف شروطًا مثل MFA وTLS تضيّق متى ينطبق السماح.","html":"<p>في الدرس السابق تعلّمت كيف يقرّر IAM. والآن تكتب ما يقرّر <em>بناءً عليه</em>. يحتاج Lantern إلى سياسة للدالة التي تستقبل الملصقات المرفوعة، وسياسة للمطوّرين، وسياسة bucket تحمي الملصقات. والثلاث مكتوبة بلغة JSON الصغيرة نفسها.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">تشريح العبارة</h2>\n<p>إليك سياسة دالة الرفع في Lantern، التي تحفظ الملصقات تحت <code dir=\"ltr\">uploads/</code> وتسرد أحيانًا ما يوجد هناك:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"json\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">lantern-upload-policy.json</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Version\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"2012-10-17\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Statement\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Sid\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"WriteUploads\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Effect\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Allow\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Action\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"s3:PutObject\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Resource\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"arn:aws:s3:::lantern-posters/uploads/*\"</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Sid\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"ListUploadsOnly\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Effect\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Allow\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Action\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"s3:ListBucket\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Resource\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"arn:aws:s3:::lantern-posters\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Condition\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        \"StringLike\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: { </span><span style=\"color:#116329;--shiki-dark:#7EE787\">\"s3:prefix\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"uploads/*\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  ]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>اقرأها من الأعلى إلى الأسفل:</p>\n<ul>\n<li><code dir=\"ltr\">Version</code> دائمًا <code dir=\"ltr\">&quot;2012-10-17&quot;</code>، وهو الإصدار الحالي للغة السياسات. إنه نص ثابت، لا تاريخ اليوم.</li>\n<li><code dir=\"ltr\">Statement</code> قائمة. وكل عبارة تُقيَّم وحدها.</li>\n<li><code dir=\"ltr\">Sid</code> تسمية اختيارية. سمِّ الغرض من العبارة؛ وستشكرك نفسك في المستقبل.</li>\n<li><code dir=\"ltr\">Effect</code> قيمته <code dir=\"ltr\">Allow</code> أو <code dir=\"ltr\">Deny</code>.</li>\n<li><code dir=\"ltr\">Action</code> إجراء واحد أو قائمة، ويُكتب بالشكل <code dir=\"ltr\">service:Operation</code>. الرموز البديلة تعمل (<code dir=\"ltr\">s3:Get*</code>) لكن كل واحد منها يوسّع الباب.</li>\n<li><code dir=\"ltr\">Resource</code> ARN واحد أو قائمة. والرموز البديلة تعمل هنا أيضًا.</li>\n<li><code dir=\"ltr\">Condition</code> اختياري ويضيّق متى تنطبق العبارة.</li>\n</ul>\n<p>تضيف السياسات القائمة على المورد، مثل سياسات الـ buckets، عنصر <code dir=\"ltr\">Principal</code> يسمّي من تخصّه العبارة. أما السياسات القائمة على الهوية فلا تحتوي عليه، لأن الهوية المرفقة بها <em>هي</em> الـ principal.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">ARN الـ bucket أم ARN الكائن؟</h2>\n<p>للسياسة أعلاه موردان مختلفان عن قصد. <code dir=\"ltr\">s3:ListBucket</code> عملية على <strong>الـ bucket</strong>، لذا موردها هو <code dir=\"ltr\">arn:aws:s3:::lantern-posters</code>. و<code dir=\"ltr\">s3:PutObject</code> عملية على <strong>الكائنات</strong>، لذا موردها ARN الـ bucket متبوعًا بمسار: <code dir=\"ltr\">lantern-posters/uploads/*</code>. بدّل بينهما فلن تطابق أيٌّ من العبارتين أبدًا، وستحصل على AccessDenied مع سياسة تبدو صحيحة.</p>\n<p>التمييز نفسه موجود في أماكن أخرى. فالإجراء <code dir=\"ltr\">dynamodb:Query</code> على index في جدول يحتاج إلى ARN الـ index (<code dir=\"ltr\">table/lantern-rsvps/index/by-event</code>)، لا إلى ARN الجدول وحده. عندما تسمح سياسة “بوضوح” بشيء وترفضه AWS، قارن ARN الطلب بـ ARN السياسة حرفًا حرفًا.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>الرمز البديل المريح</span></p>\n<p>العبارة <code dir=\"ltr\">&quot;Action&quot;: &quot;s3:*&quot;, &quot;Resource&quot;: &quot;*&quot;</code> تُخفي كل أخطاء AccessDenied، بما في ذلك الاستدعاء الذي يُجريه خطأ برمجي أو مهاجم لحذف bucket آخر. الرموز البديلة في <strong>كلٍّ من</strong> Action وResource تكاد لا تكون صحيحة أبدًا خارج role خاص بالمدير. إذا احتجت إلى رمز بديل، ضعه في مكان واحد وثبّت الآخر.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">أقل الصلاحيات عمليًا</h2>\n<p><strong>أقل الصلاحيات (least privilege)</strong> يعني ألّا تمنح إلا الإجراءات والموارد التي تحتاجها المهمة. يبدو هذا بطيئًا، لكن طريقة العمل سريعة:</p>\n<ol>\n<li>اكتب ما تفعله المهمة بكلمات بسيطة: “تكتب الملصقات في <code dir=\"ltr\">uploads/</code>، وتسرد تلك البادئة”.</li>\n<li>ترجم كل فعل إلى إجراء، وكل اسم إلى ARN دقيق.</li>\n<li>انشر واختبر. رسالة AccessDenied تسمّي الإجراء والمورد الناقصين؛ أضف ذلك بالضبط.</li>\n</ol>\n<p>بالنسبة إلى الـ roles الموجودة، يعرض IAM معلومات <strong>آخر وصول (last accessed)</strong> لكل خدمة، ويستطيع IAM Access Analyzer توليد سياسة من الإجراءات التي استخدمها الـ role فعلًا وفق AWS CloudTrail. وكلاهما طريقة جيدة لتقليص سياسة اتّسعت أكثر من اللازم.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">الشروط: متى ينطبق السماح</h2>\n<p>كتلة الشرط تربط <strong>العوامل (operators)</strong> بـ <strong>المفاتيح (keys)</strong> و<strong>القيم (values)</strong>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"json\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">JSON</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Condition\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"StringEquals\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: { </span><span style=\"color:#116329;--shiki-dark:#7EE787\">\"aws:RequestedRegion\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"eu-west-1\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"eu-south-2\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] },</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Bool\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: { </span><span style=\"color:#116329;--shiki-dark:#7EE787\">\"aws:MultiFactorAuthPresent\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"true\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>قاعدتان تجعلان الشروط قابلة للتوقّع. المفاتيح المنفصلة تُجمع بـ <strong>AND</strong>: يجب أن يكون الطلب في إحدى تلك الـ Regions <em>وأن</em> يُرسَل باستخدام MFA. والقيم المتعدّدة للمفتاح الواحد تُجمع بـ <strong>OR</strong>: أيٌّ من الاثنتين تفي بالغرض.</p>\n<p>مفاتيح مفيدة لـ Lantern:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>المفتاح</th>\n<th>استخدمه من أجل</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code dir=\"ltr\">aws:SecureTransport</code></td>\n<td>رفض طلبات HTTP العادي</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">aws:MultiFactorAuthPresent</code></td>\n<td>اشتراط MFA للإجراءات الخطرة مثل الحذف</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">aws:SourceIp</code></td>\n<td>قصر الإنسان على نطاق عناوين المكتب أو الـ VPN</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">aws:RequestedRegion</code></td>\n<td>إبقاء العمل داخل الـ Regions الخاصة بـ Lantern</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">s3:prefix</code></td>\n<td>قصر ListBucket على مجلّد واحد</td>\n</tr>\n</tbody>\n</table></div><p>والشروط ممتازة أيضًا كحواجز حماية بـ <strong>الرفض</strong>. ترفض سياسة الـ bucket في Lantern أي طلب دون TLS، من أي أحد:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"json\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">lantern-posters-bucket-policy.json</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Version\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"2012-10-17\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Statement\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Sid\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"DenyInsecureTransport\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Effect\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Deny\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Principal\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"*\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Action\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"s3:*\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Resource\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">        \"arn:aws:s3:::lantern-posters\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">        \"arn:aws:s3:::lantern-posters/*\"</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      ],</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Condition\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: { </span><span style=\"color:#116329;--shiki-dark:#7EE787\">\"Bool\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: { </span><span style=\"color:#116329;--shiki-dark:#7EE787\">\"aws:SecureTransport\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"false\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  ]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>ولأن الرفض الصريح ينتصر دائمًا، لا يستطيع أي سماح في أي مكان إلغاءه.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">تحقّق قبل أن ترفق</h2>\n<p>يشغّل محرّر السياسات في console الـ IAM فحوص <strong>IAM Access Analyzer</strong> أثناء كتابتك: أخطاء الصياغة، والإجراءات غير الصالحة، والتحذيرات الأمنية مثل رمز بديل في pass-role. ومن الطرفية:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">aws</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> accessanalyzer</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> validate-policy</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --policy-type</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> IDENTITY_POLICY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --policy-document</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> file://lantern-upload-policy.json</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --query</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"findings[].[findingType,issueCode]\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --output</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> table</span></span></code></pre></div>\n<p>النتيجة الفارغة تعني عدم وجود ملاحظات. أصلح كل <code dir=\"ltr\">ERROR</code> و<code dir=\"ltr\">SECURITY_WARNING</code>، واقرأ كل <code dir=\"ltr\">WARNING</code> و<code dir=\"ltr\">SUGGESTION</code>.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>السياسات كود</span></p>\n<p>احتفظ بسياسات Lantern في المستودع بجوار التطبيق، وراجعها في pull requests، وتحقّق منها في الـ CI. تغيير السياسة قد يُحدث ضررًا أكبر من تغيير الكود، لذا يستحق مراجعة لا تقل عنه.</p>\n</aside>\n<p>أصبحت تستطيع كتابة صلاحيات محكمة. في الدرس التالي: كيف يحصل عليها الأشخاص دون مستخدمي IAM، وكيف يحصل عليها الكود دون مفاتيح وصول.</p>\n","toc":[{"id":"sec-1","text":"تشريح العبارة","level":2},{"id":"sec-2","text":"ARN الـ bucket أم ARN الكائن؟","level":2},{"id":"sec-3","text":"أقل الصلاحيات عمليًا","level":2},{"id":"sec-4","text":"الشروط: متى ينطبق السماح","level":2},{"id":"sec-5","text":"تحقّق قبل أن ترفق","level":2}],"takeaways":["العبارة (statement) هي Effect وAction وResource، ويمكن تضييقها اختياريًا بـ Condition؛ وتسمّي السياسات القائمة على المورد Principal أيضًا.","الإجراءات على مستوى الـ bucket مثل s3:ListBucket تأخذ ARN الـ bucket، أما الإجراءات على مستوى الكائن مثل s3:GetObject فتحتاج إلى ARN الـ bucket متبوعًا بـ /* أو بمسار أضيق.","أقل الصلاحيات (least privilege) يعني أن تسرد الإجراءات والموارد المحدّدة التي تحتاجها المهمة، ثم لا توسّعها إلا حين يخبرك خطأ AccessDenied حقيقي بذلك.","مفاتيح الشروط تُجمع بينها بـ AND، والقيم المتعدّدة للمفتاح الواحد تُجمع بـ OR.","تحقّق من كل سياسة قبل إرفاقها باستخدام IAM Access Analyzer، في محرّر الـ console أو عبر aws accessanalyzer validate-policy."],"further":[{"title":"IAM JSON policy element reference","url":"https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html"},{"title":"AWS global condition context keys","url":"https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html"},{"title":"Security best practices in IAM","url":"https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html"},{"title":"Validate policies with IAM Access Analyzer","url":"https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-policy-validation.html"}],"quiz":[{"q":"<p>دالة لها هذه السياسة تستدعي <code dir=\"ltr\">GetObject</code> على <code dir=\"ltr\">posters/2026/festival.jpg</code> في الـ bucket المسمّى <code dir=\"ltr\">lantern-posters</code> فتحصل على AccessDenied. لماذا؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"json\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">JSON</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Effect\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Allow\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Action\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"s3:GetObject\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Resource\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"arn:aws:s3:::lantern-posters\"</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n","options":[{"text":"يجب أن يكون الـ Action هو <code dir=\"ltr\">s3:Get*</code> لأن <code dir=\"ltr\">GetObject</code> ليس اسم إجراء حقيقيًا.","why":"الإجراء <code dir=\"ltr\">s3:GetObject</code> حقيقي. والرمز البديل سيوسّع السياسة فقط دون أن يصلح المشكلة الفعلية."},{"text":"الـ Resource هو الـ bucket، لكن GetObject يعمل على الكائنات، فيحتاج إلى <code dir=\"ltr\">arn:aws:s3:::lantern-posters/*</code>.","why":"صحيح. إجراءات الكائنات تطابق ARNs الكائنات. ومن دون <code dir=\"ltr\">/*</code> (أو مسار أضيق) لا تطابق العبارة الطلب أبدًا."},{"text":"يجب أن تتضمّن سياسات S3 عنصر Principal، لذا تُتجاهل العبارة.","why":"السياسات القائمة على الهوية ليس فيها Principal؛ فالهوية المرفقة بها هي الـ principal. السياسات القائمة على المورد وحدها تحتاج إليه."},{"text":"سطر Version مفقود، لذا يرفض IAM السياسة كلها.","why":"من دون Version يعود IAM إلى لغة 2008 القديمة وتتوقف متغيّرات السياسات عن العمل، لكن عبارة بسيطة كهذه تظل تطابق."}],"answer":1},{"q":"<p>أيّ الطلبات يسمح بها هذا الشرط؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"json\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">JSON</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Condition\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"StringEquals\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: { </span><span style=\"color:#116329;--shiki-dark:#7EE787\">\"aws:RequestedRegion\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"eu-west-1\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"eu-south-2\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] },</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Bool\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: { </span><span style=\"color:#116329;--shiki-dark:#7EE787\">\"aws:SecureTransport\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"true\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n","options":[{"text":"الطلبات إلى أيٍّ من الـ Regions المذكورة، سواء استخدمت TLS أم لا.","why":"المفتاحان يُجمعان بـ AND، لذا يظل فحص TLS منطبقًا على كل طلب."},{"text":"الطلبات التي تستخدم TLS، إلى أي Region.","why":"المفاتيح المختلفة تُجمع بـ AND. ويظل على مفتاح الـ Region أن يطابق إحدى القيم المدرجة له."},{"text":"الطلبات إلى eu-west-1 أو eu-south-2 التي تستخدم TLS أيضًا.","why":"صحيح. القيم داخل المفتاح الواحد تُجمع بـ OR (أيٌّ من الاثنتين)، والمفاتيح المنفصلة تُجمع بـ AND (مع TLS)."}],"answer":2},{"q":"<p>تحتاج إلى سياسة لدالة تغيير حجم الملصقات في Lantern، لكنك لست متأكّدًا من إجراءات S3 التي تستدعيها. ما أفضل نقطة بداية؟</p>","options":[{"text":"امنح <code dir=\"ltr\">s3:*</code> على <code dir=\"ltr\">*</code> وضيّقها لاحقًا حين يتوفّر الوقت.","why":"نادرًا ما يأتي ذلك اللاحق، وفي الأثناء يستطيع أي خطأ أو اختراق في الدالة أن يمسّ كل bucket في الحساب."},{"text":"امنح الإجراءات التي تعرف أنها تحتاجها على مسار الـ bucket الدقيق، ثم لا تضف إجراءات إلا حين يُظهر AccessDenied حقيقي أنها ناقصة.","why":"صحيح. أخطاء AccessDenied تسمّي الإجراء الناقص، فالتوسيع بناءً على الدليل سريع ويُبقي السياسة محكمة."},{"text":"أرفق السياسة المُدارة من AWS <code dir=\"ltr\">AmazonS3FullAccess</code> لأن AWS تتولّى صيانتها.","why":"تتولّى AWS صيانة نص السياسة، لا مدى ملاءمتها لمهمتك. الوصول الكامل إلى كل bucket أوسع بكثير مما تحتاجه دالة واحدة."}],"answer":1},{"q":"<p>ماذا تفعل عبارة سياسة الـ bucket هذه؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"json\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">JSON</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Effect\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Deny\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Principal\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"*\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Action\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"s3:*\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Resource\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"arn:aws:s3:::lantern-posters\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"arn:aws:s3:::lantern-posters/*\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">],</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Condition\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: { </span><span style=\"color:#116329;--shiki-dark:#7EE787\">\"Bool\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: { </span><span style=\"color:#116329;--shiki-dark:#7EE787\">\"aws:SecureTransport\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"false\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n","options":[{"text":"تحجب كل طلب إلى الـ bucket، بما في ذلك طلبات المديرين.","why":"يقصرها الشرط على الطلبات التي تكون فيها SecureTransport بقيمة false، أي HTTP العادي. طلبات HTTPS لا تتأثّر."},{"text":"تحجب أي طلب يُرسَل عبر HTTP العادي، من أي أحد.","why":"صحيح. إنها حاجز حماية قياسي. ومع قاعدة الرفض الصريح، لا يستطيع أحد القراءة من الـ bucket أو الكتابة فيه دون TLS."},{"text":"تسمح للمستخدمين المجهولين بقراءة الـ bucket عبر HTTPS.","why":"عبارة Deny لا تمنح أي شيء أبدًا. الوصول العام يحتاج إلى Allow وإلى إيقاف Block Public Access."}],"answer":1}],"exercise":{"kind":"spot-bug","language":"json","code":"{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"ReadWriteRsvps\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"dynamodb:*\",\n      \"Resource\": \"arn:aws:dynamodb:eu-west-1:111122223333:table/lantern-rsvps\"\n    },\n    {\n      \"Sid\": \"ReadPosters\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"s3:GetObject\",\n      \"Resource\": \"arn:aws:s3:::lantern-posters\"\n    },\n    {\n      \"Sid\": \"DeletePostersOnlyWithMfa\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"s3:DeleteObject\",\n      \"Resource\": \"arn:aws:s3:::lantern-posters/*\",\n      \"Condition\": {\n        \"Bool\": { \"aws:MultiFactorAuthPresent\": \"false\" }\n      }\n    }\n  ]\n}","bugLines":[7,14,22],"title":"راجع سياسة Developers","prompt":"<p>كتب أحد زملائك هذه السياسة القائمة على الهوية لمجموعة <strong>Developers</strong> في Lantern. والمقصود، بحسب التذكرة: يستطيع المطوّرون <strong>قراءة تأكيدات الحضور وكتابتها</strong> (get وput وquery للعناصر) في جدول <code dir=\"ltr\">lantern-rsvps</code>، و<strong>قراءة أي ملصق</strong> في الـ bucket المسمّى <code dir=\"ltr\">lantern-posters</code>، و<strong>حذف الملصقات فقط عند تسجيل الدخول باستخدام MFA</strong>. اختر كل سطر يخالف هذا المقصود.</p>\n","hints":["قارن كل Action بالأفعال الواردة في التذكرة. هل يوجد شيء أوسع مما طُلب؟","هل <code dir=\"ltr\">s3:GetObject</code> إجراء على مستوى الـ bucket أم على مستوى الكائن؟ تحقّق من الـ Resource الخاص به.","اقرأ شرط MFA بصوت عالٍ: تنطبق العبارة حين يكون وجود MFA … ماذا؟"],"explanation":"<p>السطر 7 يمنح <code dir=\"ltr\">dynamodb:*</code>، وهذا يشمل <code dir=\"ltr\">DeleteTable</code> و<code dir=\"ltr\">UpdateTable</code>؛ والتذكرة لا تحتاج إلا إلى <code dir=\"ltr\">dynamodb:GetItem</code> و<code dir=\"ltr\">dynamodb:PutItem</code> و<code dir=\"ltr\">dynamodb:Query</code>، فاسردها هي. السطر 14 يستخدم ARN الـ bucket مع <code dir=\"ltr\">s3:GetObject</code>، وهو إجراء على مستوى الكائن، فلا تطابق العبارة أي تنزيل حقيقي أبدًا؛ إنها تحتاج إلى <code dir=\"ltr\">arn:aws:s3:::lantern-posters/*</code>. السطر 22 يعكس الشرط: يسمح بالحذف فقط عندما يكون MFA <strong>غائبًا</strong>. يجب أن يكون <code dir=\"ltr\">&quot;aws:MultiFactorAuthPresent&quot;: &quot;true&quot;</code>. الأسطر بعد الإصلاح:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"json\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">JSON</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Action\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"dynamodb:GetItem\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"dynamodb:PutItem\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"dynamodb:Query\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">],</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Resource\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"arn:aws:s3:::lantern-posters/*\"</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Bool\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: { </span><span style=\"color:#116329;--shiki-dark:#7EE787\">\"aws:MultiFactorAuthPresent\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"true\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span></code></pre></div>\n<p>قد يرصد التحقّق من السياسات بعض هذه الأخطاء، لكن شرط MFA المعكوس JSON صالح تمامًا وسياسة صالحة تمامًا. لا تكشفه إلا قراءة السياسة ومقارنتها بالمقصود.</p>\n","checkLabels":{}},"words":632,"readMinutes":4}