Entra ID는 Microsoft의 클라우드 기반 아이덴티티 및 액세스 관리(IAM) 플랫폼으로, Microsoft 365 및 Azure Resource Manager와 같은 서비스의 기본 인증 및 권한 부여 시스템 역할을 합니다. Azure AD는 OAuth 2.0 권한 부여 프레임워크와 OpenID Connect (OIDC) 인증 프로토콜을 구현하여 리소스에 대한 액세스를 관리합니다.
OAuth
OAuth 2.0의 주요 참여자:
리소스 서버 (RS): 리소스 소유자가 소유한 리소스를 보호합니다.
리소스 소유자 (RO): 일반적으로 보호된 리소스를 소유한 최종 사용자입니다.
클라이언트 애플리케이션 (CA): 리소스 소유자를 대신하여 리소스에 대한 액세스를 요청하는 애플리케이션입니다.
권한 부여 서버 (AS): 클라이언트 애플리케이션을 인증하고 권한을 부여한 후 액세스 토큰을 발급합니다.
범위 및 동의:
범위: 액세스 수준을 지정하는 리소스 서버에서 정의된 세분화된 권한입니다.
동의: 리소스 소유자가 특정 범위로 리소스에 접근할 수 있도록 클라이언트 애플리케이션에 권한을 부여하는 과정입니다.
Microsoft 365 통합:
Microsoft 365는 IAM을 위해 Azure AD를 사용하며 여러 개의 "1차" OAuth 애플리케이션으로 구성됩니다.
이러한 애플리케이션은 깊이 통합되어 있으며 종종 상호 의존적인 서비스 관계를 가집니다.
사용자 경험을 단순화하고 기능을 유지하기 위해 Microsoft는 이러한 1차 애플리케이션에 "암묵적 동의" 또는 "사전 동의"를 부여합니다.
암묵적 동의: 특정 애플리케이션은 명시적인 사용자 또는 관리자 승인 없이 특정 범위에 대한 액세스를 자동으로 부여받습니다.
이러한 사전 동의된 범위는 일반적으로 사용자와 관리자 모두에게 숨겨져 있어 표준 관리 인터페이스에서 덜 보입니다.
클라이언트 애플리케이션 유형:
기밀 클라이언트:
자체 자격 증명(예: 비밀번호 또는 인증서)을 보유합니다.
권한 부여 서버에 안전하게 인증할 수 있습니다.
공개 클라이언트:
고유한 자격 증명이 없습니다.
권한 부여 서버에 안전하게 인증할 수 없습니다.
보안 의미: 공격자는 토큰을 요청할 때 공개 클라이언트 애플리케이션을 가장할 수 있으며, 권한 부여 서버가 애플리케이션의 정당성을 확인할 수 있는 메커니즘이 없습니다.
Authentication Tokens
OIDC에서 사용되는 세 가지 유형의 토큰이 있습니다:
액세스 토큰: 클라이언트가 리소스 서버에 이 토큰을 제시하여 리소스에 접근합니다. 특정 사용자, 클라이언트 및 리소스의 조합에 대해서만 사용할 수 있으며 만료될 때까지 취소할 수 없습니다 - 기본적으로 1시간입니다.
ID 토큰: 클라이언트가 권한 부여 서버로부터 이 토큰을 받습니다. 사용자에 대한 기본 정보를 포함합니다. 특정 사용자와 클라이언트의 조합에 바인딩되어 있습니다.
리프레시 토큰: 액세스 토큰과 함께 클라이언트에 제공됩니다. 새 액세스 및 ID 토큰을 얻는 데 사용됩니다. 특정 사용자와 클라이언트의 조합에 바인딩되어 있으며 취소할 수 있습니다. 기본 만료는 비활성 리프레시 토큰의 경우 90일이며 활성 토큰의 경우 만료가 없습니다 (리프레시 토큰에서 새 리프레시 토큰을 얻는 것이 가능합니다).
리프레시 토큰은 aud, 일부 범위, 및 테넌트에 연결되어야 하며, 해당 aud, 범위(그리고 그 이상) 및 테넌트에 대한 액세스 토큰만 생성할 수 있어야 합니다. 그러나 FOCI 애플리케이션 토큰의 경우 이는 해당되지 않습니다.
리프레시 토큰은 암호화되어 있으며 Microsoft만 이를 복호화할 수 있습니다.
새 리프레시 토큰을 얻는 것은 이전 리프레시 토큰을 취소하지 않습니다.
조건부 액세스에 대한 정보는 JWT 내부에 저장됩니다. 따라서 허용된 IP 주소에서 토큰을 요청하면 해당 IP가 토큰에 저장되며, 이후 허용되지 않은 IP에서 리소스에 접근하기 위해 해당 토큰을 사용할 수 있습니다.
Access Tokens "aud"
"aud" 필드에 표시된 필드는 리소스 서버(애플리케이션)로, 로그인 수행에 사용됩니다.
명령어 az account get-access-token --resource-type [...]는 다음 유형을 지원하며, 각 유형은 결과 액세스 토큰에 특정 "aud"를 추가합니다:
다음은 az account get-access-token에서 지원하는 API일 뿐이며, 더 많은 API가 있습니다.
aud 예시
aad-graph (Azure Active Directory Graph API): 레거시 Azure AD Graph API(사용 중단됨)에 접근하는 데 사용되며, 애플리케이션이 Azure Active Directory(Azure AD)에서 디렉터리 데이터를 읽고 쓸 수 있도록 합니다.
https://graph.windows.net/
arm (Azure Resource Manager): Azure Resource Manager API를 통해 Azure 리소스를 관리하는 데 사용됩니다. 여기에는 가상 머신, 스토리지 계정 등과 같은 리소스를 생성, 업데이트 및 삭제하는 작업이 포함됩니다.
https://management.core.windows.net/ or https://management.azure.com/
batch (Azure Batch Services): 클라우드에서 대규모 병렬 및 고성능 컴퓨팅 애플리케이션을 효율적으로 실행할 수 있도록 하는 Azure Batch에 접근하는 데 사용됩니다.
https://batch.core.windows.net/
data-lake (Azure Data Lake Storage): Azure Data Lake Storage Gen1과 상호작용하는 데 사용되며, 이는 확장 가능한 데이터 저장 및 분석 서비스입니다.
https://datalake.azure.net/
media (Azure Media Services): 비디오 및 오디오 콘텐츠를 위한 클라우드 기반 미디어 처리 및 전달 서비스를 제공하는 Azure Media Services에 접근하는 데 사용됩니다.
https://rest.media.azure.net
ms-graph (Microsoft Graph API): Microsoft 365 서비스 데이터에 대한 통합 엔드포인트인 Microsoft Graph API에 접근하는 데 사용됩니다. Azure AD, Office 365, Enterprise Mobility 및 Security 서비스와 같은 서비스에서 데이터 및 통찰력을 접근할 수 있습니다.
https://graph.microsoft.com
oss-rdbms (Azure Open Source Relational Databases): MySQL, PostgreSQL 및 MariaDB와 같은 오픈 소스 관계형 데이터베이스 엔진을 위한 Azure Database 서비스에 접근하는 데 사용됩니다.
https://ossrdbms-aad.database.windows.net
Access Tokens Scopes "scp"
액세스 토큰의 범위는 액세스 토큰 JWT 내부의 scp 키에 저장됩니다. 이러한 범위는 액세스 토큰이 접근할 수 있는 내용을 정의합니다.
JWT가 특정 API에 연락할 수 있도록 허용되지만 요청된 작업을 수행할 범위가 없는 경우, 해당 JWT로는 작업을 수행할 수 없습니다.
Get refresh & access token example
# Code example from https://github.com/secureworks/family-of-client-ids-researchimport msalimport requestsimport jwtfrom pprint import pprintfrom typing import Any, Dict, List# LOGIN VIA CODE FLOW AUTHENTICATIONazure_cli_client = msal.PublicClientApplication("04b07795-8ddb-461a-bbee-02f9e1bf7b46"# ID for Azure CLI client)device_flow = azure_cli_client.initiate_device_flow(scopes=["https://graph.microsoft.com/.default"])print(device_flow["message"])# Perform device code flow authenticationazure_cli_bearer_tokens_for_graph_api = azure_cli_client.acquire_token_by_device_flow(device_flow)pprint(azure_cli_bearer_tokens_for_graph_api)# DECODE JWTdefdecode_jwt(base64_blob:str) -> Dict[str, Any]:"""Decodes base64 encoded JWT blob"""return jwt.decode(base64_blob, options={"verify_signature": False, "verify_aud": False})decoded_access_token =decode_jwt(azure_cli_bearer_tokens_for_graph_api.get("access_token"))pprint(decoded_access_token)# GET NEW ACCESS TOKEN AND REFRESH TOKENnew_azure_cli_bearer_tokens_for_graph_api = (# Same client as original authorizationazure_cli_client.acquire_token_by_refresh_token(azure_cli_bearer_tokens_for_graph_api.get("refresh_token"),# Same scopes as original authorizationscopes=["https://graph.microsoft.com/.default"],))pprint(new_azure_cli_bearer_tokens_for_graph_api)
FOCI 토큰 권한 상승
이전에 언급했듯이, 리프레시 토큰은 생성된 범위와 애플리케이션 및 테넌트에 연결되어야 합니다. 이러한 경계 중 하나라도 깨지면, 사용자가 접근할 수 있는 다른 리소스와 테넌트에 대한 액세스 토큰을 생성할 수 있으므로 권한을 상승시킬 수 있습니다. 이는 원래 의도한 것보다 더 많은 범위를 가질 수 있습니다.
게다가, 이는 모든 리프레시 토큰에 대해 가능합니다Microsoft identity platform (Microsoft Entra 계정, Microsoft 개인 계정, Facebook 및 Google과 같은 소셜 계정)에서, 왜냐하면 문서에서 언급하듯이: "리프레시 토큰은 사용자와 클라이언트의 조합에 바인딩되지만, 리소스나 테넌트에 묶여 있지 않습니다. 클라이언트는 리프레시 토큰을 사용하여 리소스와 테넌트의 조합에 관계없이 액세스 토큰을 획득할 수 있습니다. 리프레시 토큰은 암호화되어 있으며 Microsoft identity platform만 읽을 수 있습니다."
또한, FOCI 애플리케이션은 공개 애플리케이션이므로 서버에 인증하기 위해 비밀이 필요하지 않습니다.
# Code from https://github.com/secureworks/family-of-client-ids-researchazure_cli_bearer_tokens_for_outlook_api = (# Same client as original authorizationazure_cli_client.acquire_token_by_refresh_token(new_azure_cli_bearer_tokens_for_graph_api.get("refresh_token"),# But different scopes than original authorizationscopes=["https://outlook.office.com/.default"],))pprint(azure_cli_bearer_tokens_for_outlook_api)
다양한 클라이언트 및 범위 가져오기
# Code from https://github.com/secureworks/family-of-client-ids-researchmicrosoft_office_client = msal.PublicClientApplication("d3590ed6-52b3-4102-aeff-aad2292ab01c")microsoft_office_bearer_tokens_for_graph_api = (# This is a different client application than we used in the previous examplesmicrosoft_office_client.acquire_token_by_refresh_token(# But we can use the refresh token issued to our original client applicationazure_cli_bearer_tokens_for_outlook_api.get("refresh_token"),# And request different scopes tooscopes=["https://graph.microsoft.com/.default"],))# How is this possible?pprint(microsoft_office_bearer_tokens_for_graph_api)