Compare commits

...

6 Commits

Author SHA1 Message Date
pedrooot
37737d04b9 fix(azure): add a list[str] inside scopes 2025-04-16 10:35:20 +02:00
pedrooot
60e4253629 fix(azure): tests 2025-03-19 13:30:51 +01:00
Rubén De la Torre Vico
fc3275f0a8 fix: create the Graph Service client different when is not standar Azure Cloud 2025-03-19 10:41:56 +01:00
pedrooot
eb67e381cf feat(azure): change logic WIP 2025-03-19 10:17:34 +01:00
pedrooot
98d8508d62 feat(azure): add new azure region and refactor scopes 2025-03-18 10:38:04 +01:00
pedrooot
a8211e9013 fix(azure): handle credentials scopes for Graph 2025-03-14 11:16:47 +01:00
9 changed files with 126 additions and 18 deletions

View File

@@ -6,7 +6,11 @@ By default, Prowler uses `AzureCloud` cloud which is the comercial one. (you can
At the time of writing this documentation the available Azure Clouds from different regions are the following:
- AzureCloud
- AzureChinaCloud
- AzureUSGovernment
- AzureUSGovernmentL4
- AzureUSGovernmentL5
???+ note
More information about Azure US Goverment region [here](https://devblogs.microsoft.com/microsoft365dev/new-microsoft-graph-endpoints-in-us-government-cloud/)
If you want to change the default one you must include the flag `--azure-region`, i.e.:

View File

@@ -17,7 +17,11 @@ from azure.identity import (
)
from azure.mgmt.subscription import SubscriptionClient
from colorama import Fore, Style
from msgraph import GraphServiceClient
from kiota_authentication_azure.azure_identity_authentication_provider import (
AzureIdentityAuthenticationProvider,
)
from msgraph import GraphRequestAdapter, GraphServiceClient
from msgraph_core import GraphClientFactory
from prowler.config.config import (
default_config_file_path,
@@ -202,7 +206,7 @@ class AzureProvider(Provider):
... sp_env_auth=True,
... browser_auth=False,
... managed_identity_auth=False,
... region="AzureUSGovernment",
... region="AzureUSGovernmentL4",
... )
- Subscriptions: rowler is multisubscription, which means that is going to scan all the subscriptions is able to list. If you only assign permissions to one subscription, it is going to scan a single one.
Prowler also allows you to specify the subscriptions you want to scan by passing a list of subscription IDs.
@@ -406,6 +410,8 @@ class AzureProvider(Provider):
authority=config["authority"],
base_url=config["base_url"],
credential_scopes=config["credential_scopes"],
graph_credential_scopes=config["graph_credential_scopes"],
graph_base_url=config["graph_base_url"],
)
except ArgumentTypeError as validation_error:
logger.error(
@@ -891,7 +897,31 @@ class AzureProvider(Provider):
logger.info(
"Trying to retrieve tenant domain from AAD to populate identity structure ..."
)
client = GraphServiceClient(credentials=credentials)
if self.region_config.name == "AzureCloud":
client = GraphServiceClient(
credentials=credentials,
scopes=self.region_config.graph_credential_scopes,
)
else:
auth_provider = AzureIdentityAuthenticationProvider(
credentials,
scopes=self.region_config.graph_credential_scopes,
allowed_hosts=[
self.region_config.graph_base_url.replace(
"http://", ""
).replace("https://", "")
],
)
http_client = GraphClientFactory.create_with_default_middleware(
host=self.region_config.graph_base_url
)
adapter = GraphRequestAdapter(
auth_provider=auth_provider, client=http_client
)
adapter.base_url = self.region_config.graph_base_url
client = GraphServiceClient(
request_adapter=adapter,
)
domain_result = await client.domains.get()
if getattr(domain_result, "value"):
@@ -930,9 +960,35 @@ class AzureProvider(Provider):
identity.identity_type = "User"
try:
logger.info(
"Trying to retrieve user information from AAD to populate identity structure ..."
"Trying to retrieve user information from Microsoft Graph to populate identity structure ..."
)
client = GraphServiceClient(credentials=credentials)
if self.region_config.name == "AzureCloud":
client = GraphServiceClient(
credentials=credentials,
scopes=self.region_config.graph_credential_scopes,
)
else:
auth_provider = AzureIdentityAuthenticationProvider(
credentials,
scopes=self.region_config.graph_credential_scopes,
allowed_hosts=[
self.region_config.graph_base_url.replace(
"http://", ""
).replace("https://", "")
],
)
http_client = (
GraphClientFactory.create_with_default_middleware(
host=self.region_config.graph_base_url
)
)
adapter = GraphRequestAdapter(
auth_provider=auth_provider, client=http_client
)
adapter.base_url = self.region_config.graph_base_url
client = GraphServiceClient(
request_adapter=adapter,
)
me = await client.me.get()
if me:

View File

@@ -59,7 +59,8 @@ def validate_azure_region(region):
"""validate_azure_region validates if the region passed as argument is valid"""
regions_allowed = [
"AzureChinaCloud",
"AzureUSGovernment",
"AzureUSGovernmentL4",
"AzureUSGovernmentL5",
"AzureCloud",
]
if region not in regions_allowed:

View File

@@ -1,26 +1,45 @@
from azure.identity import AzureAuthorityHosts
AZURE_CHINA_CLOUD = "https://management.chinacloudapi.cn"
AZURE_US_GOV_CLOUD = "https://management.usgovcloudapi.net"
AZURE_GENERIC_CLOUD = "https://management.azure.com"
AZURE_GRAPH_GLOBAL = "https://graph.microsoft.com"
AZURE_US_GOV_CLOUD = "https://management.usgovcloudapi.net"
AZURE_GRAPH_GOV_US_L4 = "https://graph.microsoft.us"
AZURE_GRAPH_GOV_US_L5 = "https://dod-graph.microsoft.us"
AZURE_CHINA_CLOUD = "https://management.chinacloudapi.cn"
AZURE_GRAPH_CHINA = "https://microsoftgraph.chinacloudapi.cn"
def get_regions_config(region):
allowed_regions = {
"AzureCloud": {
"authority": None,
"authority": AzureAuthorityHosts.AZURE_PUBLIC_CLOUD,
"base_url": AZURE_GENERIC_CLOUD,
"credential_scopes": [AZURE_GENERIC_CLOUD + "/.default"],
"graph_credential_scopes": [AZURE_GRAPH_GLOBAL + "/.default"],
"graph_base_url": AZURE_GRAPH_GLOBAL,
},
"AzureChinaCloud": {
"authority": AzureAuthorityHosts.AZURE_CHINA,
"base_url": AZURE_CHINA_CLOUD,
"credential_scopes": [AZURE_CHINA_CLOUD + "/.default"],
"graph_credential_scopes": [AZURE_GRAPH_CHINA + "/.default"],
"graph_base_url": AZURE_GRAPH_CHINA,
},
"AzureUSGovernment": {
"AzureUSGovernmentL4": {
"authority": AzureAuthorityHosts.AZURE_GOVERNMENT,
"base_url": AZURE_US_GOV_CLOUD,
"credential_scopes": [AZURE_US_GOV_CLOUD + "/.default"],
"graph_credential_scopes": [AZURE_GRAPH_GOV_US_L4 + "/.default"],
"graph_base_url": AZURE_GRAPH_GOV_US_L4,
},
"AzureUSGovernmentL5": {
"authority": AzureAuthorityHosts.AZURE_GOVERNMENT,
"base_url": AZURE_US_GOV_CLOUD,
"credential_scopes": [AZURE_US_GOV_CLOUD + "/.default"],
"graph_credential_scopes": [AZURE_GRAPH_GOV_US_L5 + "/.default"],
"graph_base_url": AZURE_GRAPH_GOV_US_L5,
},
}
return allowed_regions[region]

View File

@@ -24,7 +24,14 @@ class AzureService:
clients = {}
try:
if "GraphServiceClient" in str(service):
clients.update({identity.tenant_domain: service(credentials=session)})
clients.update(
{
identity.tenant_domain: service(
credentials=session,
scopes=region_config.graph_credential_scopes,
)
}
)
else:
for display_name, id in identity.subscriptions.items():
clients.update(

View File

@@ -18,6 +18,8 @@ class AzureRegionConfig(BaseModel):
authority: str = None
base_url: str = ""
credential_scopes: list = []
graph_credential_scopes: list = []
graph_base_url: str = ""
class AzureSubscription(BaseModel):

View File

@@ -1293,12 +1293,14 @@ class Test_Parser:
def test_validate_azure_region_valid_regions(self):
expected_regions = [
"AzureChinaCloud",
"AzureUSGovernment",
"AzureUSGovernmentL4",
"AzureUSGovernmentL5",
"AzureCloud",
]
input_regions = [
"AzureChinaCloud",
"AzureUSGovernment",
"AzureUSGovernmentL4",
"AzureUSGovernmentL5",
"AzureCloud",
]
for region in input_regions:
@@ -1307,7 +1309,8 @@ class Test_Parser:
def test_validate_azure_region_invalid_regions(self):
expected_regions = [
"AzureChinaCloud",
"AzureUSGovernment",
"AzureUSGovernmentL4",
"AzureUSGovernmentL5",
"AzureCloud",
]
invalid_region = "non-valid-region"

View File

@@ -66,9 +66,11 @@ class TestAzureProvider:
assert azure_provider.region_config == AzureRegionConfig(
name="AzureCloud",
authority=None,
authority="login.microsoftonline.com",
base_url="https://management.azure.com",
credential_scopes=["https://management.azure.com/.default"],
graph_credential_scopes=["https://graph.microsoft.com/.default"],
graph_base_url="https://graph.microsoft.com",
)
assert isinstance(azure_provider.session, DefaultAzureCredential)
assert azure_provider.identity == AzureIdentityInfo(

View File

@@ -3,6 +3,10 @@ from azure.identity import AzureAuthorityHosts
from prowler.providers.azure.lib.regions.regions import (
AZURE_CHINA_CLOUD,
AZURE_GENERIC_CLOUD,
AZURE_GRAPH_CHINA,
AZURE_GRAPH_GLOBAL,
AZURE_GRAPH_GOV_US_L4,
AZURE_GRAPH_GOV_US_L5,
AZURE_US_GOV_CLOUD,
get_regions_config,
)
@@ -13,23 +17,33 @@ class Test_azure_regions:
allowed_regions = [
"AzureCloud",
"AzureChinaCloud",
"AzureUSGovernment",
"AzureUSGovernmentL4",
"AzureUSGovernmentL5",
]
expected_output = {
"AzureCloud": {
"authority": None,
"base_url": AZURE_GENERIC_CLOUD,
"credential_scopes": [AZURE_GENERIC_CLOUD + "/.default"],
"graph_credential_scopes": [AZURE_GRAPH_GLOBAL],
},
"AzureChinaCloud": {
"authority": AzureAuthorityHosts.AZURE_CHINA,
"base_url": AZURE_CHINA_CLOUD,
"credential_scopes": [AZURE_CHINA_CLOUD + "/.default"],
"graph_credential_scopes": [AZURE_GRAPH_CHINA],
},
"AzureUSGovernment": {
"AzureUSGovernmentL4": {
"authority": AzureAuthorityHosts.AZURE_GOVERNMENT,
"base_url": AZURE_US_GOV_CLOUD,
"credential_scopes": [AZURE_US_GOV_CLOUD + "/.default"],
"graph_credential_scopes": [AZURE_GRAPH_GOV_US_L4],
},
"AzureUSGovernmentL5": {
"authority": AzureAuthorityHosts.AZURE_GOVERNMENT,
"base_url": AZURE_US_GOV_CLOUD,
"credential_scopes": [AZURE_US_GOV_CLOUD + "/.default"],
"graph_credential_scopes": [AZURE_GRAPH_GOV_US_L5],
},
}