diff --git a/dashboard/assets/images/providers/m365_provider.png b/dashboard/assets/images/providers/m365_provider.png new file mode 100644 index 0000000000..d5bc8bbe5f Binary files /dev/null and b/dashboard/assets/images/providers/m365_provider.png differ diff --git a/dashboard/assets/styles/dist/output.css b/dashboard/assets/styles/dist/output.css index 62954fc771..37505d12e2 100644 --- a/dashboard/assets/styles/dist/output.css +++ b/dashboard/assets/styles/dist/output.css @@ -1361,6 +1361,9 @@ video { .lg\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } + .lg\:grid-cols-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } .lg\:justify-normal { justify-content: normal; @@ -1403,4 +1406,4 @@ video { .\32xl\:w-\[9\%\] { width: 9%; } -} \ No newline at end of file +} diff --git a/dashboard/compliance/cis_4_0_m365.py b/dashboard/compliance/cis_4_0_m365.py new file mode 100644 index 0000000000..94558f33ad --- /dev/null +++ b/dashboard/compliance/cis_4_0_m365.py @@ -0,0 +1,24 @@ +import warnings + +from dashboard.common_methods import get_section_containers_cis + +warnings.filterwarnings("ignore") + + +def get_table(data): + aux = data[ + [ + "REQUIREMENTS_ID", + "REQUIREMENTS_DESCRIPTION", + "REQUIREMENTS_ATTRIBUTES_SECTION", + "CHECKID", + "STATUS", + "REGION", + "ACCOUNTID", + "RESOURCEID", + ] + ].copy() + + return get_section_containers_cis( + aux, "REQUIREMENTS_ID", "REQUIREMENTS_ATTRIBUTES_SECTION" + ) diff --git a/dashboard/lib/layouts.py b/dashboard/lib/layouts.py index 0f13c94929..73d3ec5bcf 100644 --- a/dashboard/lib/layouts.py +++ b/dashboard/lib/layouts.py @@ -57,8 +57,9 @@ def create_layout_overview( html.Div(className="flex", id="azure_card", n_clicks=0), html.Div(className="flex", id="gcp_card", n_clicks=0), html.Div(className="flex", id="k8s_card", n_clicks=0), + html.Div(className="flex", id="m365_card", n_clicks=0), ], - className="grid gap-x-4 mb-[30px] sm:grid-cols-2 lg:grid-cols-4", + className="grid gap-x-4 mb-[30px] sm:grid-cols-2 lg:grid-cols-5", ), html.H4( "Count of Findings by severity", diff --git a/dashboard/pages/compliance.py b/dashboard/pages/compliance.py index d9c5c9f4b0..6fa8288bcf 100644 --- a/dashboard/pages/compliance.py +++ b/dashboard/pages/compliance.py @@ -76,6 +76,8 @@ def load_csv_files(csv_files): result = result.replace("_AZURE", " - AZURE") if "KUBERNETES" in result: result = result.replace("_KUBERNETES", " - KUBERNETES") + if "M65" in result: + result = result.replace("_M65", " - M65") results.append(result) unique_results = set(results) @@ -267,6 +269,15 @@ def display_data( data["REQUIREMENTS_ATTRIBUTES_PROFILE"] = data[ "REQUIREMENTS_ATTRIBUTES_PROFILE" ].apply(lambda x: x.split(" - ")[0]) + + # Add the column ACCOUNTID to the data if the provider is m65 + if "m365" in analytics_input: + data.rename(columns={"TENANTID": "ACCOUNTID"}, inplace=True) + data.rename(columns={"LOCATION": "REGION"}, inplace=True) + if "REQUIREMENTS_ATTRIBUTES_PROFILE" in data.columns: + data["REQUIREMENTS_ATTRIBUTES_PROFILE"] = data[ + "REQUIREMENTS_ATTRIBUTES_PROFILE" + ].apply(lambda x: x.split(" - ")[0]) # Filter the chosen level of the CIS if is_level_1: data = data[data["REQUIREMENTS_ATTRIBUTES_PROFILE"] == "Level 1"] diff --git a/dashboard/pages/overview.py b/dashboard/pages/overview.py index 5dfc62ee7b..3daccd039b 100644 --- a/dashboard/pages/overview.py +++ b/dashboard/pages/overview.py @@ -74,6 +74,9 @@ gcp_provider_logo = html.Img( ks8_provider_logo = html.Img( src="assets/images/providers/k8s_provider.png", alt="k8s provider" ) +m365_provider_logo = html.Img( + src="assets/images/providers/m365_provider.png", alt="m365 provider" +) def load_csv_files(csv_files): @@ -223,6 +226,8 @@ else: accounts.append(account + " - AZURE") if "gcp" in list(data[data["ACCOUNT_NAME"] == account]["PROVIDER"]): accounts.append(account + " - GCP") + if "m365" in list(data[data["ACCOUNT_NAME"] == account]["PROVIDER"]): + accounts.append(account + " - M365") if "ACCOUNT_UID" in data.columns: for account in data["ACCOUNT_UID"].unique(): @@ -273,6 +278,8 @@ else: services.append(service + " - AZURE") if "gcp" in list(data[data["SERVICE_NAME"] == service]["PROVIDER"]): services.append(service + " - GCP") + if "m365" in list(data[data["SERVICE_NAME"] == service]["PROVIDER"]): + services.append(service + " - M365") services = ["All"] + services services = [ @@ -485,6 +492,7 @@ else: Output("azure_card", "children"), Output("gcp_card", "children"), Output("k8s_card", "children"), + Output("m365_card", "children"), Output("subscribe_card", "children"), Output("info-file-over", "title"), Output("severity-filter", "value"), @@ -499,6 +507,7 @@ else: Output("azure_card", "n_clicks"), Output("gcp_card", "n_clicks"), Output("k8s_card", "n_clicks"), + Output("m365_card", "n_clicks"), ], Input("cloud-account-filter", "value"), Input("region-filter", "value"), @@ -513,6 +522,7 @@ else: Input("azure_card", "n_clicks"), Input("gcp_card", "n_clicks"), Input("k8s_card", "n_clicks"), + Input("m365_card", "n_clicks"), Input("sort_button_check_name", "n_clicks"), Input("sort_button_severity", "n_clicks"), Input("sort_button_status", "n_clicks"), @@ -534,6 +544,7 @@ def filter_data( azure_clicks, gcp_clicks, k8s_clicks, + m365_clicks, sort_button_check_name, sort_button_severity, sort_button_status, @@ -554,6 +565,7 @@ def filter_data( azure_clicks = 0 gcp_clicks = 0 k8s_clicks = 0 + m365_clicks = 0 if azure_clicks > 0: filtered_data = data.copy() if azure_clicks % 2 != 0 and "azure" in list(data["PROVIDER"]): @@ -561,6 +573,7 @@ def filter_data( aws_clicks = 0 gcp_clicks = 0 k8s_clicks = 0 + m365_clicks = 0 if gcp_clicks > 0: filtered_data = data.copy() if gcp_clicks % 2 != 0 and "gcp" in list(data["PROVIDER"]): @@ -568,6 +581,7 @@ def filter_data( aws_clicks = 0 azure_clicks = 0 k8s_clicks = 0 + m365_clicks = 0 if k8s_clicks > 0: filtered_data = data.copy() if k8s_clicks % 2 != 0 and "kubernetes" in list(data["PROVIDER"]): @@ -575,6 +589,15 @@ def filter_data( aws_clicks = 0 azure_clicks = 0 gcp_clicks = 0 + m365_clicks = 0 + if m365_clicks > 0: + filtered_data = data.copy() + if m365_clicks % 2 != 0 and "m365" in list(data["PROVIDER"]): + filtered_data = filtered_data[filtered_data["PROVIDER"] == "m365"] + aws_clicks = 0 + azure_clicks = 0 + gcp_clicks = 0 + k8s_clicks = 0 # For all the data, we will add to the status column the value 'MUTED (FAIL)' and 'MUTED (PASS)' depending on the value of the column 'STATUS' and 'MUTED' if "MUTED" in filtered_data.columns: @@ -675,6 +698,8 @@ def filter_data( all_account_names.append(account) if "gcp" in list(data[data["ACCOUNT_NAME"] == account]["PROVIDER"]): all_account_names.append(account) + if "m365" in list(data[data["ACCOUNT_NAME"] == account]["PROVIDER"]): + all_account_names.append(account) all_items = all_account_ids + all_account_names + ["All"] @@ -692,6 +717,8 @@ def filter_data( cloud_accounts_options.append(item + " - AZURE") if "gcp" in list(data[data["ACCOUNT_NAME"] == item]["PROVIDER"]): cloud_accounts_options.append(item + " - GCP") + if "m365" in list(data[data["ACCOUNT_NAME"] == item]["PROVIDER"]): + cloud_accounts_options.append(item + " - M365") # Filter ACCOUNT if cloud_account_values == ["All"]: @@ -790,6 +817,7 @@ def filter_data( service_filter_options = ["All"] all_items = filtered_data["SERVICE_NAME"].unique() + for item in all_items: if item not in service_filter_options and item.__class__.__name__ == "str": if "aws" in list( @@ -808,6 +836,10 @@ def filter_data( filtered_data[filtered_data["SERVICE_NAME"] == item]["PROVIDER"] ): service_filter_options.append(item + " - GCP") + if "m365" in list( + filtered_data[filtered_data["SERVICE_NAME"] == item]["PROVIDER"] + ): + service_filter_options.append(item + " - M365") # Filter Service if service_values == ["All"]: @@ -1235,6 +1267,10 @@ def filter_data( filtered_data.loc[ filtered_data["ACCOUNT_UID"] == account, "ACCOUNT_UID" ] = (account + " - GCP") + if "m365" in list(data[data["ACCOUNT_UID"] == account]["PROVIDER"]): + filtered_data.loc[ + filtered_data["ACCOUNT_UID"] == account, "ACCOUNT_UID" + ] = (account + " - M365") table_collapsible = [] for item in filtered_data.to_dict("records"): @@ -1302,6 +1338,9 @@ def filter_data( k8s_card = create_provider_card( "kubernetes", ks8_provider_logo, "Clusters", full_filtered_data ) + m365_card = create_provider_card( + "m365", m365_provider_logo, "Accounts", full_filtered_data + ) # Subscribe to prowler SaaS card subscribe_card = [ @@ -1346,6 +1385,7 @@ def filter_data( azure_card, gcp_card, k8s_card, + m365_card, subscribe_card, list_files, severity_values, @@ -1360,6 +1400,7 @@ def filter_data( azure_clicks, gcp_clicks, k8s_clicks, + m365_clicks, ) else: return ( @@ -1377,6 +1418,7 @@ def filter_data( azure_card, gcp_card, k8s_card, + m365_card, subscribe_card, list_files, severity_values, @@ -1391,6 +1433,7 @@ def filter_data( azure_clicks, gcp_clicks, k8s_clicks, + m365_clicks, ) diff --git a/docs/img/dashboard.png b/docs/img/dashboard.png index 2d2fe3cc95..2392debbb7 100644 Binary files a/docs/img/dashboard.png and b/docs/img/dashboard.png differ diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index eaeb7d4812..d060edaf4b 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -36,6 +36,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Add new check `teams_meeting_presenters_restricted` [(#7613)](https://github.com/prowler-cloud/prowler/pull/7613) - Add new check `teams_meeting_chat_anonymous_users_disabled` [(#7579)](https://github.com/prowler-cloud/prowler/pull/7579) - Add Prowler Threat Score Compliance Framework [(#7603)](https://github.com/prowler-cloud/prowler/pull/7603) +- Add support for m365 provider in Prowler Dashboard [(#7633)](https://github.com/prowler-cloud/prowler/pull/7633) - Add new check for Modern Authentication enabled for Exchange Online in M365 [(#7636)](https://github.com/prowler-cloud/prowler/pull/7636) - Add new check `sharepoint_onedrive_sync_restricted_unmanaged_devices` [(#7589)](https://github.com/prowler-cloud/prowler/pull/7589) - Add new check for Additional Storage restricted for Exchange in M365 [(#7638)](https://github.com/prowler-cloud/prowler/pull/7638)