mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
feat: adapt Microsoft365 provider to use PowerShell (#7331)
Co-authored-by: MrCloudSec <hello@mistercloudsec.com>
This commit is contained in:
committed by
GitHub
parent
c0d935e232
commit
52bd48168f
@@ -75,7 +75,7 @@ It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, Fe
|
||||
| GCP | 79 | 13 | 7 | 3 |
|
||||
| Azure | 140 | 18 | 8 | 3 |
|
||||
| Kubernetes | 83 | 7 | 4 | 7 |
|
||||
| Microsoft365 | 5 | 2 | 1 | 0 |
|
||||
| M365 | 5 | 2 | 1 | 0 |
|
||||
| NHN (Unofficial) | 6 | 2 | 1 | 0 |
|
||||
|
||||
> You can list the checks, services, compliance frameworks and categories with `prowler <provider> --list-checks`, `prowler <provider> --list-services`, `prowler <provider> --list-compliance` and `prowler <provider> --list-categories`.
|
||||
|
||||
@@ -175,7 +175,7 @@ Due to the complexity and differences of each provider use the rest of the provi
|
||||
- [GCP](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/gcp/gcp_provider.py)
|
||||
- [Azure](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/azure/azure_provider.py)
|
||||
- [Kubernetes](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/kubernetes/kubernetes_provider.py)
|
||||
- [Microsoft365](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/microsoft365/microsoft365_provider.py)
|
||||
- [M365](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/m365/m365_provider.py)
|
||||
|
||||
To facilitate understanding here is a pseudocode of how the most basic provider could be with examples.
|
||||
|
||||
|
||||
@@ -237,4 +237,4 @@ It is really important to check if the current Prowler's permissions for each pr
|
||||
- AWS: https://docs.prowler.cloud/en/latest/getting-started/requirements/#aws-authentication
|
||||
- Azure: https://docs.prowler.cloud/en/latest/getting-started/requirements/#permissions
|
||||
- GCP: https://docs.prowler.cloud/en/latest/getting-started/requirements/#gcp-authentication
|
||||
- Microsoft365: https://docs.prowler.cloud/en/latest/getting-started/requirements/#microsoft365-authentication
|
||||
- M365: https://docs.prowler.cloud/en/latest/getting-started/requirements/#m365-authentication
|
||||
|
||||
@@ -40,8 +40,8 @@ If your IAM entity enforces MFA you can use `--mfa` and Prowler will ask you to
|
||||
|
||||
Prowler for Azure supports the following authentication types. To use each one you need to pass the proper flag to the execution:
|
||||
|
||||
- [Service principal application](https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals?tabs=browser#service-principal-object) (recommended).
|
||||
- Current az cli credentials stored.
|
||||
- [Service Principal Application](https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals?tabs=browser#service-principal-object) (recommended).
|
||||
- Current AZ CLI credentials stored.
|
||||
- Interactive browser authentication.
|
||||
- [Managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview) authentication.
|
||||
|
||||
@@ -119,12 +119,13 @@ The above settings must be associated to a user or service account.
|
||||
???+ note
|
||||
By default, `prowler` will scan all accessible GCP Projects, use flag `--project-ids` to specify the projects to be scanned.
|
||||
|
||||
## Microsoft365
|
||||
## Microsoft 365
|
||||
|
||||
Prowler for Microsoft365 currently supports the following authentication types:
|
||||
Prowler for M365 currently supports the following authentication types:
|
||||
|
||||
- [Service principal application](https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals?tabs=browser#service-principal-object) (recommended).
|
||||
- Current az cli credentials stored.
|
||||
- [Service Principal Application](https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals?tabs=browser#service-principal-object).
|
||||
- Service Principal Application and Microsoft User Credentials (**recommended**).
|
||||
- Current AZ CLI credentials stored.
|
||||
- Interactive browser authentication.
|
||||
|
||||
|
||||
@@ -144,6 +145,35 @@ export AZURE_TENANT_ID="XXXXXXXXX"
|
||||
If you try to execute Prowler with the `--sp-env-auth` flag and those variables are empty or not exported, the execution is going to fail.
|
||||
Follow the instructions in the [Create Prowler Service Principal](../tutorials/azure/create-prowler-service-principal.md) section to create a service principal.
|
||||
|
||||
### Service Principal and User Credentials authentication (recommended)
|
||||
This authentication method follows the same approach as the service principal method but introduces two additional environment variables for user credentials: `M365_USER` and `M365_ENCRYPTED_PASSWORD`.
|
||||
|
||||
```console
|
||||
export AZURE_CLIENT_ID="XXXXXXXXX"
|
||||
export AZURE_CLIENT_SECRET="XXXXXXXXX"
|
||||
export AZURE_TENANT_ID="XXXXXXXXX"
|
||||
export M365_USER="your_email@example.com"
|
||||
export M365_ENCRYPTED_PASSWORD="6500780061006d0070006c006500700061007300730077006f0072006400" # replace this to yours
|
||||
```
|
||||
|
||||
These two new environment variables are required to execute the PowerShell modules needed to retrieve information from M365 services. Prowler will use service principal authentication to log into MS Graph and user credentials to authenticate to Microsoft PowerShell modules.
|
||||
|
||||
The `M365_USER` should be your Microsoft account email, and `M365_ENCRYPTED_PASSWORD` must be an encrypted SecureString.
|
||||
To convert your password into a valid encrypted string, run the following commands in PowerShell:
|
||||
|
||||
```console
|
||||
$securePassword = ConvertTo-SecureString "examplepassword" -AsPlainText -Force
|
||||
$encryptedPassword = $securePassword | ConvertFrom-SecureString
|
||||
```
|
||||
|
||||
If everything is done correctly, you will see the encrypted string that you need to set as the `M365_ENCRYPTED_PASSWORD` environment variable.
|
||||
```console
|
||||
Write-Output $encryptedPassword
|
||||
6500780061006d0070006c006500700061007300730077006f0072006400
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Interactive Browser authentication
|
||||
|
||||
To use `--browser-auth` the user needs to authenticate against Azure using the default browser to start the scan, also `--tenant-id` flag is required.
|
||||
|
||||
@@ -170,7 +170,7 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler/),
|
||||
|
||||
* `Python >= 3.9, <= 3.12`
|
||||
* `Python pip >= 21.0.0`
|
||||
* AWS, GCP, Azure, Microsoft365 and/or Kubernetes credentials
|
||||
* AWS, GCP, Azure, M365 and/or Kubernetes credentials
|
||||
|
||||
_Commands_:
|
||||
|
||||
@@ -423,7 +423,7 @@ While the scan is running, start exploring the findings in these sections:
|
||||
|
||||
### Prowler CLI
|
||||
|
||||
To run Prowler, you will need to specify the provider (e.g `aws`, `gcp`, `azure`, `microsoft365` or `kubernetes`):
|
||||
To run Prowler, you will need to specify the provider (e.g `aws`, `gcp`, `azure`, `m365` or `kubernetes`):
|
||||
|
||||
???+ note
|
||||
If no provider specified, AWS will be used for backward compatibility with most of v2 options.
|
||||
@@ -565,23 +565,23 @@ kubectl logs prowler-XXXXX --namespace prowler-ns
|
||||
???+ note
|
||||
By default, `prowler` will scan all namespaces in your active Kubernetes context. Use the flag `--context` to specify the context to be scanned and `--namespaces` to specify the namespaces to be scanned.
|
||||
|
||||
#### Microsoft365
|
||||
#### Microsoft 365
|
||||
|
||||
With Microsoft365 you need to specify which auth method is going to be used:
|
||||
With M365 you need to specify which auth method is going to be used:
|
||||
|
||||
```console
|
||||
# To use service principal authentication
|
||||
prowler microsoft365 --sp-env-auth
|
||||
prowler m365 --sp-env-auth
|
||||
|
||||
# To use az cli authentication
|
||||
prowler microsoft365 --az-cli-auth
|
||||
prowler m365 --az-cli-auth
|
||||
|
||||
# To use browser authentication
|
||||
prowler microsoft365 --browser-auth --tenant-id "XXXXXXXX"
|
||||
prowler m365 --browser-auth --tenant-id "XXXXXXXX"
|
||||
|
||||
```
|
||||
|
||||
See more details about Microsoft365 Authentication in [Requirements](getting-started/requirements.md#microsoft365)
|
||||
See more details about M365 Authentication in [Requirements](getting-started/requirements/#microsoft-365)
|
||||
|
||||
## Prowler v2 Documentation
|
||||
For **Prowler v2 Documentation**, please check it out [here](https://github.com/prowler-cloud/prowler/blob/8818f47333a0c1c1a457453c87af0ea5b89a385f/README.md).
|
||||
|
||||
@@ -97,14 +97,15 @@ The following list includes all the Kubernetes checks with configurable variable
|
||||
| `kubelet_strong_ciphers_only` | `kubelet_strong_ciphers` | String |
|
||||
|
||||
|
||||
## Microsoft365
|
||||
## M365
|
||||
|
||||
### Configurable Checks
|
||||
The following list includes all the Microsoft365 checks with configurable variables that can be changed in the configuration yaml file:
|
||||
The following list includes all the Microsoft 365 checks with configurable variables that can be changed in the configuration yaml file:
|
||||
|
||||
| Check Name | Value | Type |
|
||||
|---------------------------------------------------------------|--------------------------------------------------|-----------------|
|
||||
| `entra_admin_users_sign_in_frequency_enabled` | `sign_in_frequency` | Integer |
|
||||
| `teams_external_file_sharing_restricted` | `allowed_cloud_storage_services` | List of Strings |
|
||||
|
||||
|
||||
## Config YAML File Structure
|
||||
@@ -504,10 +505,20 @@ kubernetes:
|
||||
"TLS_RSA_WITH_AES_128_GCM_SHA256",
|
||||
]
|
||||
|
||||
# Microsoft365 Configuration
|
||||
microsoft365:
|
||||
# Conditional Access Policy
|
||||
# policy.session_controls.sign_in_frequency.frequency in hours
|
||||
sign_in_frequency: 4
|
||||
# M365 Configuration
|
||||
m365:
|
||||
# Entra Conditional Access Policy
|
||||
# m365.entra_admin_users_sign_in_frequency_enabled
|
||||
sign_in_frequency: 4 # 4 hours
|
||||
# Teams Settings
|
||||
# m365.teams_external_file_sharing_restricted
|
||||
allowed_cloud_storage_services:
|
||||
[
|
||||
#"allow_box",
|
||||
#"allow_drop_box",
|
||||
#"allow_egnyte",
|
||||
#"allow_google_drive",
|
||||
#"allow_share_file",
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
# Microsoft365 authentication
|
||||
# Microsoft 365 authentication
|
||||
|
||||
By default Prowler uses MsGraph Python SDK identity package authentication methods using the class `ClientSecretCredential`.
|
||||
This allows Prowler to authenticate against microsoft365 using the following methods:
|
||||
This allows Prowler to authenticate against Microsoft 365 using the following methods:
|
||||
|
||||
- Service principal authentication by environment variables (Enterprise Application)
|
||||
- Service principal and Microsoft user credentials by environment variabled (using PowerShell requires this authentication method)
|
||||
- Current CLI credentials stored
|
||||
- Interactive browser authentication
|
||||
|
||||
|
||||
To launch the tool first you need to specify which method is used through the following flags:
|
||||
|
||||
```console
|
||||
# To use service principal (app) authentication and Microsoft user credentials (to use PowerShell)
|
||||
prowler m365 --env-auth
|
||||
|
||||
# To use service principal authentication
|
||||
prowler microsoft365 --sp-env-auth
|
||||
prowler m365 --sp-env-auth
|
||||
|
||||
# To use cli authentication
|
||||
prowler microsoft365 --az-cli-auth
|
||||
prowler m365 --az-cli-auth
|
||||
|
||||
# To use browser authentication
|
||||
prowler microsoft365 --browser-auth --tenant-id "XXXXXXXX"
|
||||
prowler m365 --browser-auth --tenant-id "XXXXXXXX"
|
||||
```
|
||||
|
||||
To use Prowler you need to set up also the permissions required to access your resources in your Microsoft365 account, to more details refer to [Requirements](../../getting-started/requirements.md)
|
||||
To use Prowler you need to set up also the permissions required to access your resources in your Microsoft 365 account, to more details refer to [Requirements](../../getting-started/requirements.md#microsoft-365)
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
"# from prowler.providers.gcp.gcp_provider import GcpProvider\n",
|
||||
"# from prowler.providers.azure.azure_provider import AzureProvider\n",
|
||||
"# from prowler.providers.kubernetes.kubernetes_provider import KubernetesProvider\n",
|
||||
"# from prowler.providers.microsoft365.microsoft365_provider import Microsoft365Provider"
|
||||
"# from prowler.providers.m365.m365_provider import M365Provider"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
|
||||
- Add SOC2 compliance framework to Azure [(#7489)](https://github.com/prowler-cloud/prowler/pull/7489).
|
||||
- Add check for unused Service Accounts in GCP [(#7419)](https://github.com/prowler-cloud/prowler/pull/7419).
|
||||
- Add Powershell to Microsoft365 [(#7331)](https://github.com/prowler-cloud/prowler/pull/7331)
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ from prowler.lib.outputs.compliance.cis.cis_aws import AWSCIS
|
||||
from prowler.lib.outputs.compliance.cis.cis_azure import AzureCIS
|
||||
from prowler.lib.outputs.compliance.cis.cis_gcp import GCPCIS
|
||||
from prowler.lib.outputs.compliance.cis.cis_kubernetes import KubernetesCIS
|
||||
from prowler.lib.outputs.compliance.cis.cis_microsoft365 import Microsoft365CIS
|
||||
from prowler.lib.outputs.compliance.cis.cis_m365 import M365CIS
|
||||
from prowler.lib.outputs.compliance.compliance import display_compliance_table
|
||||
from prowler.lib.outputs.compliance.ens.ens_aws import AWSENS
|
||||
from prowler.lib.outputs.compliance.ens.ens_azure import AzureENS
|
||||
@@ -85,7 +85,7 @@ from prowler.providers.common.provider import Provider
|
||||
from prowler.providers.common.quick_inventory import run_provider_quick_inventory
|
||||
from prowler.providers.gcp.models import GCPOutputOptions
|
||||
from prowler.providers.kubernetes.models import KubernetesOutputOptions
|
||||
from prowler.providers.microsoft365.models import Microsoft365OutputOptions
|
||||
from prowler.providers.m365.models import M365OutputOptions
|
||||
from prowler.providers.nhn.models import NHNOutputOptions
|
||||
|
||||
|
||||
@@ -268,8 +268,8 @@ def prowler():
|
||||
output_options = KubernetesOutputOptions(
|
||||
args, bulk_checks_metadata, global_provider.identity
|
||||
)
|
||||
elif provider == "microsoft365":
|
||||
output_options = Microsoft365OutputOptions(
|
||||
elif provider == "m365":
|
||||
output_options = M365OutputOptions(
|
||||
args, bulk_checks_metadata, global_provider.identity
|
||||
)
|
||||
elif provider == "nhn":
|
||||
@@ -666,7 +666,7 @@ def prowler():
|
||||
generated_outputs["compliance"].append(generic_compliance)
|
||||
generic_compliance.batch_write_data_to_file()
|
||||
|
||||
elif provider == "microsoft365":
|
||||
elif provider == "m365":
|
||||
for compliance_name in input_compliance_frameworks:
|
||||
if compliance_name.startswith("cis_"):
|
||||
# Generate CIS Finding Object
|
||||
@@ -674,7 +674,7 @@ def prowler():
|
||||
f"{output_options.output_directory}/compliance/"
|
||||
f"{output_options.output_filename}_{compliance_name}.csv"
|
||||
)
|
||||
cis = Microsoft365CIS(
|
||||
cis = M365CIS(
|
||||
findings=finding_outputs,
|
||||
compliance=bulk_compliance_frameworks[compliance_name],
|
||||
file_path=filename,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"Framework": "CIS",
|
||||
"Version": "4.0",
|
||||
"Provider": "Microsoft365",
|
||||
"Provider": "M365",
|
||||
"Description": "The CIS Microsoft 365 Foundations Benchmark provides prescriptive guidance for establishing a secure configuration posture for Microsoft 365 Cloud offerings running on any OS.",
|
||||
"Requirements": [
|
||||
{
|
||||
@@ -28,7 +28,7 @@ class Provider(str, Enum):
|
||||
GCP = "gcp"
|
||||
AZURE = "azure"
|
||||
KUBERNETES = "kubernetes"
|
||||
MICROSOFT365 = "microsoft365"
|
||||
M365 = "m365"
|
||||
NHN = "nhn"
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ def load_and_validate_config_file(provider: str, config_file_path: str) -> dict:
|
||||
# and a new format with a key for each provider to include their configuration values within.
|
||||
if any(
|
||||
key in config_file
|
||||
for key in ["aws", "gcp", "azure", "kubernetes", "microsoft365"]
|
||||
for key in ["aws", "gcp", "azure", "kubernetes", "m365"]
|
||||
):
|
||||
config = config_file.get(provider, {})
|
||||
else:
|
||||
|
||||
@@ -483,8 +483,18 @@ kubernetes:
|
||||
]
|
||||
|
||||
|
||||
# Microsoft365 Configuration
|
||||
microsoft365:
|
||||
# Conditional Access Policy
|
||||
# policy.session_controls.sign_in_frequency.frequency in hours
|
||||
sign_in_frequency: 4
|
||||
# M365 Configuration
|
||||
m365:
|
||||
# Entra Conditional Access Policy
|
||||
# m365.entra_admin_users_sign_in_frequency_enabled
|
||||
sign_in_frequency: 4 # 4 hours
|
||||
# Teams Settings
|
||||
# m365.teams_external_file_sharing_restricted
|
||||
allowed_cloud_storage_services:
|
||||
[
|
||||
#"allow_box",
|
||||
#"allow_drop_box",
|
||||
#"allow_egnyte",
|
||||
#"allow_google_drive",
|
||||
#"allow_share_file",
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
### Account, Check and/or Region can be * to apply for all the cases.
|
||||
### Account == Microsoft365 Tenant and Region == Microsoft365 Location
|
||||
### Account == M365 Tenant and Region == M365 Location
|
||||
### Resources and tags are lists that can have either Regex or Keywords.
|
||||
### Tags is an optional list that matches on tuples of 'key=value' and are "ANDed" together.
|
||||
### Use an alternation Regex to match one of multiple tags with "ORed" logic.
|
||||
@@ -543,8 +543,8 @@ class Check_Report_Kubernetes(Check_Report):
|
||||
|
||||
|
||||
@dataclass
|
||||
class CheckReportMicrosoft365(Check_Report):
|
||||
"""Contains the Microsoft365 Check's finding information."""
|
||||
class CheckReportM365(Check_Report):
|
||||
"""Contains the M365 Check's finding information."""
|
||||
|
||||
resource_name: str
|
||||
resource_id: str
|
||||
@@ -558,7 +558,7 @@ class CheckReportMicrosoft365(Check_Report):
|
||||
resource_id: str,
|
||||
resource_location: str = "global",
|
||||
) -> None:
|
||||
"""Initialize the Microsoft365 Check's finding information.
|
||||
"""Initialize the M365 Check's finding information.
|
||||
|
||||
Args:
|
||||
metadata: The metadata of the check.
|
||||
|
||||
@@ -26,15 +26,15 @@ class ProwlerArgumentParser:
|
||||
self.parser = argparse.ArgumentParser(
|
||||
prog="prowler",
|
||||
formatter_class=RawTextHelpFormatter,
|
||||
usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,microsoft365,nhn,dashboard} ...",
|
||||
usage="prowler [-h] [--version] {aws,azure,gcp,kubernetes,m365,nhn,dashboard} ...",
|
||||
epilog="""
|
||||
Available Cloud Providers:
|
||||
{aws,azure,gcp,kubernetes,microsoft365,nhn}
|
||||
{aws,azure,gcp,kubernetes,m365,nhn}
|
||||
aws AWS Provider
|
||||
azure Azure Provider
|
||||
gcp GCP Provider
|
||||
kubernetes Kubernetes Provider
|
||||
microsoft365 Microsoft 365 Provider
|
||||
m365 Microsoft 365 Provider
|
||||
nhn NHN Provider (Unofficial)
|
||||
|
||||
Available components:
|
||||
@@ -104,6 +104,11 @@ Detailed documentation at https://docs.prowler.com
|
||||
if "-" in sys.argv[1]:
|
||||
sys.argv = self.__set_default_provider__(sys.argv)
|
||||
|
||||
# Provider aliases mapping
|
||||
# Microsoft 365
|
||||
elif sys.argv[1] == "microsoft365":
|
||||
sys.argv[1] = "m365"
|
||||
|
||||
# Parse arguments
|
||||
args = self.parser.parse_args()
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from prowler.lib.check.compliance_models import Compliance
|
||||
from prowler.lib.outputs.compliance.cis.models import Microsoft365CISModel
|
||||
from prowler.lib.outputs.compliance.cis.models import M365CISModel
|
||||
from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput
|
||||
from prowler.lib.outputs.finding import Finding
|
||||
|
||||
|
||||
class Microsoft365CIS(ComplianceOutput):
|
||||
class M365CIS(ComplianceOutput):
|
||||
"""
|
||||
This class represents the Azure CIS compliance output.
|
||||
|
||||
@@ -39,7 +39,7 @@ class Microsoft365CIS(ComplianceOutput):
|
||||
for requirement in compliance.Requirements:
|
||||
if requirement.Id in finding_requirements:
|
||||
for attribute in requirement.Attributes:
|
||||
compliance_row = Microsoft365CISModel(
|
||||
compliance_row = M365CISModel(
|
||||
Provider=finding.provider,
|
||||
Description=compliance.Description,
|
||||
TenantId=finding.account_uid,
|
||||
@@ -70,7 +70,7 @@ class Microsoft365CIS(ComplianceOutput):
|
||||
for requirement in compliance.Requirements:
|
||||
if not requirement.Checks:
|
||||
for attribute in requirement.Attributes:
|
||||
compliance_row = Microsoft365CISModel(
|
||||
compliance_row = M365CISModel(
|
||||
Provider=compliance.Provider.lower(),
|
||||
Description=compliance.Description,
|
||||
TenantId=finding.account_uid,
|
||||
@@ -69,9 +69,9 @@ class AzureCISModel(BaseModel):
|
||||
Muted: bool
|
||||
|
||||
|
||||
class Microsoft365CISModel(BaseModel):
|
||||
class M365CISModel(BaseModel):
|
||||
"""
|
||||
Microsoft365CISModel generates a finding's output in Microsoft365 CIS Compliance format.
|
||||
M365CISModel generates a finding's output in Microsoft 365 CIS Compliance format.
|
||||
"""
|
||||
|
||||
Provider: str
|
||||
|
||||
@@ -245,7 +245,7 @@ class Finding(BaseModel):
|
||||
)
|
||||
output_data["region"] = f"namespace: {check_output.namespace}"
|
||||
|
||||
elif provider.type == "microsoft365":
|
||||
elif provider.type == "m365":
|
||||
output_data["auth_method"] = (
|
||||
f"{provider.identity.identity_type}: {provider.identity.identity_id}"
|
||||
)
|
||||
|
||||
@@ -546,9 +546,9 @@ class HTML(Output):
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def get_microsoft365_assessment_summary(provider: Provider) -> str:
|
||||
def get_m365_assessment_summary(provider: Provider) -> str:
|
||||
"""
|
||||
get_microsoft365_assessment_summary gets the HTML assessment summary for the provider
|
||||
get_m365_assessment_summary gets the HTML assessment summary for the provider
|
||||
|
||||
Args:
|
||||
provider (Provider): the provider object
|
||||
@@ -561,11 +561,11 @@ class HTML(Output):
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Microsoft365 Assessment Summary
|
||||
M365 Assessment Summary
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Microsoft365 Tenant Domain:</b> {provider.identity.tenant_domain}
|
||||
<b>M365 Tenant Domain:</b> {provider.identity.tenant_domain}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -573,14 +573,14 @@ class HTML(Output):
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Microsoft365 Credentials
|
||||
M365 Credentials
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Microsoft365 Identity Type:</b> {provider.identity.identity_type}
|
||||
<b>M365 Identity Type:</b> {provider.identity.identity_type}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Microsoft365 Identity ID:</b> {provider.identity.identity_id}
|
||||
<b>M365 Identity ID:</b> {provider.identity.identity_id}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@ def stdout_report(finding, color, verbose, status, fix):
|
||||
details = finding.location.lower()
|
||||
if finding.check_metadata.Provider == "kubernetes":
|
||||
details = finding.namespace.lower()
|
||||
if finding.check_metadata.Provider == "microsoft365":
|
||||
if finding.check_metadata.Provider == "m365":
|
||||
details = finding.location
|
||||
if finding.check_metadata.Provider == "nhn":
|
||||
details = finding.location
|
||||
|
||||
@@ -40,7 +40,7 @@ def display_summary_table(
|
||||
elif provider.type == "kubernetes":
|
||||
entity_type = "Context"
|
||||
audited_entities = provider.identity.context
|
||||
elif provider.type == "microsoft365":
|
||||
elif provider.type == "m365":
|
||||
entity_type = "Tenant Domain"
|
||||
audited_entities = provider.identity.tenant_domain
|
||||
elif provider.type == "nhn":
|
||||
|
||||
222
prowler/lib/powershell/powershell.py
Normal file
222
prowler/lib/powershell/powershell.py
Normal file
@@ -0,0 +1,222 @@
|
||||
import json
|
||||
import platform
|
||||
import queue
|
||||
import re
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
|
||||
class PowerShellSession:
|
||||
"""
|
||||
Base class for managing PowerShell sessions.
|
||||
|
||||
This class provides the core functionality for interacting with PowerShell,
|
||||
including command execution, output handling, and session management.
|
||||
It serves as a foundation for more specific PowerShell implementations.
|
||||
|
||||
Features:
|
||||
- Maintains a persistent PowerShell session
|
||||
- Handles command execution and output parsing
|
||||
- Provides secure input sanitization
|
||||
- Manages ANSI escape sequence removal
|
||||
- Supports JSON output parsing
|
||||
- Implements timeout handling for long-running commands
|
||||
|
||||
Attributes:
|
||||
END (str): Marker string used to signal the end of PowerShell command output.
|
||||
process (subprocess.Popen): The underlying PowerShell subprocess with open stdin, stdout, and stderr streams.
|
||||
|
||||
Note:
|
||||
This is an abstract base class that should be extended by specific implementations
|
||||
for different PowerShell use cases.
|
||||
"""
|
||||
|
||||
END = "<END>"
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize a persistent PowerShell session.
|
||||
|
||||
Creates a subprocess running PowerShell with pipes for stdin, stdout, and stderr.
|
||||
The session is configured to run in interactive mode with no exit.
|
||||
|
||||
Note:
|
||||
This is a base implementation that should be extended by subclasses
|
||||
to add specific initialization logic (e.g., credential setup).
|
||||
"""
|
||||
# Determine the appropriate PowerShell command based on the OS
|
||||
if platform.system() == "Windows":
|
||||
powershell_cmd = "powershell"
|
||||
else:
|
||||
powershell_cmd = "pwsh"
|
||||
|
||||
self.process = subprocess.Popen(
|
||||
[powershell_cmd, "-NoExit", "-Command", "-"],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
)
|
||||
|
||||
def sanitize(self, credential: str) -> str:
|
||||
"""
|
||||
Sanitize input to prevent command injection.
|
||||
|
||||
Filters the input string to allow only letters, numbers, @, periods, underscores,
|
||||
plus signs, and hyphens. This is a security measure to prevent command injection
|
||||
attacks through credential input.
|
||||
|
||||
Args:
|
||||
credential (str): The string to sanitize.
|
||||
|
||||
Returns:
|
||||
str: The sanitized string containing only allowed characters.
|
||||
|
||||
Example:
|
||||
>>> sanitize("user@domain.com!@#$")
|
||||
"user@domain.com"
|
||||
"""
|
||||
return re.sub(r"[^a-zA-Z0-9@._+\-]", "", credential)
|
||||
|
||||
def remove_ansi(self, text: str) -> str:
|
||||
"""
|
||||
Remove ANSI color codes and other escape sequences from PowerShell output.
|
||||
|
||||
PowerShell often includes ANSI escape sequences in its output for terminal
|
||||
coloring and formatting. This method strips these sequences to produce clean,
|
||||
parseable text that can be processed programmatically.
|
||||
|
||||
Args:
|
||||
text (str): Raw text containing ANSI escape sequences from PowerShell output.
|
||||
|
||||
Returns:
|
||||
str: Clean text with all ANSI escape sequences removed, suitable for parsing.
|
||||
|
||||
Example:
|
||||
>>> remove_ansi("\x1b[32mSuccess\x1b[0m")
|
||||
"Success"
|
||||
"""
|
||||
ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
|
||||
return ansi_escape.sub("", text)
|
||||
|
||||
def execute(self, command: str) -> dict:
|
||||
"""
|
||||
Send a command to PowerShell and retrieve its output.
|
||||
|
||||
Executes the given command in the PowerShell session, adds an END marker,
|
||||
and parses the output as JSON if possible. The command is executed
|
||||
asynchronously with a timeout mechanism.
|
||||
|
||||
Args:
|
||||
command (str): PowerShell command to execute.
|
||||
|
||||
Returns:
|
||||
dict: JSON-parsed output if available, otherwise an empty dictionary.
|
||||
|
||||
Example:
|
||||
>>> execute("Get-Process | ConvertTo-Json")
|
||||
{"Name": "process1", "Id": 1234}
|
||||
"""
|
||||
self.process.stdin.write(f"{command}\n")
|
||||
self.process.stdin.write(f"Write-Output '{self.END}'\n")
|
||||
return self.json_parse_output(self.read_output())
|
||||
|
||||
def read_output(self, timeout: int = 10, default: str = "") -> str:
|
||||
"""
|
||||
Read output from a process with timeout functionality.
|
||||
|
||||
This method reads lines from process stdout until it encounters the END marker
|
||||
or the stream ends. If reading takes longer than the timeout period, the method
|
||||
returns a default value while allowing the reading to continue in the background.
|
||||
|
||||
Args:
|
||||
timeout (int, optional): Maximum time in seconds to wait for output.
|
||||
Defaults to 10.
|
||||
default (str, optional): Value to return if timeout occurs.
|
||||
Defaults to empty string.
|
||||
|
||||
Returns:
|
||||
str: Concatenated output lines or default value if timeout occurs.
|
||||
|
||||
Note:
|
||||
This method uses a daemon thread to read the output asynchronously,
|
||||
ensuring that the main thread is not blocked.
|
||||
"""
|
||||
output_lines = []
|
||||
result_queue = queue.Queue()
|
||||
|
||||
def reader_thread():
|
||||
try:
|
||||
while True:
|
||||
line = self.remove_ansi(self.process.stdout.readline().strip())
|
||||
if line == self.END:
|
||||
break
|
||||
output_lines.append(line)
|
||||
result_queue.put("\n".join(output_lines))
|
||||
except Exception as e:
|
||||
result_queue.put(str(e))
|
||||
|
||||
thread = threading.Thread(target=reader_thread)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
try:
|
||||
return result_queue.get(timeout=timeout)
|
||||
except queue.Empty:
|
||||
return default
|
||||
|
||||
def json_parse_output(self, output: str) -> dict:
|
||||
"""
|
||||
Parse command execution output to JSON format.
|
||||
|
||||
Searches for a JSON object in the output string and parses it.
|
||||
The method looks for both object and array JSON structures.
|
||||
|
||||
Args:
|
||||
output (str): The string output from a PowerShell command.
|
||||
|
||||
Returns:
|
||||
dict: Parsed JSON object if found, otherwise an empty dictionary.
|
||||
|
||||
Example:
|
||||
>>> json_parse_output('Some text {"key": "value"} more text')
|
||||
{"key": "value"}
|
||||
"""
|
||||
json_match = re.search(r"(\[.*\]|\{.*\})", output, re.DOTALL)
|
||||
if json_match:
|
||||
return json.loads(json_match.group(1))
|
||||
return {}
|
||||
|
||||
def close(self) -> None:
|
||||
"""
|
||||
Terminate the PowerShell session.
|
||||
|
||||
Sends an exit command to PowerShell and terminates the subprocess.
|
||||
This method should be called when the session is no longer needed
|
||||
to ensure proper cleanup of resources.
|
||||
|
||||
Note:
|
||||
It's important to call this method when done with the session
|
||||
to prevent resource leaks.
|
||||
"""
|
||||
if self.process:
|
||||
try:
|
||||
# Send exit command
|
||||
self.process.stdin.write("exit\n")
|
||||
self.process.stdin.flush()
|
||||
|
||||
# Terminate the process
|
||||
self.process.terminate()
|
||||
|
||||
# Wait for the process to finish
|
||||
self.process.wait(timeout=5)
|
||||
except Exception:
|
||||
# If process is still running, force kill it
|
||||
self.process.kill()
|
||||
finally:
|
||||
# Close all pipes
|
||||
self.process.stdin.close()
|
||||
self.process.stdout.close()
|
||||
self.process.stderr.close()
|
||||
self.process = None
|
||||
@@ -211,12 +211,13 @@ class Provider(ABC):
|
||||
mutelist_path=arguments.mutelist_file,
|
||||
fixer_config=fixer_config,
|
||||
)
|
||||
elif "microsoft365" in provider_class_name.lower():
|
||||
elif "m365" in provider_class_name.lower():
|
||||
provider_class(
|
||||
region=arguments.region,
|
||||
config_path=arguments.config_file,
|
||||
mutelist_path=arguments.mutelist_file,
|
||||
sp_env_auth=arguments.sp_env_auth,
|
||||
env_auth=arguments.env_auth,
|
||||
az_cli_auth=arguments.az_cli_auth,
|
||||
browser_auth=arguments.browser_auth,
|
||||
tenant_id=arguments.tenant_id,
|
||||
|
||||
@@ -1,103 +1,111 @@
|
||||
from prowler.exceptions.exceptions import ProwlerException
|
||||
|
||||
|
||||
# Exceptions codes from 5000 to 5999 are reserved for Microsoft365 exceptions
|
||||
class Microsoft365BaseException(ProwlerException):
|
||||
"""Base class for Microsoft365 Errors."""
|
||||
# Exceptions codes from 5000 to 5999 are reserved for M365 exceptions
|
||||
class M365BaseException(ProwlerException):
|
||||
"""Base class for M365 Errors."""
|
||||
|
||||
MICROSOFT365_ERROR_CODES = {
|
||||
(6000, "Microsoft365EnvironmentVariableError"): {
|
||||
"message": "Microsoft365 environment variable error",
|
||||
"remediation": "Check the Microsoft365 environment variables and ensure they are properly set.",
|
||||
(6000, "M365EnvironmentVariableError"): {
|
||||
"message": "Microsoft 365 environment variable error",
|
||||
"remediation": "Check the Microsoft 365 environment variables and ensure they are properly set.",
|
||||
},
|
||||
(6001, "Microsoft365ArgumentTypeValidationError"): {
|
||||
"message": "Microsoft365 argument type validation error",
|
||||
"remediation": "Check the provided argument types specific to Microsoft365 and ensure they meet the required format.",
|
||||
(6001, "M365ArgumentTypeValidationError"): {
|
||||
"message": "Microsoft 365 argument type validation error",
|
||||
"remediation": "Check the provided argument types specific to Microsoft 365 and ensure they meet the required format.",
|
||||
},
|
||||
(6002, "Microsoft365SetUpRegionConfigError"): {
|
||||
"message": "Microsoft365 region configuration setup error",
|
||||
"remediation": "Check the Microsoft365 region configuration and ensure it is properly set up.",
|
||||
(6002, "M365SetUpRegionConfigError"): {
|
||||
"message": "Microsoft 365 region configuration setup error",
|
||||
"remediation": "Check the Microsoft 365 region configuration and ensure it is properly set up.",
|
||||
},
|
||||
(6003, "Microsoft365HTTPResponseError"): {
|
||||
"message": "Error in HTTP response from Microsoft365",
|
||||
(6003, "M365HTTPResponseError"): {
|
||||
"message": "Error in HTTP response from Microsoft 365",
|
||||
"remediation": "",
|
||||
},
|
||||
(6004, "Microsoft365CredentialsUnavailableError"): {
|
||||
"message": "Error trying to configure Microsoft365 credentials because they are unavailable",
|
||||
"remediation": "Check the dictionary and ensure it is properly set up for Microsoft365 credentials. TENANT_ID, CLIENT_ID and CLIENT_SECRET are required.",
|
||||
(6004, "M365CredentialsUnavailableError"): {
|
||||
"message": "Error trying to configure Microsoft 365 credentials because they are unavailable",
|
||||
"remediation": "Check the dictionary and ensure it is properly set up for Microsoft 365 credentials. TENANT_ID, CLIENT_ID and CLIENT_SECRET are required.",
|
||||
},
|
||||
(6005, "Microsoft365GetTokenIdentityError"): {
|
||||
"message": "Error trying to get token from Microsoft365 Identity",
|
||||
"remediation": "Check the Microsoft365 Identity and ensure it is properly set up.",
|
||||
(6005, "M365GetTokenIdentityError"): {
|
||||
"message": "Error trying to get token from Microsoft 365 Identity",
|
||||
"remediation": "Check the Microsoft 365 Identity and ensure it is properly set up.",
|
||||
},
|
||||
(6006, "Microsoft365ClientAuthenticationError"): {
|
||||
(6006, "M365ClientAuthenticationError"): {
|
||||
"message": "Error in client authentication",
|
||||
"remediation": "Check the client authentication and ensure it is properly set up.",
|
||||
},
|
||||
(6007, "Microsoft365NotValidTenantIdError"): {
|
||||
(6007, "M365NotValidTenantIdError"): {
|
||||
"message": "The provided tenant ID is not valid",
|
||||
"remediation": "Check the tenant ID and ensure it is a valid ID.",
|
||||
},
|
||||
(6008, "Microsoft365NotValidClientIdError"): {
|
||||
(6008, "M365NotValidClientIdError"): {
|
||||
"message": "The provided client ID is not valid",
|
||||
"remediation": "Check the client ID and ensure it is a valid ID.",
|
||||
},
|
||||
(6009, "Microsoft365NotValidClientSecretError"): {
|
||||
(6009, "M365NotValidClientSecretError"): {
|
||||
"message": "The provided client secret is not valid",
|
||||
"remediation": "Check the client secret and ensure it is a valid secret.",
|
||||
},
|
||||
(6010, "Microsoft365ConfigCredentialsError"): {
|
||||
"message": "Error in configuration of Microsoft365 credentials",
|
||||
"remediation": "Check the configuration of Microsoft365 credentials and ensure it is properly set up.",
|
||||
(6010, "M365ConfigCredentialsError"): {
|
||||
"message": "Error in configuration of Microsoft 365 credentials",
|
||||
"remediation": "Check the configuration of Microsoft 365 credentials and ensure it is properly set up.",
|
||||
},
|
||||
(6011, "Microsoft365ClientIdAndClientSecretNotBelongingToTenantIdError"): {
|
||||
(6011, "M365ClientIdAndClientSecretNotBelongingToTenantIdError"): {
|
||||
"message": "The provided client ID and client secret do not belong to the provided tenant ID",
|
||||
"remediation": "Check the client ID and client secret and ensure they belong to the provided tenant ID.",
|
||||
},
|
||||
(6012, "Microsoft365TenantIdAndClientSecretNotBelongingToClientIdError"): {
|
||||
(6012, "M365TenantIdAndClientSecretNotBelongingToClientIdError"): {
|
||||
"message": "The provided tenant ID and client secret do not belong to the provided client ID",
|
||||
"remediation": "Check the tenant ID and client secret and ensure they belong to the provided client ID.",
|
||||
},
|
||||
(6013, "Microsoft365TenantIdAndClientIdNotBelongingToClientSecretError"): {
|
||||
(6013, "M365TenantIdAndClientIdNotBelongingToClientSecretError"): {
|
||||
"message": "The provided tenant ID and client ID do not belong to the provided client secret",
|
||||
"remediation": "Check the tenant ID and client ID and ensure they belong to the provided client secret.",
|
||||
},
|
||||
(6014, "Microsoft365InvalidProviderIdError"): {
|
||||
(6014, "M365InvalidProviderIdError"): {
|
||||
"message": "The provided provider_id does not match with the available subscriptions",
|
||||
"remediation": "Check the provider_id and ensure it is a valid subscription for the given credentials.",
|
||||
},
|
||||
(6015, "Microsoft365NoAuthenticationMethodError"): {
|
||||
"message": "No Microsoft365 authentication method found",
|
||||
"remediation": "Check that any authentication method is properly set up for Microsoft365.",
|
||||
(6015, "M365NoAuthenticationMethodError"): {
|
||||
"message": "No Microsoft 365 authentication method found",
|
||||
"remediation": "Check that any authentication method is properly set up for Microsoft 365.",
|
||||
},
|
||||
(6016, "Microsoft365SetUpSessionError"): {
|
||||
(6016, "M365SetUpSessionError"): {
|
||||
"message": "Error setting up session",
|
||||
"remediation": "Check the session setup and ensure it is properly set up.",
|
||||
},
|
||||
(6017, "Microsoft365DefaultAzureCredentialError"): {
|
||||
(6017, "M365DefaultAzureCredentialError"): {
|
||||
"message": "Error with DefaultAzureCredential",
|
||||
"remediation": "Ensure DefaultAzureCredential is correctly configured.",
|
||||
},
|
||||
(6018, "Microsoft365InteractiveBrowserCredentialError"): {
|
||||
(6018, "M365InteractiveBrowserCredentialError"): {
|
||||
"message": "Error with InteractiveBrowserCredential",
|
||||
"remediation": "Ensure InteractiveBrowserCredential is correctly configured.",
|
||||
},
|
||||
(6019, "Microsoft365BrowserAuthNoTenantIDError"): {
|
||||
"message": "Microsoft365 Tenant ID (--tenant-id) is required for browser authentication mode",
|
||||
"remediation": "Check the Microsoft365 Tenant ID and ensure it is properly set up.",
|
||||
(6019, "M365BrowserAuthNoTenantIDError"): {
|
||||
"message": "Microsoft 365 Tenant ID (--tenant-id) is required for browser authentication mode",
|
||||
"remediation": "Check the Microsoft 365 Tenant ID and ensure it is properly set up.",
|
||||
},
|
||||
(6020, "Microsoft365BrowserAuthNoFlagError"): {
|
||||
"message": "Microsoft365 tenant ID error: browser authentication flag (--browser-auth) not found",
|
||||
(6020, "M365BrowserAuthNoFlagError"): {
|
||||
"message": "Microsoft 365 tenant ID error: browser authentication flag (--browser-auth) not found",
|
||||
"remediation": "To use browser authentication, ensure the tenant ID is properly set.",
|
||||
},
|
||||
(6021, "Microsoft365NotTenantIdButClientIdAndClientSecretError"): {
|
||||
"message": "Tenant Id is required for Microsoft365 static credentials. Make sure you are using the correct credentials.",
|
||||
"remediation": "Check the Microsoft365 Tenant ID and ensure it is properly set up.",
|
||||
(6021, "M365NotTenantIdButClientIdAndClientSecretError"): {
|
||||
"message": "Tenant Id is required for Microsoft 365 static credentials. Make sure you are using the correct credentials.",
|
||||
"remediation": "Check the Microsoft 365 Tenant ID and ensure it is properly set up.",
|
||||
},
|
||||
(6022, "M365MissingEnvironmentUserCredentialsError"): {
|
||||
"message": "User and Password environment variables are needed to use Credentials authentication method.",
|
||||
"remediation": "Ensure your environment variables are properly set up.",
|
||||
},
|
||||
(6023, "M365EnvironmentUserCredentialsError"): {
|
||||
"message": "User or Password environment variables are not correct.",
|
||||
"remediation": "Ensure you are using the right credentials.",
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, code, file=None, original_exception=None, message=None):
|
||||
provider = "Microsoft365"
|
||||
provider = "M365"
|
||||
error_info = self.MICROSOFT365_ERROR_CODES.get((code, self.__class__.__name__))
|
||||
if message:
|
||||
error_info["message"] = message
|
||||
@@ -110,170 +118,176 @@ class Microsoft365BaseException(ProwlerException):
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365CredentialsError(Microsoft365BaseException):
|
||||
"""Base class for Microsoft365 credentials errors."""
|
||||
class M365CredentialsError(M365BaseException):
|
||||
"""Base class for M365 credentials errors."""
|
||||
|
||||
def __init__(self, code, file=None, original_exception=None, message=None):
|
||||
super().__init__(code, file, original_exception, message)
|
||||
|
||||
|
||||
class Microsoft365EnvironmentVariableError(Microsoft365CredentialsError):
|
||||
class M365EnvironmentVariableError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6000, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365ArgumentTypeValidationError(Microsoft365BaseException):
|
||||
class M365ArgumentTypeValidationError(M365BaseException):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6001, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365SetUpRegionConfigError(Microsoft365BaseException):
|
||||
class M365SetUpRegionConfigError(M365BaseException):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6002, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365HTTPResponseError(Microsoft365BaseException):
|
||||
class M365HTTPResponseError(M365BaseException):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6003, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365CredentialsUnavailableError(Microsoft365CredentialsError):
|
||||
class M365CredentialsUnavailableError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6004, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365GetTokenIdentityError(Microsoft365BaseException):
|
||||
class M365GetTokenIdentityError(M365BaseException):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6005, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365ClientAuthenticationError(Microsoft365CredentialsError):
|
||||
class M365ClientAuthenticationError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6006, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365NotValidTenantIdError(Microsoft365CredentialsError):
|
||||
class M365NotValidTenantIdError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6007, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365NotValidClientIdError(Microsoft365CredentialsError):
|
||||
class M365NotValidClientIdError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6008, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365NotValidClientSecretError(Microsoft365CredentialsError):
|
||||
class M365NotValidClientSecretError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6009, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365ConfigCredentialsError(Microsoft365CredentialsError):
|
||||
class M365ConfigCredentialsError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6010, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365ClientIdAndClientSecretNotBelongingToTenantIdError(
|
||||
Microsoft365CredentialsError
|
||||
):
|
||||
class M365ClientIdAndClientSecretNotBelongingToTenantIdError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6011, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365TenantIdAndClientSecretNotBelongingToClientIdError(
|
||||
Microsoft365CredentialsError
|
||||
):
|
||||
class M365TenantIdAndClientSecretNotBelongingToClientIdError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6012, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365TenantIdAndClientIdNotBelongingToClientSecretError(
|
||||
Microsoft365CredentialsError
|
||||
):
|
||||
class M365TenantIdAndClientIdNotBelongingToClientSecretError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6013, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365InvalidProviderIdError(Microsoft365BaseException):
|
||||
class M365InvalidProviderIdError(M365BaseException):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6014, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365NoAuthenticationMethodError(Microsoft365CredentialsError):
|
||||
class M365NoAuthenticationMethodError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6015, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365SetUpSessionError(Microsoft365CredentialsError):
|
||||
class M365SetUpSessionError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6016, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365DefaultAzureCredentialError(Microsoft365CredentialsError):
|
||||
class M365DefaultAzureCredentialError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6017, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365InteractiveBrowserCredentialError(Microsoft365CredentialsError):
|
||||
class M365InteractiveBrowserCredentialError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6018, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365BrowserAuthNoTenantIDError(Microsoft365CredentialsError):
|
||||
class M365BrowserAuthNoTenantIDError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6019, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365BrowserAuthNoFlagError(Microsoft365CredentialsError):
|
||||
class M365BrowserAuthNoFlagError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6020, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class Microsoft365NotTenantIdButClientIdAndClientSecretError(
|
||||
Microsoft365CredentialsError
|
||||
):
|
||||
class M365NotTenantIdButClientIdAndClientSecretError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6021, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class M365MissingEnvironmentUserCredentialsError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6022, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
|
||||
|
||||
class M365EnvironmentUserCredentialsError(M365CredentialsError):
|
||||
def __init__(self, file=None, original_exception=None, message=None):
|
||||
super().__init__(
|
||||
6023, file=file, original_exception=original_exception, message=message
|
||||
)
|
||||
61
prowler/providers/m365/lib/arguments/arguments.py
Normal file
61
prowler/providers/m365/lib/arguments/arguments.py
Normal file
@@ -0,0 +1,61 @@
|
||||
def init_parser(self):
|
||||
"""Init the M365 Provider CLI parser"""
|
||||
m365_parser = self.subparsers.add_parser(
|
||||
"m365",
|
||||
parents=[self.common_providers_parser],
|
||||
help="M365 Provider",
|
||||
)
|
||||
# Authentication Modes
|
||||
m365_auth_subparser = m365_parser.add_argument_group("Authentication Modes")
|
||||
m365_auth_modes_group = m365_auth_subparser.add_mutually_exclusive_group()
|
||||
m365_auth_modes_group.add_argument(
|
||||
"--az-cli-auth",
|
||||
action="store_true",
|
||||
help="Use Azure CLI authentication to log in against Microsoft 365",
|
||||
)
|
||||
m365_auth_modes_group.add_argument(
|
||||
"--env-auth",
|
||||
action="store_true",
|
||||
help="Use User and Password environment variables authentication to log in against Microsoft 365",
|
||||
)
|
||||
m365_auth_modes_group.add_argument(
|
||||
"--sp-env-auth",
|
||||
action="store_true",
|
||||
help="Use Azure Service Principal environment variables authentication to log in against Microsoft 365",
|
||||
)
|
||||
m365_auth_modes_group.add_argument(
|
||||
"--browser-auth",
|
||||
action="store_true",
|
||||
help="Use Azure interactive browser authentication to log in against Microsoft 365",
|
||||
)
|
||||
m365_parser.add_argument(
|
||||
"--tenant-id",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="Microsoft 365 Tenant ID to be used with --browser-auth option",
|
||||
)
|
||||
m365_parser.add_argument(
|
||||
"--user",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="Microsoft 365 user email",
|
||||
)
|
||||
m365_parser.add_argument(
|
||||
"--encypted-password",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="Microsoft 365 encrypted password",
|
||||
)
|
||||
# Regions
|
||||
m365_regions_subparser = m365_parser.add_argument_group("Regions")
|
||||
m365_regions_subparser.add_argument(
|
||||
"--region",
|
||||
nargs="?",
|
||||
default="M365Global",
|
||||
choices=[
|
||||
"M365Global",
|
||||
"M365GlobalChina",
|
||||
"M365USGovernment",
|
||||
],
|
||||
help="Microsoft 365 region to be used, default is M365Global",
|
||||
)
|
||||
@@ -1,12 +1,12 @@
|
||||
from prowler.lib.check.models import CheckReportMicrosoft365
|
||||
from prowler.lib.check.models import CheckReportM365
|
||||
from prowler.lib.mutelist.mutelist import Mutelist
|
||||
from prowler.lib.outputs.utils import unroll_dict, unroll_tags
|
||||
|
||||
|
||||
class Microsoft365Mutelist(Mutelist):
|
||||
class M365Mutelist(Mutelist):
|
||||
def is_finding_muted(
|
||||
self,
|
||||
finding: CheckReportMicrosoft365,
|
||||
finding: CheckReportM365,
|
||||
) -> bool:
|
||||
return self.is_muted(
|
||||
finding.tenant_id,
|
||||
171
prowler/providers/m365/lib/powershell/m365_powershell.py
Normal file
171
prowler/providers/m365/lib/powershell/m365_powershell.py
Normal file
@@ -0,0 +1,171 @@
|
||||
import msal
|
||||
|
||||
from prowler.lib.powershell.powershell import PowerShellSession
|
||||
from prowler.providers.m365.models import M365Credentials
|
||||
|
||||
|
||||
class M365PowerShell(PowerShellSession):
|
||||
"""
|
||||
Microsoft 365 specific PowerShell session management implementation.
|
||||
|
||||
This class extends the base PowerShellSession to provide Microsoft 365 specific
|
||||
functionality, including authentication, Teams management, and Exchange Online
|
||||
operations.
|
||||
|
||||
Features:
|
||||
- Microsoft 365 credential management
|
||||
- Teams client configuration
|
||||
- Exchange Online connectivity
|
||||
- Audit log configuration
|
||||
- Secure credential handling
|
||||
|
||||
Attributes:
|
||||
credentials (M365Credentials): The Microsoft 365 credentials used for authentication.
|
||||
|
||||
Note:
|
||||
This class requires the Microsoft Teams and Exchange Online PowerShell modules
|
||||
to be installed and available in the PowerShell environment.
|
||||
"""
|
||||
|
||||
def __init__(self, credentials: M365Credentials):
|
||||
"""
|
||||
Initialize a Microsoft 365 PowerShell session.
|
||||
|
||||
Sets up the PowerShell session and initializes the provided credentials
|
||||
for Microsoft 365 authentication.
|
||||
|
||||
Args:
|
||||
credentials (M365Credentials): The Microsoft 365 credentials to use
|
||||
for authentication.
|
||||
"""
|
||||
super().__init__()
|
||||
self.init_credential(credentials)
|
||||
|
||||
def init_credential(self, credentials: M365Credentials) -> None:
|
||||
"""
|
||||
Initialize PowerShell credential object for Microsoft 365 authentication.
|
||||
|
||||
Sanitizes the username and password, then creates a PSCredential object
|
||||
in the PowerShell session for use with Microsoft 365 cmdlets.
|
||||
|
||||
Args:
|
||||
credentials (M365Credentials): The credentials object containing
|
||||
username and password.
|
||||
|
||||
Note:
|
||||
The credentials are sanitized to prevent command injection and
|
||||
stored securely in the PowerShell session.
|
||||
"""
|
||||
# Sanitize user and password
|
||||
user = self.sanitize(credentials.user)
|
||||
passwd = self.sanitize(credentials.passwd)
|
||||
|
||||
# Securely convert encrypted password to SecureString
|
||||
self.execute(f'$user = "{user}"')
|
||||
self.execute(f'$secureString = "{passwd}" | ConvertTo-SecureString')
|
||||
self.execute(
|
||||
"$credential = New-Object System.Management.Automation.PSCredential ($user, $secureString)"
|
||||
)
|
||||
|
||||
def test_credentials(self, credentials: M365Credentials) -> bool:
|
||||
"""
|
||||
Test Microsoft 365 credentials by attempting to authenticate against Entra ID.
|
||||
|
||||
Args:
|
||||
credentials (M365Credentials): The credentials object containing
|
||||
username and password to test.
|
||||
|
||||
Returns:
|
||||
bool: True if credentials are valid and authentication succeeds, False otherwise.
|
||||
"""
|
||||
self.execute(
|
||||
f'$securePassword = "{credentials.passwd}" | ConvertTo-SecureString\n'
|
||||
)
|
||||
self.execute(
|
||||
f'$credential = New-Object System.Management.Automation.PSCredential("{credentials.user}", $securePassword)\n'
|
||||
)
|
||||
self.process.stdin.write(
|
||||
'Write-Output "$($credential.GetNetworkCredential().Password)"\n'
|
||||
)
|
||||
self.process.stdin.write(f"Write-Output '{self.END}'\n")
|
||||
decrypted_password = self.read_output()
|
||||
|
||||
app = msal.ConfidentialClientApplication(
|
||||
client_id=credentials.client_id,
|
||||
client_credential=credentials.client_secret,
|
||||
authority=f"https://login.microsoftonline.com/{credentials.tenant_id}",
|
||||
)
|
||||
|
||||
result = app.acquire_token_by_username_password(
|
||||
username=credentials.user,
|
||||
password=decrypted_password, # Needs to be in plain text
|
||||
scopes=["https://graph.microsoft.com/.default"],
|
||||
)
|
||||
|
||||
return "access_token" in result
|
||||
|
||||
def connect_microsoft_teams(self) -> dict:
|
||||
"""
|
||||
Connect to Microsoft Teams Module PowerShell Module.
|
||||
|
||||
Establishes a connection to Microsoft Teams using the initialized credentials.
|
||||
|
||||
Returns:
|
||||
dict: Connection status information in JSON format.
|
||||
|
||||
Note:
|
||||
This method requires the Microsoft Teams PowerShell module to be installed.
|
||||
"""
|
||||
return self.execute("Connect-MicrosoftTeams -Credential $credential")
|
||||
|
||||
def get_teams_settings(self) -> dict:
|
||||
"""
|
||||
Get Teams Client Settings.
|
||||
|
||||
Retrieves the current Microsoft Teams client configuration settings.
|
||||
|
||||
Returns:
|
||||
dict: Teams client configuration settings in JSON format.
|
||||
|
||||
Example:
|
||||
>>> get_teams_settings()
|
||||
{
|
||||
"AllowBox": true,
|
||||
"AllowDropBox": true,
|
||||
"AllowGoogleDrive": true
|
||||
}
|
||||
"""
|
||||
return self.execute("Get-CsTeamsClientConfiguration | ConvertTo-Json")
|
||||
|
||||
def connect_exchange_online(self) -> dict:
|
||||
"""
|
||||
Connect to Exchange Online PowerShell Module.
|
||||
|
||||
Establishes a connection to Exchange Online using the initialized credentials.
|
||||
|
||||
Returns:
|
||||
dict: Connection status information in JSON format.
|
||||
|
||||
Note:
|
||||
This method requires the Exchange Online PowerShell module to be installed.
|
||||
"""
|
||||
return self.execute("Connect-ExchangeOnline -Credential $credential")
|
||||
|
||||
def get_audit_log_config(self) -> dict:
|
||||
"""
|
||||
Get Purview Admin Audit Log Settings.
|
||||
|
||||
Retrieves the current audit log configuration settings for Microsoft Purview.
|
||||
|
||||
Returns:
|
||||
dict: Audit log configuration settings in JSON format.
|
||||
|
||||
Example:
|
||||
>>> get_audit_log_config()
|
||||
{
|
||||
"UnifiedAuditLogIngestionEnabled": true
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-AdminAuditLogConfig | Select-Object UnifiedAuditLogIngestionEnabled | ConvertTo-Json"
|
||||
)
|
||||
@@ -8,17 +8,17 @@ MICROSOFT365_GENERIC_CLOUD = "https://graph.microsoft.com"
|
||||
|
||||
def get_regions_config(region):
|
||||
allowed_regions = {
|
||||
"Microsoft365Global": {
|
||||
"M365Global": {
|
||||
"authority": None,
|
||||
"base_url": MICROSOFT365_GENERIC_CLOUD,
|
||||
"credential_scopes": [MICROSOFT365_GENERIC_CLOUD + "/.default"],
|
||||
},
|
||||
"Microsoft365China": {
|
||||
"M365China": {
|
||||
"authority": AzureAuthorityHosts.AZURE_CHINA,
|
||||
"base_url": MICROSOFT365_CHINA_CLOUD,
|
||||
"credential_scopes": [MICROSOFT365_CHINA_CLOUD + "/.default"],
|
||||
},
|
||||
"Microsoft365USGovernment": {
|
||||
"M365USGovernment": {
|
||||
"authority": AzureAuthorityHosts.AZURE_GOVERNMENT,
|
||||
"base_url": MICROSOFT365_US_GOV_CLOUD,
|
||||
"credential_scopes": [MICROSOFT365_US_GOV_CLOUD + "/.default"],
|
||||
17
prowler/providers/m365/lib/service/service.py
Normal file
17
prowler/providers/m365/lib/service/service.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from msgraph import GraphServiceClient
|
||||
|
||||
from prowler.providers.m365.lib.powershell.m365_powershell import M365PowerShell
|
||||
from prowler.providers.m365.m365_provider import M365Provider
|
||||
|
||||
|
||||
class M365Service:
|
||||
def __init__(
|
||||
self,
|
||||
provider: M365Provider,
|
||||
):
|
||||
self.client = GraphServiceClient(credentials=provider.session)
|
||||
self.audit_config = provider.audit_config
|
||||
self.fixer_config = provider.fixer_config
|
||||
|
||||
if provider.credentials:
|
||||
self.powershell = M365PowerShell(provider.credentials)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ from prowler.config.config import output_file_timestamp
|
||||
from prowler.providers.common.models import ProviderOutputOptions
|
||||
|
||||
|
||||
class Microsoft365IdentityInfo(BaseModel):
|
||||
class M365IdentityInfo(BaseModel):
|
||||
identity_id: str = ""
|
||||
identity_type: str = ""
|
||||
tenant_id: str = ""
|
||||
@@ -12,14 +12,22 @@ class Microsoft365IdentityInfo(BaseModel):
|
||||
location: str = ""
|
||||
|
||||
|
||||
class Microsoft365RegionConfig(BaseModel):
|
||||
class M365RegionConfig(BaseModel):
|
||||
name: str = ""
|
||||
authority: str = None
|
||||
base_url: str = ""
|
||||
credential_scopes: list = []
|
||||
|
||||
|
||||
class Microsoft365OutputOptions(ProviderOutputOptions):
|
||||
class M365Credentials(BaseModel):
|
||||
user: str = ""
|
||||
passwd: str = ""
|
||||
client_id: str = ""
|
||||
client_secret: str = ""
|
||||
tenant_id: str = ""
|
||||
|
||||
|
||||
class M365OutputOptions(ProviderOutputOptions):
|
||||
def __init__(self, arguments, bulk_checks_metadata, identity):
|
||||
# First call Provider_Output_Options init
|
||||
super().__init__(arguments, bulk_checks_metadata)
|
||||
@@ -1,6 +1,4 @@
|
||||
from prowler.providers.common.provider import Provider
|
||||
from prowler.providers.microsoft365.services.admincenter.admincenter_service import (
|
||||
AdminCenter,
|
||||
)
|
||||
from prowler.providers.m365.services.admincenter.admincenter_service import AdminCenter
|
||||
|
||||
admincenter_client = AdminCenter(Provider.get_global_provider())
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "admincenter_groups_not_public_visibility",
|
||||
"CheckTitle": "Ensure that only organizationally managed/approved public groups exist",
|
||||
"CheckType": [],
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.admincenter.admincenter_client import (
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.admincenter.admincenter_client import (
|
||||
admincenter_client,
|
||||
)
|
||||
|
||||
@@ -16,18 +16,18 @@ class admincenter_groups_not_public_visibility(Check):
|
||||
metadata: Metadata associated with the check (inherited from Check).
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportMicrosoft365]:
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""Execute the check for groups with public visibility.
|
||||
|
||||
This method iterates through all groups in Microsoft Admin Center and checks
|
||||
if any group has 'Public' visibility. If so, the check fails for that group.
|
||||
|
||||
Returns:
|
||||
List[CheckReportMicrosoft365]: A list containing the results of the check for each group.
|
||||
List[CheckReportM365]: A list containing the results of the check for each group.
|
||||
"""
|
||||
findings = []
|
||||
for group in admincenter_client.groups.values():
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=group,
|
||||
resource_name=group.name,
|
||||
@@ -5,12 +5,12 @@ from msgraph.generated.models.o_data_errors.o_data_error import ODataError
|
||||
from pydantic import BaseModel
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.microsoft365.lib.service.service import Microsoft365Service
|
||||
from prowler.providers.microsoft365.microsoft365_provider import Microsoft365Provider
|
||||
from prowler.providers.m365.lib.service.service import M365Service
|
||||
from prowler.providers.m365.m365_provider import M365Provider
|
||||
|
||||
|
||||
class AdminCenter(Microsoft365Service):
|
||||
def __init__(self, provider: Microsoft365Provider):
|
||||
class AdminCenter(M365Service):
|
||||
def __init__(self, provider: M365Provider):
|
||||
super().__init__(provider)
|
||||
|
||||
loop = get_event_loop()
|
||||
@@ -31,7 +31,7 @@ class AdminCenter(Microsoft365Service):
|
||||
self.domains = attributes[2]
|
||||
|
||||
async def _get_users(self):
|
||||
logger.info("Microsoft365 - Getting users...")
|
||||
logger.info("M365 - Getting users...")
|
||||
users = {}
|
||||
try:
|
||||
users_list = await self.client.users.get()
|
||||
@@ -75,7 +75,7 @@ class AdminCenter(Microsoft365Service):
|
||||
return users
|
||||
|
||||
async def _get_directory_roles(self):
|
||||
logger.info("Microsoft365 - Getting directory roles...")
|
||||
logger.info("M365 - Getting directory roles...")
|
||||
directory_roles_with_members = {}
|
||||
try:
|
||||
directory_roles_with_members.update({})
|
||||
@@ -110,7 +110,7 @@ class AdminCenter(Microsoft365Service):
|
||||
return directory_roles_with_members
|
||||
|
||||
async def _get_groups(self):
|
||||
logger.info("Microsoft365 - Getting groups...")
|
||||
logger.info("M365 - Getting groups...")
|
||||
groups = {}
|
||||
try:
|
||||
groups_list = await self.client.groups.get()
|
||||
@@ -133,7 +133,7 @@ class AdminCenter(Microsoft365Service):
|
||||
return groups
|
||||
|
||||
async def _get_domains(self):
|
||||
logger.info("Microsoft365 - Getting domains...")
|
||||
logger.info("M365 - Getting domains...")
|
||||
domains = {}
|
||||
try:
|
||||
domains_list = await self.client.domains.get()
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "admincenter_settings_password_never_expire",
|
||||
"CheckTitle": "Ensure the 'Password expiration policy' is set to 'Set passwords to never expire (recommended)'",
|
||||
"CheckType": [],
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.admincenter.admincenter_client import (
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.admincenter.admincenter_client import (
|
||||
admincenter_client,
|
||||
)
|
||||
|
||||
@@ -17,19 +17,19 @@ class admincenter_settings_password_never_expire(Check):
|
||||
metadata: Metadata associated with the check (inherited from Check).
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportMicrosoft365]:
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""Execute the check for password never expires policy.
|
||||
|
||||
This method iterates over all domains and checks if the password validity period is set
|
||||
to `2147483647`, indicating that passwords for users in the domain never expire.
|
||||
|
||||
Returns:
|
||||
List[CheckReportMicrosoft365]: A list of reports indicating whether the domain's password
|
||||
List[CheckReportM365]: A list of reports indicating whether the domain's password
|
||||
policy is set to never expire.
|
||||
"""
|
||||
findings = []
|
||||
for domain in admincenter_client.domains.values():
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
self.metadata(),
|
||||
resource=domain,
|
||||
resource_name=domain.id,
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "admincenter_users_admins_reduced_license_footprint",
|
||||
"CheckTitle": "Ensure administrative accounts use licenses with a reduced application footprint",
|
||||
"CheckType": [],
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.admincenter.admincenter_client import (
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.admincenter.admincenter_client import (
|
||||
admincenter_client,
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ class admincenter_users_admins_reduced_license_footprint(Check):
|
||||
metadata: Metadata associated with the check (inherited from Check).
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportMicrosoft365]:
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""Execute the check for users with administrative roles and their licenses.
|
||||
|
||||
This method iterates over all users and checks if those with administrative roles
|
||||
@@ -25,7 +25,7 @@ class admincenter_users_admins_reduced_license_footprint(Check):
|
||||
the check passes; otherwise, it fails.
|
||||
|
||||
Returns:
|
||||
List[CheckReportMicrosoft365]: A list containing the result of the check for each user.
|
||||
List[CheckReportM365]: A list containing the result of the check for each user.
|
||||
"""
|
||||
findings = []
|
||||
allowed_licenses = ["AAD_PREMIUM", "AAD_PREMIUM_P2"]
|
||||
@@ -39,7 +39,7 @@ class admincenter_users_admins_reduced_license_footprint(Check):
|
||||
)
|
||||
|
||||
if admin_roles:
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=user,
|
||||
resource_name=user.name,
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "admincenter_users_between_two_and_four_global_admins",
|
||||
"CheckTitle": "Ensure that between two and four global admins are designated",
|
||||
"CheckType": [],
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.admincenter.admincenter_client import (
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.admincenter.admincenter_client import (
|
||||
admincenter_client,
|
||||
)
|
||||
|
||||
@@ -16,21 +16,21 @@ class admincenter_users_between_two_and_four_global_admins(Check):
|
||||
metadata: Metadata associated with the check (inherited from Check).
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportMicrosoft365]:
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""Execute the check for the number of Global Administrators.
|
||||
|
||||
This method checks if the number of users with the 'Global Administrator' role
|
||||
is between two and four. If the condition is met, the check passes; otherwise, it fails.
|
||||
|
||||
Returns:
|
||||
List[CheckReportMicrosoft365]: A list containing the result of the check for the Global Administrators.
|
||||
List[CheckReportM365]: A list containing the result of the check for the Global Administrators.
|
||||
"""
|
||||
findings = []
|
||||
directory_roles = admincenter_client.directory_roles
|
||||
global_admin_role = directory_roles.get("Global Administrator", {})
|
||||
|
||||
if global_admin_role:
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=global_admin_role,
|
||||
resource_name=global_admin_role.name,
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_admin_consent_workflow_enabled",
|
||||
"CheckTitle": "Ensure the admin consent workflow is enabled.",
|
||||
"CheckType": [],
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.entra.entra_client import entra_client
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
|
||||
|
||||
class entra_admin_consent_workflow_enabled(Check):
|
||||
@@ -17,7 +17,7 @@ class entra_admin_consent_workflow_enabled(Check):
|
||||
from accessing critical applications or forced to use insecure workarounds.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportMicrosoft365]:
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""
|
||||
Execute the admin consent workflow requirement check.
|
||||
|
||||
@@ -25,12 +25,12 @@ class entra_admin_consent_workflow_enabled(Check):
|
||||
whether the admin consent workflow is enabled.
|
||||
|
||||
Returns:
|
||||
List[CheckReportMicrosoft365]: A list containing the report with the result of the check.
|
||||
List[CheckReportM365]: A list containing the report with the result of the check.
|
||||
"""
|
||||
findings = []
|
||||
admin_consent_policy = entra_client.admin_consent_policy
|
||||
if admin_consent_policy:
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
self.metadata(),
|
||||
resource=admin_consent_policy,
|
||||
resource_name="Admin Consent Policy",
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_admin_portals_access_restriction",
|
||||
"CheckTitle": "Ensure that only administrative roles have access to Microsoft Admin Portals",
|
||||
"CheckAliases": [
|
||||
@@ -1,6 +1,6 @@
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.microsoft365.services.entra.entra_service import (
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
AdminRoles,
|
||||
ConditionalAccessGrantControl,
|
||||
ConditionalAccessPolicyState,
|
||||
@@ -13,15 +13,15 @@ class entra_admin_portals_access_restriction(Check):
|
||||
This check ensures that Conditional Access policies are in place to deny access to the Microsoft 365 admin center for users with limited access roles.
|
||||
"""
|
||||
|
||||
def execute(self) -> list[CheckReportMicrosoft365]:
|
||||
def execute(self) -> list[CheckReportM365]:
|
||||
"""Execute the check to ensure that Conditional Access policies deny access to the Microsoft 365 admin center for users with limited access roles.
|
||||
|
||||
Returns:
|
||||
list[CheckReportMicrosoft365]: A list containing the results of the check.
|
||||
list[CheckReportM365]: A list containing the results of the check.
|
||||
"""
|
||||
findings = []
|
||||
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource={},
|
||||
resource_name="Conditional Access Policies",
|
||||
@@ -52,7 +52,7 @@ class entra_admin_portals_access_restriction(Check):
|
||||
ConditionalAccessGrantControl.BLOCK
|
||||
in policy.grant_controls.built_in_controls
|
||||
):
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=policy,
|
||||
resource_name=policy.display_name,
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_admin_users_cloud_only",
|
||||
"CheckTitle": "Ensure all Microsoft 365 administrative users are cloud-only",
|
||||
"CheckType": [],
|
||||
@@ -15,7 +15,7 @@
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Identify on-premises synchronized administrative users using Microsoft Entra Connect or equivalent tools.\n2. Create new cloud-only administrative user with appropriate permissions.\n3. Migrate administrative tasks from on-premises synchronized users to the new cloud-only user.\n4. Disable or remove the on-premises synchronized administrative users.",
|
||||
"Other": "1. Identify on-premises synchronized administrative users using Microsoft Entra Connect or equivalent tools. 2. Create new cloud-only administrative user with appropriate permissions. 3. Migrate administrative tasks from on-premises synchronized users to the new cloud-only user. 4. Disable or remove the on-premises synchronized administrative users.",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.microsoft365.services.entra.entra_service import AdminRoles
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.m365.services.entra.entra_service import AdminRoles
|
||||
|
||||
|
||||
class entra_admin_users_cloud_only(Check):
|
||||
@@ -12,11 +12,11 @@ class entra_admin_users_cloud_only(Check):
|
||||
If such users are found, the check will fail.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportMicrosoft365]:
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""
|
||||
Execute the check to identify admin accounts with non-cloud-only accounts.
|
||||
Returns:
|
||||
List[CheckReportMicrosoft365]: A list containing the check report with the status and details.
|
||||
List[CheckReportM365]: A list containing the check report with the status and details.
|
||||
"""
|
||||
findings = []
|
||||
if entra_client.users:
|
||||
@@ -29,7 +29,7 @@ class entra_admin_users_cloud_only(Check):
|
||||
):
|
||||
non_cloud_admins.append(user_id)
|
||||
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource={},
|
||||
resource_name="Cloud-only account",
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_admin_users_mfa_enabled",
|
||||
"CheckTitle": "Ensure multifactor authentication is enabled for all users in administrative roles.",
|
||||
"CheckAliases": [
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.microsoft365.services.entra.entra_service import (
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
AdminRoles,
|
||||
ConditionalAccessGrantControl,
|
||||
ConditionalAccessPolicyState,
|
||||
@@ -20,7 +20,7 @@ class entra_admin_users_mfa_enabled(Check):
|
||||
The check fails if no enabled policy is found that requires MFA for any administrative role.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportMicrosoft365]:
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""
|
||||
Execute the admin MFA requirement check for administrative roles.
|
||||
|
||||
@@ -28,11 +28,11 @@ class entra_admin_users_mfa_enabled(Check):
|
||||
indicating whether MFA is enforced for users in administrative roles.
|
||||
|
||||
Returns:
|
||||
List[CheckReportMicrosoft365]: A list containing a single report with the result of the check.
|
||||
List[CheckReportM365]: A list containing a single report with the result of the check.
|
||||
"""
|
||||
findings = []
|
||||
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource={},
|
||||
resource_name="Conditional Access Policies",
|
||||
@@ -62,7 +62,7 @@ class entra_admin_users_mfa_enabled(Check):
|
||||
ConditionalAccessGrantControl.MFA
|
||||
in policy.grant_controls.built_in_controls
|
||||
):
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=entra_client.conditional_access_policies,
|
||||
resource_name=policy.display_name,
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_admin_users_phishing_resistant_mfa_enabled",
|
||||
"CheckTitle": "Ensure phishing-resistant MFA strength is required for all administrator accounts",
|
||||
"CheckType": [],
|
||||
@@ -1,6 +1,6 @@
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.microsoft365.services.entra.entra_service import (
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
AdminRoles,
|
||||
AuthenticationStrength,
|
||||
ConditionalAccessPolicyState,
|
||||
@@ -10,14 +10,14 @@ from prowler.providers.microsoft365.services.entra.entra_service import (
|
||||
class entra_admin_users_phishing_resistant_mfa_enabled(Check):
|
||||
"""Check if Conditional Access policies require Phishing-resistant MFA strength for admin users."""
|
||||
|
||||
def execute(self) -> list[CheckReportMicrosoft365]:
|
||||
def execute(self) -> list[CheckReportM365]:
|
||||
"""Execute the check to ensure that Conditional Access policies require Phishing-resistant MFA strength for admin users.
|
||||
|
||||
Returns:
|
||||
list[CheckReportMicrosoft365]: A list containing the results of the check.
|
||||
list[CheckReportM365]: A list containing the results of the check.
|
||||
"""
|
||||
findings = []
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource={},
|
||||
resource_name="Conditional Access Policies",
|
||||
@@ -49,7 +49,7 @@ class entra_admin_users_phishing_resistant_mfa_enabled(Check):
|
||||
and policy.grant_controls.authentication_strength
|
||||
== AuthenticationStrength.PHISHING_RESISTANT_MFA
|
||||
):
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=policy,
|
||||
resource_name=policy.display_name,
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_admin_users_sign_in_frequency_enabled",
|
||||
"CheckTitle": "Ensure Sign-in frequency periodic reauthentication is enabled and properly configured.",
|
||||
"CheckType": [],
|
||||
@@ -1,6 +1,6 @@
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.microsoft365.services.entra.entra_service import (
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
AdminRoles,
|
||||
ConditionalAccessPolicyState,
|
||||
SignInFrequencyInterval,
|
||||
@@ -11,10 +11,10 @@ from prowler.providers.microsoft365.services.entra.entra_service import (
|
||||
class entra_admin_users_sign_in_frequency_enabled(Check):
|
||||
"""Check if Conditional Access policies enforce sign-in frequency for admin users."""
|
||||
|
||||
def execute(self) -> list[CheckReportMicrosoft365]:
|
||||
def execute(self) -> list[CheckReportM365]:
|
||||
"""Validate sign-in frequency enforcement for admin users."""
|
||||
findings = []
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource={},
|
||||
resource_name="Conditional Access Policies",
|
||||
@@ -40,7 +40,7 @@ class entra_admin_users_sign_in_frequency_enabled(Check):
|
||||
):
|
||||
continue
|
||||
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=policy,
|
||||
resource_name=policy.display_name,
|
||||
@@ -1,4 +1,4 @@
|
||||
from prowler.providers.common.provider import Provider
|
||||
from prowler.providers.microsoft365.services.entra.entra_service import Entra
|
||||
from prowler.providers.m365.services.entra.entra_service import Entra
|
||||
|
||||
entra_client = Entra(Provider.get_global_provider())
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_dynamic_group_for_guests_created",
|
||||
"CheckTitle": "Ensure a dynamic group for guest users is created.",
|
||||
"CheckType": [],
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.entra.entra_client import entra_client
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
|
||||
|
||||
class entra_dynamic_group_for_guests_created(Check):
|
||||
@@ -14,7 +14,7 @@ class entra_dynamic_group_for_guests_created(Check):
|
||||
automated enforcement of conditional access policies and reduces manual management of guest access.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportMicrosoft365]:
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""
|
||||
Execute the dynamic group for guest users check.
|
||||
|
||||
@@ -22,7 +22,7 @@ class entra_dynamic_group_for_guests_created(Check):
|
||||
indicating whether at least one dynamic group exists with a membership rule targeting guest users.
|
||||
|
||||
Returns:
|
||||
List[CheckReportMicrosoft365]: A list containing a single report with the result of the check.
|
||||
List[CheckReportM365]: A list containing a single report with the result of the check.
|
||||
"""
|
||||
findings = []
|
||||
if entra_client.groups:
|
||||
@@ -33,7 +33,7 @@ class entra_dynamic_group_for_guests_created(Check):
|
||||
dynamic_group = group
|
||||
break
|
||||
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
self.metadata(),
|
||||
resource=dynamic_group if dynamic_group else {},
|
||||
resource_name=dynamic_group.name if dynamic_group else "Group",
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_identity_protection_sign_in_risk_enabled",
|
||||
"CheckTitle": "Ensure that Identity Protection sign-in risk policies are enabled",
|
||||
"CheckType": [],
|
||||
@@ -1,6 +1,6 @@
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.microsoft365.services.entra.entra_service import (
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
ConditionalAccessGrantControl,
|
||||
ConditionalAccessPolicyState,
|
||||
RiskLevel,
|
||||
@@ -14,15 +14,15 @@ class entra_identity_protection_sign_in_risk_enabled(Check):
|
||||
This check ensures that at least one Conditional Access policy is a Identity Protection sign-in risk policy.
|
||||
"""
|
||||
|
||||
def execute(self) -> list[CheckReportMicrosoft365]:
|
||||
def execute(self) -> list[CheckReportM365]:
|
||||
"""Execute the check to ensure that at least one Conditional Access policy is a Identity Protection sign-in risk policy.
|
||||
|
||||
Returns:
|
||||
list[CheckReportMicrosoft365]: A list containing the results of the check.
|
||||
list[CheckReportM365]: A list containing the results of the check.
|
||||
"""
|
||||
findings = []
|
||||
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource={},
|
||||
resource_name="Conditional Access Policies",
|
||||
@@ -56,7 +56,7 @@ class entra_identity_protection_sign_in_risk_enabled(Check):
|
||||
):
|
||||
continue
|
||||
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=policy,
|
||||
resource_name=policy.display_name,
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_identity_protection_user_risk_enabled",
|
||||
"CheckTitle": "Ensure that Identity Protection user risk policies are enabled",
|
||||
"CheckType": [],
|
||||
@@ -1,6 +1,6 @@
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.microsoft365.services.entra.entra_service import (
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
ConditionalAccessGrantControl,
|
||||
ConditionalAccessPolicyState,
|
||||
GrantControlOperator,
|
||||
@@ -14,15 +14,15 @@ class entra_identity_protection_user_risk_enabled(Check):
|
||||
This check ensures that at least one Conditional Access policy is a Identity Protection user risk policy.
|
||||
"""
|
||||
|
||||
def execute(self) -> list[CheckReportMicrosoft365]:
|
||||
def execute(self) -> list[CheckReportM365]:
|
||||
"""Execute the check to ensure that at least one Conditional Access policy is a Identity Protection user risk policy.
|
||||
|
||||
Returns:
|
||||
list[CheckReportMicrosoft365]: A list containing the results of the check.
|
||||
list[CheckReportM365]: A list containing the results of the check.
|
||||
"""
|
||||
findings = []
|
||||
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource={},
|
||||
resource_name="Conditional Access Policies",
|
||||
@@ -54,7 +54,7 @@ class entra_identity_protection_user_risk_enabled(Check):
|
||||
continue
|
||||
|
||||
if policy.conditions.user_risk_levels:
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=policy,
|
||||
resource_name=policy.display_name,
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_legacy_authentication_blocked",
|
||||
"CheckTitle": "Ensure that Conditional Access policy blocks legacy authentication",
|
||||
"CheckType": [],
|
||||
@@ -1,6 +1,6 @@
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.microsoft365.services.entra.entra_service import (
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
ClientAppType,
|
||||
ConditionalAccessGrantControl,
|
||||
ConditionalAccessPolicyState,
|
||||
@@ -13,14 +13,14 @@ class entra_legacy_authentication_blocked(Check):
|
||||
This check ensures that at least one Conditional Access policy blocks legacy authentication.
|
||||
"""
|
||||
|
||||
def execute(self) -> list[CheckReportMicrosoft365]:
|
||||
def execute(self) -> list[CheckReportM365]:
|
||||
"""Execute the check to ensure that at least one Conditional Access policy blocks legacy authentication.
|
||||
|
||||
Returns:
|
||||
list[CheckReportMicrosoft365]: A list containing the results of the check.
|
||||
list[CheckReportM365]: A list containing the results of the check.
|
||||
"""
|
||||
findings = []
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource={},
|
||||
resource_name="Conditional Access Policies",
|
||||
@@ -55,7 +55,7 @@ class entra_legacy_authentication_blocked(Check):
|
||||
ConditionalAccessGrantControl.BLOCK
|
||||
in policy.grant_controls.built_in_controls
|
||||
):
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=policy,
|
||||
resource_name=policy.display_name,
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_managed_device_required_for_authentication",
|
||||
"CheckTitle": "Ensure that only managed devices are required for authentication",
|
||||
"CheckType": [],
|
||||
@@ -1,6 +1,6 @@
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.microsoft365.services.entra.entra_service import (
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
ConditionalAccessGrantControl,
|
||||
ConditionalAccessPolicyState,
|
||||
GrantControlOperator,
|
||||
@@ -13,15 +13,15 @@ class entra_managed_device_required_for_authentication(Check):
|
||||
This check ensures that Conditional Access policies are in place to enforce managed device requirement for authentication.
|
||||
"""
|
||||
|
||||
def execute(self) -> list[CheckReportMicrosoft365]:
|
||||
def execute(self) -> list[CheckReportM365]:
|
||||
"""Execute the check to ensure that Conditional Access policies enforce managed device requirement for authentication.
|
||||
|
||||
Returns:
|
||||
list[CheckReportMicrosoft365]: A list containing the results of the check.
|
||||
list[CheckReportM365]: A list containing the results of the check.
|
||||
"""
|
||||
findings = []
|
||||
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource={},
|
||||
resource_name="Conditional Access Policies",
|
||||
@@ -54,7 +54,7 @@ class entra_managed_device_required_for_authentication(Check):
|
||||
continue
|
||||
|
||||
if policy.grant_controls.operator == GrantControlOperator.OR:
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=policy,
|
||||
resource_name=policy.display_name,
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_managed_device_required_for_mfa_registration",
|
||||
"CheckTitle": "Ensure that only managed devices are required for MFA registration",
|
||||
"CheckType": [],
|
||||
@@ -1,6 +1,6 @@
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.microsoft365.services.entra.entra_service import (
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
ConditionalAccessGrantControl,
|
||||
ConditionalAccessPolicyState,
|
||||
GrantControlOperator,
|
||||
@@ -14,15 +14,15 @@ class entra_managed_device_required_for_mfa_registration(Check):
|
||||
This check ensures that Conditional Access policies are in place to enforce MFA registration on a managed device.
|
||||
"""
|
||||
|
||||
def execute(self) -> list[CheckReportMicrosoft365]:
|
||||
def execute(self) -> list[CheckReportM365]:
|
||||
"""Execute the check to ensure that Conditional Access policies enforce MFA registration on a managed device.
|
||||
|
||||
Returns:
|
||||
list[CheckReportMicrosoft365]: A list containing the results of the check.
|
||||
list[CheckReportM365]: A list containing the results of the check.
|
||||
"""
|
||||
findings = []
|
||||
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource={},
|
||||
resource_name="Conditional Access Policies",
|
||||
@@ -53,7 +53,7 @@ class entra_managed_device_required_for_mfa_registration(Check):
|
||||
continue
|
||||
|
||||
if policy.grant_controls.operator == GrantControlOperator.OR:
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=policy,
|
||||
resource_name=policy.display_name,
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_password_hash_sync_enabled",
|
||||
"CheckTitle": "Ensure that password hash sync is enabled for hybrid deployments.",
|
||||
"CheckType": [],
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.entra.entra_client import entra_client
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
|
||||
|
||||
class entra_password_hash_sync_enabled(Check):
|
||||
@@ -16,7 +16,7 @@ class entra_password_hash_sync_enabled(Check):
|
||||
Note: This control applies only to hybrid deployments using Microsoft Entra Connect sync and does not apply to federated domains.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportMicrosoft365]:
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""
|
||||
Execute the password hash synchronization requirement check.
|
||||
|
||||
@@ -24,11 +24,11 @@ class entra_password_hash_sync_enabled(Check):
|
||||
password hash synchronization is enabled.
|
||||
|
||||
Returns:
|
||||
List[CheckReportMicrosoft365]: A list containing the report object with the result of the check.
|
||||
List[CheckReportM365]: A list containing the report object with the result of the check.
|
||||
"""
|
||||
findings = []
|
||||
for organization in entra_client.organizations:
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
self.metadata(),
|
||||
resource=organization,
|
||||
resource_id=organization.id,
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Provider": "microsoft365",
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_policy_ensure_default_user_cannot_create_tenants",
|
||||
"CheckTitle": "Ensure that 'Restrict non-admin users from creating tenants' is set to 'Yes'",
|
||||
"CheckType": [],
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportMicrosoft365
|
||||
from prowler.providers.microsoft365.services.entra.entra_client import entra_client
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
|
||||
|
||||
class entra_policy_ensure_default_user_cannot_create_tenants(Check):
|
||||
@@ -14,7 +14,7 @@ class entra_policy_ensure_default_user_cannot_create_tenants(Check):
|
||||
metadata: Metadata associated with the check (inherited from Check).
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportMicrosoft365]:
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""Execute the check for tenant creation restrictions.
|
||||
|
||||
This method examines the authorization policy settings to determine if
|
||||
@@ -22,12 +22,12 @@ class entra_policy_ensure_default_user_cannot_create_tenants(Check):
|
||||
restricted, the check passes.
|
||||
|
||||
Returns:
|
||||
List[Check_Report_Microsoft365]: A list containing the result of the check.
|
||||
List[Check_Report_M365]: A list containing the result of the check.
|
||||
"""
|
||||
findings = []
|
||||
auth_policy = entra_client.authorization_policy
|
||||
|
||||
report = CheckReportMicrosoft365(
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=auth_policy if auth_policy else {},
|
||||
resource_name=auth_policy.name if auth_policy else "Authorization Policy",
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user