PowerShell Graph API 마스터하기: 쉬운 따라하기

Microsoft Graph API는 단일 REST API 엔드포인트 아래에서 Azure AD와 Office 365의 거의 모든 측면을 읽고 수정하고 관리할 수 있는 서비스입니다. 이 문서에서는 API를 PowerShell Graph API로 변환하는 방법을 배우실 수 있습니다.

준비 사항

이 문서에서 함께 따라하려면 다음 요구 사항을 충족해야 합니다:

  • Windows PowerShell 5.1 실행(이 버전을 테스트했습니다. 다른 버전은 작동할 수 있지만 보장되지는 않습니다)
  • Azure 테넌트
  • 전역 관리자 권한이 있는 계정이나 구독에서 앱 등록 권한이 있는 계정으로 Azure에 인증되어 있으며 앱 등록 요청을 수락할 수 있는 전역 관리자

Microsoft Graph API용 응용 프로그램 ID 만들기

Microsoft Graph API에 액세스하려면 먼저 OAuth 토큰을 얻을 수 있는 ID가 필요합니다. 이는 주로 Azure Portal에서 생성할 수 있는 응용 프로그램 ID를 사용하여 수행됩니다. Azure Portal에서 응용 프로그램 ID를 만들 수 있습니다. 다음 단계를 따르세요:

  • Azure Portal로 이동하여 Azure Active Directory로 이동하세요.
  • 왼쪽 메뉴에서 Manage 아래의 App Registrations를 클릭하고 New registration 버튼을 클릭하세요.
Authenticating before creating the PowerShell Graph API
  • 응용 프로그램에 이름을 입력하고 Register를 클릭하세요.
  • 나중에 사용할 응용 프로그램 ID GUID를 복사하세요.
Registering an application

Microsoft Graph API용 보안 비밀 생성

그래프 API에는 두 가지 주요 인증 방법이 있습니다: AppId/Secret 및 인증서 기반 인증. PowerShell을 사용하여 그래프 API에 연결할 때 인증해야 합니다.

이제 두 가지 방법으로 인증하는 방법에 대해 살펴보겠습니다.

AppId/Secret

애플리케이션 ID/비밀은 일반적인 사용자 이름/비밀번호와 같습니다. 애플리케이션 ID는 사용자 이름 대신 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)


# 가독성을 위한 Splat
$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 API의 거의 모든 내용은 문서에서 찾을 수 있습니다.

한 번 설정이 완료되면, 나는 내 테넌트에서 모든 보안 이벤트를 수집할 것입니다. 이를 수행하기 위해 최소한 SecurityEvents.Read.All 권한이 필요합니다. 이렇게 하면 불가능한 이동 이벤트, VPN/TOR을 통한 사용자 연결 등을 수집하고 조치할 수 있습니다.

SecurityEvents.Read.All를 애플리케이션에 추가하려면 – API 권한을 클릭한 다음 권한 추가를 클릭하십시오. 이렇게 하면 그래프 API 뿐만 아니라 Azure의 다른 애플리케이션도 표시됩니다. 이러한 대부분의 애플리케이션은 Microsoft 그래프 API에 연결하는 방법을 알고 나면 쉽게 연결할 수 있습니다.

Microsoft Graph을 클릭한 다음 애플리케이션 권한 > 보안 이벤트를 선택하고 SecurityEvents.Read.All을 확인하십시오. 그런 다음 권한 추가 버튼을 누르십시오.

관리자 동의 필요 열에서 해당 권한의 값이 로 설정된 것을 보았나요? 이는 권한이 애플리케이션에 추가되기 전에 테넌트 관리자의 승인이 필요하다는 것을 의미합니다.

Tenant admin permission approval

글로벌 관리자인 경우, 관리자 동의 부여를 누르거나 글로벌 관리자에게 승인을 요청하십시오. 사용자 대신 관리자에게 읽기/쓰기 권한을 설정하는 것을 요청하는 것은 OAuth 인증의 큰 부분입니다. 그러나 이를 Microsoft Graph의 대부분의 권한에 대해 우회할 수 있게 해줍니다.

아마도 페이스북이나 Google에서 이미 이것을 보았을 것입니다: “애플리케이션 X가 프로필에 액세스할 수 있도록 허용하시겠습니까?”

Granting admin consent

이제 모든 준비가 되었습니다 – 로그인하여 데이터를 가져오겠습니다!

액세스 토큰(응용 프로그램 ID 및 비밀번호)을 획득하십시오.

이를 위해 Microsoft Graph OAuth 엔드포인트에서 액세스 토큰을 가져 오려면 요청을 게시해야합니다. 그 요청의 본문에는 다음을 제공해야합니다:

  • client_id – 응용 프로그램 ID – URL 인코딩
  • client_secret – 응용 프로그램 비밀번호 – URL 인코딩
  • scope – 액세스하려는 내용을 지정하는 URL 인코딩 URL
  • grant_type – 사용하는 인증 방법

엔드포인트의 URL은 https://login.microsoftonline.com/<tenantname>/oauth2/v2.0/token입니다. 아래 코드 스니펫을 사용하여 PowerShell 및 Graph API를 사용하여 액세스 토큰을 요청할 수 있습니다.

# 응용 프로그램 ID, 비밀번호 및 스코프, 테넌트 이름 및 엔드포인트 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"

# urlencode를 위해 System.Web 추가
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 API에 인증하는 방법은 일반적인 애플리케이션 ID/비밀번호 흐름과 약간 다릅니다. 인증서를 사용하여 액세스 토큰을 가져 오려면 다음을 수행해야합니다:

  1. JWT 헤더를 생성합니다.
  2. JWT 페이로드를 생성합니다.
  3. 이전에 생성한 자체 서명된 인증서로 JWT 헤더와 페이로드를 서명합니다. 이렇게 하면 Microsoft Graph 액세스 토큰을 요청하는 데 사용되는 자체 제작된 액세스 토큰이 생성됩니다.
  4. 다음 내용을 포함하는 요청 본문을 만듭니다:
  • client_id=<application id>
  • client_assertion=<the JWT>
  • client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
  • scope=<URLEncoded scope>
  • grant_type=client_credentials
  1. 해당 헤더에 Authorization=<JWT>를 포함하여 oauth 엔드포인트로 본문을 포함한 POST 요청을 만듭니다.

이 작업을 수행하는 방법은 Microsoft의 문서에서 명확하지 않았지만, 이 작업을 수행하기 위한 PowerShell 스크립트는 다음과 같습니다:

$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 ID: 랜덤 guid
    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)

# 유효한(서명되지 않은) 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

요청 액세스 토큰 출력 이해

애플리케이션 ID/비밀 또는 인증서를 통해 액세스 토큰을 획득한 후에는 네 개의 속성이 있는 객체를 볼 수 있어야 합니다.

  • token_type – 토큰의 종류
  • expires_in – 액세스 토큰의 유효 시간(초)
  • ext_expires_in – 토큰 서비스 중단에 대비한 expires_in과 유사한 값
  • access_token – 우리가 찾은 것

다음으로, token_typeaccess_token을 사용하여 헤더를 생성하고 PowerShell을 사용하여 Microsoft Graph API에 요청을 시작합니다.

Microsoft Powershell Graph API에 요청 만들기

이제 API에 일부 요청을 시작하겠습니다.

예제로 진행하면, 먼저 보안 경보 목록의 URL이 필요합니다. 필요한 것을 확인하려면 Microsoft Graph API 문서를 참조하십시오.

이 경우, Authorization=Bearer <access_token> 헤더와 Graph API Alerts 엔드포인트로의 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":  [

                            ]
}

API 출력 페이징 이해 및 관리

Microsoft Graph API는 반환할 항목 수에 대한 각 기능 당 제한이 있습니다. 이 제한은 각 기능 당이지만, 예를 들어 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"
}

결론

Microsoft Graph API에 인증하는 방법을 익힌 후에는 데이터를 수집하는 것이 매우 쉽습니다. 이는 사용되어야 할 정도로 강력한 서비스입니다.

다행히도 Microsoft Graph API를 활용하기 위한 많은 모듈이 이미 있지만, 자체적인 요구 사항을 충족시키기 위해 자체 모듈을 생성해야 할 수도 있습니다. 이 글에서 배운 기술을 사용하면 충분히 잘 할 수 있을 것입니다.

Office 365에서 게스트 액세스를 제어하는 자세한 내용은 블로그에서 깊이 있는 기사를 작성했으니 권장합니다. 확인해보세요.

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