Beherrschen der PowerShell Graph API: Leicht verständliche Einblicke

معرض Microsoft Graph API هو خدمة تتيح لك قراءة وتعديل وإدارة معظم جوانب Azure AD و Office 365 تحت واجهة برمجة تطبيقات REST واحدة. في هذا المقال، تعلم كيفية تحويل واجهة برمجة التطبيقات الخاصة بك إلى واجهة برمجة PowerShell Graph API.

المتطلبات المسبقة

إذا كنت ترغب في متابعتي في هذا المقال، تأكد أولاً من تحقيق الشروط التالية:

  • تشغيل Windows PowerShell 5.1 (هذه هي الإصدار الذي قمت بالاختبار معه. قد تعمل الإصدارات الأخرى ولكن ليست مضمونة)
  • مستأجر Azure
  • مصادقة على Azure باستخدام حساب يتمتع بأذونات مسؤول عام على مستوى المجال أو أذونات تسجيل التطبيق في الاشتراك ومسؤول عام لقبول طلبات تسجيل التطبيق الخاصة بك.

إنشاء هوية تطبيق لـ Microsoft Graph API

للوصول إلى Microsoft Graph API، تحتاج في البداية إلى هوية للحصول على رمز OAuth. يتم ذلك في المقام الأول من خلال هوية تطبيق يمكنك إنشاؤها في بوابة Azure. يمكنك إنشاء هوية تطبيق عبر بوابة Azure. للقيام بذلك:

  • انتقل إلى بوابة Azure وانتقل إلى Azure Active Directory.
  • انقر فوق تسجيل التطبيقات تحت الإدارة في القائمة اليسرى وانقر فوق زر التسجيل الجديد.
Authenticating before creating the PowerShell Graph API
  • أدخل اسمًا لتطبيقك وانقر فوق تسجيل.
  • انسخ معرف التطبيق لاستخدامه لاحقًا.
Registering an application

إنشاء مفاتيح لـ Microsoft Graph API

يمكنك المصادقة على واجهة برمجة التطبيقات (Graph API) باستخدام طريقتين رئيسيتين: AppId/Secret والمصادقة بناءً على الشهادة. ستحتاج إلى المصادقة عند الاتصال بواجهة برمجة التطبيقات باستخدام PowerShell.

لنتناول كيفية المصادقة باستخدام كلا الطريقتين.

AppId/Secret

معرف التطبيق/السرّ هو عبارة عن اسم مستخدم/كلمة مرور عادية. يتكون معرف التطبيق من GUID بدلاً من اسم المستخدم والكلمة السرية هي سلسلة متعشّية.

لإنشاء سرّ، انقر فوق الشهادات والأسرار في القائمة اليسارية واضغط على سرّ عميل جديد.

Creating a client secret

أدخل وصفًا للسرّ وحدد متى تريد أن ينتهي الصلاحية. الآن، مسألة طلب الإذن فقط حتى تتمكن من الوصول إلى البيانات التي تريدها.

الشهادة

هناك إمكانية لإنشاء شهادة موقعة ذاتياً وتحميل مفتاحها العمومي إلى Azure. هذه الطريقة المفضلة والأكثر أمانًا للمصادقة.

ستحتاج أولاً إلى إنشاء شهادة موقعة ذاتياً. لحسن الحظ، يمكن القيام بذلك بسهولة باستخدام PowerShell.

# اسم المستأجر الخاص بك (يمكن أن يكون شيئًا أكثر وصفًا أيضًا)
$TenantName        = "contoso.onmicrosoft.com"

# حيث يتم تصدير الشهادة بدون المفتاح الخاص
$CerOutputPath     = "C:\Temp\PowerShellGraphCert.cer"

# المتجر الذي تريد أن تكون فيه
$StoreLocation     = "Cert:\CurrentUser\My"

# تاريخ انتهاء الصلاحية للشهادة الجديدة
$ExpirationDate    = (Get-Date).AddYears(2)


# رش متكتل لسهولة القراءة
$CreateCertificateSplat = @{
    FriendlyName      = "AzureApp"
    DnsName           = $TenantName
    CertStoreLocation = $StoreLocation
    NotAfter          = $ExpirationDate
    KeyExportPolicy   = "Exportable"
    KeySpec           = "Signature"
    Provider          = "Microsoft Enhanced RSA and AES Cryptographic Provider"
    HashAlgorithm     = "SHA256"
}

# إنشاء شهادة
$Certificate = New-SelfSignedCertificate @CreateCertificateSplat

# الحصول على مسار الشهادة
$CertificatePath = Join-Path -Path $StoreLocation -ChildPath $Certificate.Thumbprint

# تصدير الشهادة بدون المفتاح الخاص
Export-Certificate -Cert $CertificatePath -FilePath $CerOutputPath | Out-Null

الآن، قم بتحميل الشهادة الخاصة بك الموقعة ذاتيًا التي قمت بتصديرها إلى $CerOutputPath إلى تطبيق Azure الخاص بك بالنقر فوق الشهادات والأسرار في القائمة اليسارية والضغط على تحميل الشهادة.

Uploading a certificate

إضافة الأذونات إلى التطبيق

منح التطبيق الأذونات المناسبة أمر مهم – ليس فقط لوظيفة تطبيقك ولكن أيضًا للأمان. يمكن العثور على معرفة ذلك و (تقريبًا) كل شيء آخر في واجهة برمجة تطبيقات Microsoft Graph في الوثائق.

بمجرد أن أنشّئ هذا، سأجمع كل أحداث الأمان من مستأجري. للقيام بذلك، أحتاج SecurityEvents.Read.All كصلاحية دنيا. بهذا يمكنني جمع الأحداث المتعلقة بالسفر المستحيل والمستخدمين الذين يتصلون عبر VPN/TOR وما شابه.

لإضافة SecurityEvents.Read.All إلى تطبيقك – انقر على أذونات الواجهة البرمجية للتطبيقات ثم إضافة أذن. سيقدم لك ذلك ليس فقط واجهة برمجة الرسوم بل أيضًا العديد من التطبيقات الأخرى في خدمة Azure. معظم هذه التطبيقات سهلة الاتصال بها بمجرد معرفتك كيفية الاتصال بواجهة برمجة الرسوم لشركة Microsoft.

انقر على Microsoft Graph > أذونات التطبيق > أحداث الأمان وافحص SecurityEvents.Read.All. بعد ذلك، اضغط على زر إضافة أذن.

هل لاحظت أن العمود الموافقة الإدارية مطلوبة تم تعيينه إلى نعم على تلك الإذن؟ هذا يعني أن مسؤول المستأجر يحتاج إلى الموافقة قبل أن يتم إضافة الإذن إلى التطبيق.

Tenant admin permission approval

إذا كنت مسؤولاً عالمياً، اضغط على منح الموافقة الإدارية لـ أو اطلب من مسؤول عالمي الموافقة عليه. طلب الإذن من المستخدم بدلاً من مسؤول تعيين القراءة/الكتابة هو جزء كبير من المصادقة باستخدام OAuth. ولكن هذا يتيح لنا تجاوزه لمعظم الأذونات في واجهة برمجة الرسوم لشركة Microsoft.

ربما رأيت هذا بالفعل على Facebook أو Google: “هل تسمح لتطبيق X بالوصول إلى ملفك الشخصي؟”

Granting admin consent

الآن أنت مستعد تمامًا – لنقم بتسجيل الدخول والحصول على بعض البيانات!

احصل على رمز الوصول (معرّف التطبيق والسرّ)

سنحتاج إلى إرسال طلب للحصول على رمز وصول من نقطة نهاية Microsoft Graph OAuth. وفي جسم هذا الطلب، يجب علينا توفير:

  • client_id – معرّف التطبيق الخاص بك – مُشفر في عنوان URL
  • client_secret – سر تطبيقك – مُشفر في عنوان URL
  • scope – عنوان URL مُشفر يحدد ما الذي ترغب في الوصول إليه
  • grant_type – نوع أسلوب المصادقة الذي تستخدمه

عنوان URL لنقطة النهاية هو https://login.microsoftonline.com/<اسم المستأجر>/oauth2/v2.0/token. يمكنك طلب رمز الوصول باستخدام PowerShell وواجهة برمجة التطبيقات باستخدام قطعة الشيفرة التالية.

# حدد AppId و secret و scope واسم المستأجر الخاص بك وعنوان URL لنقطة النهاية
$AppId = '2d10909e-0396-49f2-ba2f-854b77c1e45b'
$AppSecret = 'abcdefghijklmnopqrstuv12345'
$Scope = "https://graph.microsoft.com/.default"
$TenantName = "contoso.onmicrosoft.com"

$Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"

# أضف System.Web لترميز عناوين URL
Add-Type -AssemblyName System.Web

# قم بإنشاء الجسم
$Body = @{
    client_id = $AppId
	client_secret = $AppSecret
	scope = $Scope
	grant_type = 'client_credentials'
}

# انتشل المعلمات لـ Invoke-Restmethod للحصول على شيفرة نظيفة
$PostSplat = @{
    ContentType = 'application/x-www-form-urlencoded'
    Method = 'POST'
    # قم بإنشاء سلسلة عن طريق الانضمام إلى bodylist بواسطة '&'
    Body = $Body
    Uri = $Url
}

# اطلب الرمز!
$Request = Invoke-RestMethod @PostSplat

احصل على رمز الوصول (باستخدام شهادة)

المصادقة على واجهة برمجة التطبيقات Microsoft Graph باستخدام شهادة تختلف قليلاً عن تدفق AppId/Secret العادي. للحصول على رمز وصول باستخدام شهادة، يجب عليك:

  1. إنشاء رأس رمز JWT في جافا.
  2. إنشاء حمولة JWT.
  3. قم بتوقيع رأس ال JWT والحمولة باستخدام الشهادة الذاتية الموقعة مسبقًا. سيتم إنشاء رمز وصول مصنوع ذاتيًا يُستخدم لطلب رمز وصول Microsoft Graph.
  4. إنشاء جسم طلب يحتوي على:
  • client_id=<معرف التطبيق>
  • client_assertion=<JWT>
  • client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
  • scope=<نطاق URLEncoded>
  • grant_type=client_credentials
  1. قم بعمل طلب برمز الجسم إلى نقطة النهاية oauth بـAuthorization=<JWT> في رأسه.

كيفية القيام بذلك لم يكن واضحًا في وثائق Microsoft ولكن هذا هو السكريبت باوشيل لجعل هذا يحدث:

$TenantName = "<your tenant name>.onmicrosoft.com"
$AppId = "<your application id"
$Certificate = Get-Item Cert:\CurrentUser\My\<self signed and uploaded cert thumbprint>
$Scope = "https://graph.microsoft.com/.default"

# إنشاء تجزئة base64 للشهادة
$CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash())

# إنشاء الطابع الزمني JWT للانتهاء
$StartDate = (Get-Date "1970-01-01T00:00:00Z" ).ToUniversalTime()
$JWTExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes(2)).TotalSeconds
$JWTExpiration = [math]::Round($JWTExpirationTimeSpan,0)

# إنشاء الطابع الزمني لبداية صلاحية JWT
$NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds
$NotBefore = [math]::Round($NotBeforeExpirationTimeSpan,0)

# إنشاء رأس JWT
$JWTHeader = @{
    alg = "RS256"
    typ = "JWT"
    # استخدام CertificateBase64Hash واستبدال / تجريد لمطابقة ترميز base64 للويب
    x5t = $CertificateBase64Hash -replace '\+','-' -replace '/','_' -replace '='
}

# إنشاء حمولة JWT
$JWTPayLoad = @{
    # أي الطرف المستهدف مسموح له باستخدام هذا JWT
    aud = "https://login.microsoftonline.com/$TenantName/oauth2/token"

    # الطابع الزمني للانتهاء
    exp = $JWTExpiration

    # المصدر = تطبيقك
    iss = $AppId

    # معرف JWT: معرف توجيه عشوائي
    jti = [guid]::NewGuid()

    # غير مراد استخدامه قبل
    nbf = $NotBefore

    # موضوع JWT
    sub = $AppId
}

# تحويل رأس وحمولة إلى base64
$JWTHeaderToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTHeader | ConvertTo-Json))
$EncodedHeader = [System.Convert]::ToBase64String($JWTHeaderToByte)

$JWTPayLoadToByte =  [System.Text.Encoding]::UTF8.GetBytes(($JWTPayload | ConvertTo-Json))
$EncodedPayload = [System.Convert]::ToBase64String($JWTPayLoadToByte)

# انضم إلى رأس و Payload بـ "." لإنشاء JWT صالح (غير موقع) 
$JWT = $EncodedHeader + "." + $EncodedPayload

# الحصول على كائن المفتاح الخاص لشهادتك
$PrivateKey = $Certificate.PrivateKey

# تعريف توقيع RSA وخوارزمية التجزئة
$RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
$HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256

# إنشاء توقيع لـ JWT
$Signature = [Convert]::ToBase64String(
    $PrivateKey.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT),$HashAlgorithm,$RSAPadding)
) -replace '\+','-' -replace '/','_' -replace '='

# انضمام التوقيع إلى JWT بـ "."
$JWT = $JWT + "." + $Signature

# إنشاء تجزئة مع معلمات الجسم
$Body = @{
    client_id = $AppId
    client_assertion = $JWT
    client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
    scope = $Scope
    grant_type = "client_credentials"

}

$Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"

# استخدام JWT المولدة ذاتيًا كترخيص
$Header = @{
    Authorization = "Bearer $JWT"
}

# فرز المعلمات لـ Invoke-Restmethod للحصول على كود نظيف أكثر
$PostSplat = @{
    ContentType = 'application/x-www-form-urlencoded'
    Method = 'POST'
    Body = $Body
    Uri = $Url
    Headers = $Header
}

$Request = Invoke-RestMethod @PostSplat

فهم إخراج طلب الوصول إلى الرمز

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

  • token_type – نوع الرمز
  • expires_in – الوقت بالثواني الذي يكون فيه رمز الوصول صالحًا
  • ext_expires_in – مثل expires_in ولكن لضمان الصمود في حالة انقطاع خدمة الرمز
  • access_token – هذا هو هدفنا

بعد ذلك، ستقوم بإنشاء رأس باستخدام token_type و access_token وتبدأ في إجراء طلبات باستخدام PowerShell إلى واجه برمجة التطبيقات في Microsoft Graph.

إجراء طلبات إلى واجه برمجة التطبيقات باستخدام PowerShell في Microsoft

ابدأ الآن في إجراء بعض الطلبات إلى واجهة برمجة التطبيقات.

وفقًا لمثالنا، ستحتاج أولاً إلى عنوان URL لعرض التنبيهات الأمان. تذكر استخدام وثائق واجه برمجة التطبيقات في Microsoft Graph لرؤية ما هو مطلوب.

في هذه الحالة، تحتاج إلى رأس مع Authorization=Bearer <access_token> وطلب GET نحو نقطة نهاية تنبيهات واجه برمجة التطبيقات. إليك كيفية القيام بذلك باستخدام PowerShell.

# إنشاء رأس
$Header = @{
    Authorization = "$($Request.token_type) $($Request.access_token)"
}

$Uri = "https://graph.microsoft.com/v1.0/security/alerts"

# جلب جميع التنبيهات الأمانية
$SecurityAlertsRequest = Invoke-RestMethod -Uri $Uri -Headers $Header -Method Get -ContentType "application/json"

$SecurityAlerts = $SecurityAlertsRequest.Value

الآن إذا كان لديك أي تنبيهات أمان في المتغير $SecurityAlerts، يجب أن تبدو مثل هذا:

$SecurityAlerts | select eventDateTime,Title

eventDateTime                title
-------------                -----
2019-08-05T17:59:47.6271981Z Atypical travel
2019-08-05T08:23:01.7325708Z Anonymous IP address
2019-08-05T08:23:55.5000456Z Anonymous IP address
2019-08-04T22:06:51.063797Z  Anonymous IP address
2019-08-04T21:56:10.981437Z  Anonymous IP address
2019-08-08T09:30:00Z         Creation of forwarding/redirect rule
2019-07-19T13:30:00Z         eDiscovery search started or exported
2019-07-19T08:00:00Z         eDiscovery search started or exported

فحص تنبيه أمان واحد كـ JSON سيظهر بهذا الشكل:

"id":  "censored",
    "azureTenantId":  "censored",
    "azureSubscriptionId":  "censored",
    "riskScore":  null,
    "tags":  [

             ],
    "activityGroupName":  null,
    "assignedTo":  null,
    "category":  "AnonymousLogin",
    "closedDateTime":  null,
    "comments":  [

                 ],
    "confidence":  null,
    "createdDateTime":  "2019-08-08T09:46:59.65722253Z",
    "description":  "Sign-in from an anonymous IP address (e.g. Tor browser, anonymizer VPNs)",
    "detectionIds":  [

                     ],
    "eventDateTime":  "2019-08-08T09:46:59.65722253Z",
    "feedback":  null,
    "lastModifiedDateTime":  "2019-08-08T09:54:30.7256251Z",
    "recommendedActions":  [

                           ],
    "severity":  "medium",
    "sourceMaterials":  [

                        ],
    "status":  "newAlert",
    "title":  "Anonymous IP address",
    "vendorInformation":  {
                              "provider":  "IPC",
                              "providerVersion":  null,
                              "subProvider":  null,
                              "vendor":  "Microsoft"
                          },
    "cloudAppStates":  [

                       ],
    "fileStates":  [

                   ],
    "hostStates":  [

                   ],
    "historyStates":  [

                      ],
    "malwareStates":  [

                      ],
    "networkConnections":  [

                           ],
    "processes":  [

                  ],
    "registryKeyStates":  [

                          ],
    "triggers":  [

                 ],
    "userStates":  [
                       {
                           "aadUserId":  "censored",
                           "accountName":  "john.doe",
                           "domainName":  "contoso.com",
                           "emailRole":  "unknown",
                           "isVpn":  null,
                           "logonDateTime":  "2019-08-08T09:45:59.6174156Z",
                           "logonId":  null,
                           "logonIp":  "censored",
                           "logonLocation":  "Denver, Colorado, US",
                           "logonType":  null,
                           "onPremisesSecurityIdentifier":  null,
                           "riskScore":  null,
                           "userAccountType":  null,
                           "userPrincipalName":  "[email protected]"
                       }
                   ],
    "vulnerabilityStates":  [

                            ]
}

فهم وإدارة إخراج واجهة برمجة التطبيقات

تحدد واجهة برمجة التطبيقات لـ Microsoft Graph حدًا لكل وظيفة على عدد العناصر التي ستعيد. يكون هذا الحد لكل وظيفة، ولكن لنفترض أنه 1000 عنصر. يعني ذلك أنه يمكنك الحصول فقط على حد أقصى من 1000 عنصر في طلبك.

عندما يتم الوصول إلى هذا الحد، سيتم استخدام تقسيم الصفحات لتقديم باقي العناصر. يتم ذلك عن طريق إضافة خاصية @odata.nextLink إلى إجابة طلبك. تحتوي @odata.nextLink على عنوان URL يمكنك استدعائه للحصول على الصفحة التالية من طلبك.

يمكنك قراءة جميع العناصر عن طريق التحقق من وجود هذه الخاصية واستخدام حلقة:

$Uri = "https://graph.microsoft.com/v1.0/auditLogs/signIns"

# جلب جميع تنبيهات الأمان
$AuditLogRequest = Invoke-RestMethod -Uri $Uri -Headers $Header -Method Get -ContentType "application/json"

$AuditLogs = @()
$AuditLogs+=$AuditLogRequest.value

while($AuditLogRequest.'@odata.nextLink' -ne $null) {
    $AuditLogRequest += Invoke-RestMethod -Uri $AuditLogRequest.'@odata.nextLink' -Headers $Header -Method Get -ContentType "application/json"
}

الاستنتاج

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

لحسن الحظ، هناك العديد من الوحدات المتاحة بالفعل للاستفادة من واجهة برمجة التطبيقات Microsoft Graph، ولكن قد تحتاج إلى إنشاء وحدتك الخاصة لتلبية احتياجاتك. باستخدام المهارات التي تعلمتها في هذا المقال، يجب أن تكون على مسار جيد.

لمزيد من المعلومات حول التحكم في الوصول الضيف في Office 365، قد كتبت مقالًا مفصلًا على مدونتي أشجعك على التحقق منه.

Source:
https://adamtheautomator.com/powershell-graph-api/